@svgrid/enterprise 2.4.0 → 2.5.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.
- package/dist/cdn/svgrid-enterprise.svelte-external.js +5917 -5545
- package/dist/designer/assets/{index-okIcRP9P.js → index-qL7ir9C7.js} +3 -3
- package/dist/designer/assets/{src-CIla2aVG.js → src-96d31O63.js} +259 -228
- package/dist/designer/assets/{src-BvsJ00AO.css → src-D-O2F805.css} +1 -1
- package/dist/designer/index.html +3 -3
- package/dist/node/studio.js +681 -84
- package/package.json +3 -3
- package/src/SvGridEditPanel.dom.test.ts +174 -0
- package/src/SvGridEditPanel.svelte +209 -46
- package/src/edit-panel.test.ts +145 -36
- package/src/edit-panel.ts +167 -16
- package/src/expressions/evaluate.ts +1 -1
- package/src/expressions/parse.ts +3 -3
- package/src/index.ts +8 -0
- package/src/license-core.ts +77 -5
- package/src/license-expiry.test.ts +91 -0
- package/src/license.ts +31 -2
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +178 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js +3875 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js +21 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js +1938 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js +205 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js +18 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js +264 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js +697 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +3 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +134 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +55 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +2 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +46843 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +237 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +2 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +4 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +7 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +4 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +5 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +5 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +5 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +6 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +83 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +559 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +24 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +122 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +2 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +89 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +241 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js +4938 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js +495 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js +190 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js +8 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js +138 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js.map +1 -0
- package/src/schema.ts +63 -0
- package/src/studio/emit-project.test.ts +191 -0
- package/src/studio/emit-project.ts +237 -27
- package/src/studio/emit-schema.ts +4 -1
- package/src/studio/index.ts +2 -0
- package/src/studio/init-flow.test.ts +150 -0
- package/src/studio/init-flow.ts +169 -22
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +178 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +3875 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +21 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +1938 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +205 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +18 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +264 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +697 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +3 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +134 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +55 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +2 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +46843 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +237 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +2 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +4 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +7 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +4 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +5 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +5 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +5 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +6 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +83 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +559 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +24 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +122 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +2 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +89 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +241 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +4938 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +495 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +190 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +8 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +138 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js.map +1 -0
- package/src/studio/project.test.ts +30 -0
- package/src/studio/project.ts +72 -8
- package/src/studio/samples/crm.ts +11 -2
- package/src/studio/samples/insurance.ts +10 -4
- package/src/studio/samples/samples.test.ts +36 -0
- package/src/studio/samples/shared.ts +14 -0
- package/src/studio/samples/support.ts +12 -5
- package/src/studio/screen-suites.test.ts +43 -0
- package/src/studio/screen-suites.ts +5 -2
- package/src/studio/user-error.ts +31 -0
- package/src/upgrade-prompt.ts +23 -10
- package/src/version.test.ts +17 -0
- package/src/version.ts +13 -0
package/dist/node/studio.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "@svgrid/grid/filtering";
|
|
1
2
|
import { defaultThemePreset, getThemePreset, resolveThemeTokens as resolveThemeTokens$1, themePresets } from "@svgrid/grid/themes";
|
|
2
3
|
//#region src/schema.ts
|
|
3
4
|
/** `first_name` / `firstName` / `first-name` -> `First Name`. */
|
|
@@ -128,7 +129,8 @@ function schemaToFormFields(schema) {
|
|
|
128
129
|
placeholder: f.input?.placeholder,
|
|
129
130
|
help: f.input?.help,
|
|
130
131
|
span: f.input?.span ?? (f.type === "json" ? 2 : 1),
|
|
131
|
-
computed: f.computed
|
|
132
|
+
computed: f.computed,
|
|
133
|
+
when: f.when
|
|
132
134
|
};
|
|
133
135
|
});
|
|
134
136
|
}
|
|
@@ -1119,7 +1121,7 @@ function introspectOpenApi(specText) {
|
|
|
1119
1121
|
}
|
|
1120
1122
|
//#endregion
|
|
1121
1123
|
//#region src/studio/introspect-db.ts
|
|
1122
|
-
function truthy(v) {
|
|
1124
|
+
function truthy$1(v) {
|
|
1123
1125
|
return v === 1 || v === "1" || v === true;
|
|
1124
1126
|
}
|
|
1125
1127
|
function identQuote(name) {
|
|
@@ -1154,7 +1156,7 @@ function fieldFrom(name, rawType, pk, notNull) {
|
|
|
1154
1156
|
}
|
|
1155
1157
|
/** Map catalog rows (aliased name/type/nullable/pk) from information_schema dialects. */
|
|
1156
1158
|
function infoSchemaFields(rows) {
|
|
1157
|
-
return rows.map((r) => fieldFrom(String(r.name), String(r.type ?? ""), truthy(r.pk), !truthy(r.nullable)));
|
|
1159
|
+
return rows.map((r) => fieldFrom(String(r.name), String(r.type ?? ""), truthy$1(r.pk), !truthy$1(r.nullable)));
|
|
1158
1160
|
}
|
|
1159
1161
|
/** Map SQLite `PRAGMA table_info` rows (name/type/notnull/pk-as-index). */
|
|
1160
1162
|
function sqliteFields(rows) {
|
|
@@ -1920,6 +1922,145 @@ function scaffoldApp(schemas, options = {}) {
|
|
|
1920
1922
|
return { files };
|
|
1921
1923
|
}
|
|
1922
1924
|
//#endregion
|
|
1925
|
+
//#region src/expressions/expression-columns.ts
|
|
1926
|
+
var TEXT = ["text"];
|
|
1927
|
+
var NUM = ["number"];
|
|
1928
|
+
var DATES = ["date", "datetime"];
|
|
1929
|
+
var ALL = [
|
|
1930
|
+
"text",
|
|
1931
|
+
"number",
|
|
1932
|
+
"date",
|
|
1933
|
+
"datetime",
|
|
1934
|
+
"boolean"
|
|
1935
|
+
];
|
|
1936
|
+
/** The full operator catalogue, ordered as the editor should present it. */
|
|
1937
|
+
var OPERATORS = [
|
|
1938
|
+
{
|
|
1939
|
+
value: "equals",
|
|
1940
|
+
label: "Equals",
|
|
1941
|
+
types: ALL
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
value: "notEquals",
|
|
1945
|
+
label: "Not equals",
|
|
1946
|
+
types: ALL
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
value: "contains",
|
|
1950
|
+
label: "Contains",
|
|
1951
|
+
types: TEXT
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
value: "notContains",
|
|
1955
|
+
label: "Not contains",
|
|
1956
|
+
types: TEXT
|
|
1957
|
+
},
|
|
1958
|
+
{
|
|
1959
|
+
value: "startsWith",
|
|
1960
|
+
label: "Starts with",
|
|
1961
|
+
types: TEXT
|
|
1962
|
+
},
|
|
1963
|
+
{
|
|
1964
|
+
value: "endsWith",
|
|
1965
|
+
label: "Ends with",
|
|
1966
|
+
types: TEXT
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
value: "regex",
|
|
1970
|
+
label: "Matches regex",
|
|
1971
|
+
types: TEXT
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
value: "greaterThan",
|
|
1975
|
+
label: "Greater than",
|
|
1976
|
+
types: [...NUM, ...DATES]
|
|
1977
|
+
},
|
|
1978
|
+
{
|
|
1979
|
+
value: "lessThan",
|
|
1980
|
+
label: "Less than",
|
|
1981
|
+
types: [...NUM, ...DATES]
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
value: "between",
|
|
1985
|
+
label: "Between",
|
|
1986
|
+
types: [...NUM, ...DATES],
|
|
1987
|
+
range: true
|
|
1988
|
+
},
|
|
1989
|
+
{
|
|
1990
|
+
value: "in",
|
|
1991
|
+
label: "In",
|
|
1992
|
+
types: ["text", "number"],
|
|
1993
|
+
set: true
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
value: "notIn",
|
|
1997
|
+
label: "Not in",
|
|
1998
|
+
types: ["text", "number"],
|
|
1999
|
+
set: true
|
|
2000
|
+
},
|
|
2001
|
+
{
|
|
2002
|
+
value: "isBlank",
|
|
2003
|
+
label: "Is blank",
|
|
2004
|
+
types: ALL,
|
|
2005
|
+
valueless: true
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
value: "isNotBlank",
|
|
2009
|
+
label: "Is not blank",
|
|
2010
|
+
types: ALL,
|
|
2011
|
+
valueless: true
|
|
2012
|
+
}
|
|
2013
|
+
];
|
|
2014
|
+
new Map(OPERATORS.map((op) => [op.value, op]));
|
|
2015
|
+
//#endregion
|
|
2016
|
+
//#region src/expressions/parse.ts
|
|
2017
|
+
/** Every column id referenced anywhere in a predicate (deduped, in order). */
|
|
2018
|
+
function collectColumnRefs(expr) {
|
|
2019
|
+
const out = /* @__PURE__ */ new Set();
|
|
2020
|
+
walkPredicate(expr, out);
|
|
2021
|
+
return [...out];
|
|
2022
|
+
}
|
|
2023
|
+
function walkPredicate(expr, out) {
|
|
2024
|
+
switch (expr.kind) {
|
|
2025
|
+
case "and":
|
|
2026
|
+
case "or":
|
|
2027
|
+
expr.parts.forEach((p) => walkPredicate(p, out));
|
|
2028
|
+
return;
|
|
2029
|
+
case "not":
|
|
2030
|
+
walkPredicate(expr.expr, out);
|
|
2031
|
+
return;
|
|
2032
|
+
case "cmp":
|
|
2033
|
+
out.add(expr.column);
|
|
2034
|
+
return;
|
|
2035
|
+
case "scalarCmp":
|
|
2036
|
+
walkScalar(expr.left, out);
|
|
2037
|
+
walkScalar(expr.right, out);
|
|
2038
|
+
return;
|
|
2039
|
+
case "const": return;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
function walkScalar(expr, out) {
|
|
2043
|
+
switch (expr.kind) {
|
|
2044
|
+
case "col":
|
|
2045
|
+
out.add(expr.id);
|
|
2046
|
+
return;
|
|
2047
|
+
case "agg":
|
|
2048
|
+
out.add(expr.column);
|
|
2049
|
+
return;
|
|
2050
|
+
case "neg":
|
|
2051
|
+
walkScalar(expr.expr, out);
|
|
2052
|
+
return;
|
|
2053
|
+
case "bin":
|
|
2054
|
+
walkScalar(expr.left, out);
|
|
2055
|
+
walkScalar(expr.right, out);
|
|
2056
|
+
return;
|
|
2057
|
+
case "func":
|
|
2058
|
+
expr.args.forEach((a) => walkScalar(a, out));
|
|
2059
|
+
return;
|
|
2060
|
+
case "lit": return;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
//#endregion
|
|
1923
2064
|
//#region src/studio/ui-components.generated.ts
|
|
1924
2065
|
var GENERATED_UI_SURFACE = {
|
|
1925
2066
|
"SvButton": {
|
|
@@ -11402,11 +11543,15 @@ var SSR_READ_KINDS = /* @__PURE__ */ new Set([
|
|
|
11402
11543
|
* null when it must stay SPA. */
|
|
11403
11544
|
function ssrScreenShape(project, screen) {
|
|
11404
11545
|
if (!screen.entity || screen.code) return null;
|
|
11405
|
-
const
|
|
11406
|
-
|
|
11546
|
+
const source = project.dataSources?.[screen.entity];
|
|
11547
|
+
const kind = source?.kind ?? project.dataSource;
|
|
11548
|
+
if (kind === "rest") {
|
|
11549
|
+
if (!(source?.kind === "rest" && /^https?:\/\//i.test(source.baseUrl ?? ""))) return null;
|
|
11550
|
+
} else if (kind !== "memory" && kind !== "sql") return null;
|
|
11407
11551
|
const blocks = screen.blocks ?? [];
|
|
11408
|
-
|
|
11409
|
-
|
|
11552
|
+
const grids = blocks.filter((b) => b.config.kind === "grid");
|
|
11553
|
+
if (grids.length === 1 && blocks.every((b) => b.config.kind === "grid" || b.config.kind === "filter")) {
|
|
11554
|
+
const g = grids[0].config;
|
|
11410
11555
|
if (g.treeData || g.scheduler) return null;
|
|
11411
11556
|
return "grid";
|
|
11412
11557
|
}
|
|
@@ -11431,6 +11576,34 @@ function setScreenRenderMode(project, screenId, mode) {
|
|
|
11431
11576
|
function isSsrScreen(project, screen) {
|
|
11432
11577
|
return screenRenderMode(project, screen) === "ssr" && ssrEligible(project, screen);
|
|
11433
11578
|
}
|
|
11579
|
+
/**
|
|
11580
|
+
* Turn server rendering on for every screen that can support it, for entities
|
|
11581
|
+
* backed by a real database. Applied when a project is first generated, so a new
|
|
11582
|
+
* app reads as idiomatic SvelteKit (a `load` and form `actions`) rather than a
|
|
11583
|
+
* client SPA. Screens that already state a mode are left alone, so this never
|
|
11584
|
+
* overrides a choice somebody made.
|
|
11585
|
+
*
|
|
11586
|
+
* Deliberately limited to sources that hold one copy of the data. In-memory and
|
|
11587
|
+
* PGlite sources are module singletons, so an SSR screen would read and write the
|
|
11588
|
+
* *server's* copy of the rows while the app's remaining SPA screens read the
|
|
11589
|
+
* browser's - create a row on one and the other never sees it. SQL and REST have
|
|
11590
|
+
* no such split: every path goes to the same database or the same remote API.
|
|
11591
|
+
*/
|
|
11592
|
+
var SSR_DEFAULT_KINDS = /* @__PURE__ */ new Set(["sql", "rest"]);
|
|
11593
|
+
function withSsrDefaults(project) {
|
|
11594
|
+
return {
|
|
11595
|
+
...project,
|
|
11596
|
+
screens: project.screens.map((screen) => {
|
|
11597
|
+
if (screen.renderMode || !screen.entity) return screen;
|
|
11598
|
+
const kind = project.dataSources?.[screen.entity]?.kind ?? project.dataSource;
|
|
11599
|
+
if (!SSR_DEFAULT_KINDS.has(kind)) return screen;
|
|
11600
|
+
return ssrEligible(project, screen) ? {
|
|
11601
|
+
...screen,
|
|
11602
|
+
renderMode: "ssr"
|
|
11603
|
+
} : screen;
|
|
11604
|
+
})
|
|
11605
|
+
};
|
|
11606
|
+
}
|
|
11434
11607
|
/** The triggers configured for an entity (or an empty object). */
|
|
11435
11608
|
function triggersOf(project, entity) {
|
|
11436
11609
|
return project.triggers?.[entity] ?? {};
|
|
@@ -13333,6 +13506,19 @@ function validateProject(project) {
|
|
|
13333
13506
|
level: "warning",
|
|
13334
13507
|
message: "No screens yet."
|
|
13335
13508
|
});
|
|
13509
|
+
for (const entity of project.entities) {
|
|
13510
|
+
const names = new Set(entity.fields.map((f) => f.field));
|
|
13511
|
+
for (const field of entity.fields) {
|
|
13512
|
+
if (!field.when) continue;
|
|
13513
|
+
for (const [flag, expr] of Object.entries(field.when)) {
|
|
13514
|
+
if (!expr) continue;
|
|
13515
|
+
for (const ref of collectColumnRefs(expr)) if (!names.has(ref)) issues.push({
|
|
13516
|
+
level: "error",
|
|
13517
|
+
message: `"${entity.name}.${field.field}" has a ${flag} condition on "${ref}", which is not a field on ${entity.name}.`
|
|
13518
|
+
});
|
|
13519
|
+
}
|
|
13520
|
+
}
|
|
13521
|
+
}
|
|
13336
13522
|
const routes = /* @__PURE__ */ new Set();
|
|
13337
13523
|
for (const s of project.screens) {
|
|
13338
13524
|
if (s.entity !== void 0 && !entityOf(project, s.entity)) issues.push({
|
|
@@ -13854,7 +14040,7 @@ function namesFor(schema) {
|
|
|
13854
14040
|
/** Lookup variable name for a relation field, e.g. `postAuthorIdLookup`. */
|
|
13855
14041
|
var lookupVar = (schema, field) => `${camel(schema.name)}${pascal(field)}Lookup`;
|
|
13856
14042
|
function rowType(schema, derived) {
|
|
13857
|
-
const optional = (f) => derived.has(f.field) || !!f.formula || !!f.computed;
|
|
14043
|
+
const optional = (f) => derived.has(f.field) || !!f.formula || !!f.computed || !!f.when?.visible;
|
|
13858
14044
|
const lines = schema.fields.map((f) => ` ${f.field}${optional(f) ? "?" : ""}: ${tsType(f)}`);
|
|
13859
14045
|
return `export type ${pascal(schema.name)} = {\n${lines.join("\n")}\n}`;
|
|
13860
14046
|
}
|
|
@@ -14925,6 +15111,27 @@ function themeStyleString(theme) {
|
|
|
14925
15111
|
return `${Object.entries(tokens).map(([k, v]) => `${k}: ${v}`).join("; ")}; color-scheme: ${isDarkTheme(theme) ? "dark" : "light"}; font-family: ${tokens["--sg-font"]}`;
|
|
14926
15112
|
}
|
|
14927
15113
|
//#endregion
|
|
15114
|
+
//#region src/edit-panel.ts
|
|
15115
|
+
/** True when any field's behaviour depends on the values (i.e. has a `when`). */
|
|
15116
|
+
function hasFieldConditions(schema) {
|
|
15117
|
+
return schema.fields.some((f) => !!f.when && (!!f.when.visible || !!f.when.disabled || !!f.when.required));
|
|
15118
|
+
}
|
|
15119
|
+
//#endregion
|
|
15120
|
+
//#region src/version.ts
|
|
15121
|
+
/**
|
|
15122
|
+
* The version of this package, as a value the pure (browser-safe) generator can
|
|
15123
|
+
* read - it cannot open `package.json` at runtime.
|
|
15124
|
+
*
|
|
15125
|
+
* Generated apps depend on `^SVGRID_VERSION` rather than `latest`, so an app
|
|
15126
|
+
* always installs a runtime at least as new as the generator that wrote it.
|
|
15127
|
+
* `latest` made that a coin toss: an app scaffolded today and the same app
|
|
15128
|
+
* scaffolded tomorrow could resolve different runtimes, and an unrelated publish
|
|
15129
|
+
* could break an app nobody had touched.
|
|
15130
|
+
*
|
|
15131
|
+
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
15132
|
+
*/
|
|
15133
|
+
var SVGRID_VERSION = "2.4.0";
|
|
15134
|
+
//#endregion
|
|
14928
15135
|
//#region src/studio/emit-project.ts
|
|
14929
15136
|
var has = (blocks, kind) => blocks.some((b) => b.config.kind === kind);
|
|
14930
15137
|
/** Per-Tabs-block active-tab state var + a stable tab id. */
|
|
@@ -16828,9 +17035,6 @@ function screenChildEntities(blocks, resolve) {
|
|
|
16828
17035
|
}
|
|
16829
17036
|
return [...out.values()];
|
|
16830
17037
|
}
|
|
16831
|
-
/** `+page.server.ts` for a read-only SSR screen (data-viz / detail / master-
|
|
16832
|
-
* detail): a `load` that returns the full dataset (+ each child collection) -
|
|
16833
|
-
* the page renders real SSR HTML from `data.*`. No actions (read-only). */
|
|
16834
17038
|
function ssrReadServerFile(schema, screen, sourceKind, accessEnabled, screenIds, resolve) {
|
|
16835
17039
|
const n = namesFor(schema);
|
|
16836
17040
|
const isSql = sourceKind === "sql";
|
|
@@ -16845,8 +17049,8 @@ function ssrReadServerFile(schema, screen, sourceKind, accessEnabled, screenIds,
|
|
|
16845
17049
|
contents: `import type { PageServerLoad } from './$types'
|
|
16846
17050
|
import type { ServerRequest } from '@svgrid/grid'
|
|
16847
17051
|
${isSql ? "import { createKitDataSource } from '@svgrid/enterprise'\n" : ""}${rbac ? "import { error } from '@sveltejs/kit'\nimport { authorizeAction, getServerRole } from '$lib/access'\n" : ""}${memImports.length ? `import { ${memImports.join(", ")} } from '$lib/data'\n` : ""}
|
|
16848
|
-
//
|
|
16849
|
-
//
|
|
17052
|
+
// This screen renders on the server: real SSR HTML, not just a server-side
|
|
17053
|
+
// load. Stated explicitly so it holds whichever way the root layout is set.
|
|
16850
17054
|
export const ssr = true
|
|
16851
17055
|
${rbac ? `\nconst SCREEN_IDS = ${JSON.stringify(screenIds)}\n` : ""}
|
|
16852
17056
|
const PAGE: ServerRequest = { startRow: 0, endRow: 1000, pageIndex: 0, pageSize: 1000, sortModel: [], filterModel: {} }
|
|
@@ -16896,18 +17100,42 @@ function ssrQueryHelperFile() {
|
|
|
16896
17100
|
};
|
|
16897
17101
|
}
|
|
16898
17102
|
var htmlEsc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
17103
|
+
/** Escape a value going inside a double-quoted HTML attribute. */
|
|
17104
|
+
var attrEsc = (s) => htmlEsc(s).replace(/"/g, """);
|
|
17105
|
+
/**
|
|
17106
|
+
* The field's own constraints as native HTML attributes, so a server-rendered
|
|
17107
|
+
* form validates in the browser too. The server still re-checks every one of
|
|
17108
|
+
* them: these only save a round-trip, they never decide anything.
|
|
17109
|
+
*/
|
|
17110
|
+
function attrs(f) {
|
|
17111
|
+
const out = [];
|
|
17112
|
+
if (f.input?.placeholder) out.push(`placeholder="${attrEsc(f.input.placeholder)}"`);
|
|
17113
|
+
if (f.type === "number") {
|
|
17114
|
+
if (f.min != null) out.push(`min="${f.min}"`);
|
|
17115
|
+
if (f.max != null) out.push(`max="${f.max}"`);
|
|
17116
|
+
if (f.input?.step != null) out.push(`step="${f.input.step}"`);
|
|
17117
|
+
} else {
|
|
17118
|
+
if (f.minLength != null) out.push(`minlength="${f.minLength}"`);
|
|
17119
|
+
if (f.maxLength != null) out.push(`maxlength="${f.maxLength}"`);
|
|
17120
|
+
if (f.pattern && !f.format) out.push(`pattern="${attrEsc(f.pattern)}"`);
|
|
17121
|
+
}
|
|
17122
|
+
return out.length ? " " + out.join(" ") : "";
|
|
17123
|
+
}
|
|
16899
17124
|
/** The `+page.server.ts` + SSR `+page.svelte` for a single-grid CRUD screen. */
|
|
16900
17125
|
function emitSsrGridScreen(schema, screen, sourceKind, accessEnabled, screenIds, byName) {
|
|
16901
17126
|
const n = namesFor(schema);
|
|
16902
17127
|
const isSql = sourceKind === "sql";
|
|
17128
|
+
const isRest = sourceKind === "rest";
|
|
16903
17129
|
const relSchemaOf = (f) => byName.get(f.relation.entity);
|
|
16904
17130
|
const relIdOf = (rs) => rs.idField ?? rs.fields.find((x) => x.primaryKey)?.field ?? "id";
|
|
16905
|
-
const grid = screen.blocks
|
|
17131
|
+
const grid = screen.blocks.find((b) => b.config.kind === "grid").config;
|
|
17132
|
+
const facetPanel = screen.blocks.find((b) => b.config.kind === "filter")?.config;
|
|
16906
17133
|
const pageSize = grid.pageSize && grid.pageSize > 0 ? grid.pageSize : 25;
|
|
16907
17134
|
const wantsFilter = grid.filterable !== false;
|
|
16908
17135
|
const idField = schema.idField ?? schema.fields.find((f) => f.primaryKey)?.field ?? "id";
|
|
16909
17136
|
const isFormHidden = (f) => f.hidden === true || typeof f.hidden === "object" && !!f.hidden.form;
|
|
16910
|
-
const
|
|
17137
|
+
const editable = schema.fields.filter((f) => f.field !== idField && !f.primaryKey && !f.readonly && !f.computed && !f.formula && !isFormHidden(f));
|
|
17138
|
+
const formFields = grid.formFields?.length ? grid.formFields.map((name) => editable.find((f) => f.field === name)).filter(Boolean) : editable;
|
|
16911
17139
|
const normType = (t) => t === "number" ? "number" : t === "boolean" ? "boolean" : "text";
|
|
16912
17140
|
const fieldTypesLit = `{ ${formFields.map((f) => `${jsStr(f.field)}: ${jsStr(normType(f.type))}`).join(", ")} }`;
|
|
16913
17141
|
const isRel = (f) => f.type === "relation" && !!f.relation && byName.has(f.relation.entity);
|
|
@@ -16924,12 +17152,18 @@ function emitSsrGridScreen(schema, screen, sourceKind, accessEnabled, screenIds,
|
|
|
16924
17152
|
const relReturn = relFields.map((f) => `, ${f.field}Options`).join("");
|
|
16925
17153
|
const src = isSql ? "source(fetch)" : n.sourceVar;
|
|
16926
17154
|
const fetchArg = isSql ? ", fetch" : "";
|
|
16927
|
-
const
|
|
16928
|
-
const
|
|
17155
|
+
const conditional = hasFieldConditions(schema);
|
|
17156
|
+
const readValues = (fd) => conditional ? `stripHiddenValues(${n.schemaVar}, formToValues(${fd}))` : `formToValues(${fd})`;
|
|
17157
|
+
const enterpriseImports = [
|
|
17158
|
+
"validateAll",
|
|
17159
|
+
...conditional ? ["stripHiddenValues"] : [],
|
|
17160
|
+
...isSql ? ["createKitDataSource"] : []
|
|
17161
|
+
].join(", ");
|
|
17162
|
+
const sourceImport = isSql ? "" : `import { ${[.../* @__PURE__ */ new Set([n.sourceVar, ...relSourceVars]), ...isRest ? [] : ["nextId"]].join(", ")} } from '$lib/data'\n`;
|
|
16929
17163
|
const schemaImport = `import { ${n.schemaVar}${isSql ? `, type ${n.type}` : ""} } from '$lib/schemas'`;
|
|
16930
|
-
const idConst = isSql ? "" : `\nconst ID_FIELD = ${jsStr(idField)}`;
|
|
17164
|
+
const idConst = isSql || isRest ? "" : `\nconst ID_FIELD = ${jsStr(idField)}`;
|
|
16931
17165
|
const srcHelper = isSql ? `\n// Same-origin client over the connected /api/${n.route} route (that route runs\n// validation, RBAC, triggers + audit via createKitHandlers).\nconst source = (fetch: typeof globalThis.fetch) => createKitDataSource<${n.type}>({ endpoint: ${jsStr("/api/" + n.route)}, fetch })\n` : "";
|
|
16932
|
-
const createCall = isSql ? `await ${src}.createRow(values)` : `await ${n.sourceVar}.createRow({ [ID_FIELD]: nextId(${jsStr(n.idPrefix)}), ...values })`;
|
|
17166
|
+
const createCall = isSql || isRest ? `await ${src}.createRow(values)` : `await ${n.sourceVar}.createRow({ [ID_FIELD]: nextId(${jsStr(n.idPrefix)}), ...values })`;
|
|
16933
17167
|
const rbac = accessEnabled && !isSql;
|
|
16934
17168
|
const localsArg = rbac ? ", locals" : "";
|
|
16935
17169
|
const readGuard = rbac ? ` if (!authorizeAction(getServerRole({ locals }), 'read', SCREEN_IDS)) throw error(403, 'Not allowed')\n` : "";
|
|
@@ -16940,8 +17174,8 @@ import { ${enterpriseImports} } from '@svgrid/enterprise'
|
|
|
16940
17174
|
${rbac ? "import { authorizeAction, getServerRole } from '$lib/access'\n" : ""}${sourceImport}${schemaImport}
|
|
16941
17175
|
import { planFromSearchParams } from '$lib/server/query'
|
|
16942
17176
|
|
|
16943
|
-
//
|
|
16944
|
-
//
|
|
17177
|
+
// This screen renders on the server: real SSR HTML, not just a server-side
|
|
17178
|
+
// load. Stated explicitly so it holds whichever way the root layout is set.
|
|
16945
17179
|
export const ssr = true
|
|
16946
17180
|
${idConst}${rbac ? `\nconst SCREEN_IDS = ${JSON.stringify(screenIds)}` : ""}
|
|
16947
17181
|
const FIELD_TYPES: Record<string, 'text' | 'number' | 'boolean'> = ${fieldTypesLit}
|
|
@@ -16967,7 +17201,7 @@ ${relPrefetch ? relPrefetch + "\n" : ""} return { rows, total: rowCount, page:
|
|
|
16967
17201
|
|
|
16968
17202
|
export const actions: Actions = {
|
|
16969
17203
|
create: async ({ request${fetchArg}${localsArg} }) => {
|
|
16970
|
-
${writeGuard("create")} const values =
|
|
17204
|
+
${writeGuard("create")} const values = ${readValues("await request.formData()")}
|
|
16971
17205
|
const errors = await validateAll(${n.schemaVar}, values)
|
|
16972
17206
|
if (Object.keys(errors).length) return fail(422, { errors, values })
|
|
16973
17207
|
${createCall}
|
|
@@ -16976,7 +17210,7 @@ ${writeGuard("create")} const values = formToValues(await request.formData())
|
|
|
16976
17210
|
update: async ({ request${fetchArg}${localsArg} }) => {
|
|
16977
17211
|
const fd = await request.formData()
|
|
16978
17212
|
${writeGuard("update")} const id = String(fd.get('__id') ?? '')
|
|
16979
|
-
const values =
|
|
17213
|
+
const values = ${readValues("fd")}
|
|
16980
17214
|
const errors = await validateAll(${n.schemaVar}, values)
|
|
16981
17215
|
if (Object.keys(errors).length) return fail(422, { errors, values })
|
|
16982
17216
|
await ${src}.updateRow(id, values)
|
|
@@ -16989,6 +17223,58 @@ ${writeGuard("delete")} const fd = await request.formData()
|
|
|
16989
17223
|
},
|
|
16990
17224
|
}
|
|
16991
17225
|
`;
|
|
17226
|
+
const facetControls = {
|
|
17227
|
+
select: false,
|
|
17228
|
+
input: false
|
|
17229
|
+
};
|
|
17230
|
+
const facetForm = (() => {
|
|
17231
|
+
if (!facetPanel) return "";
|
|
17232
|
+
const fields = filterFieldsOf(schema, facetPanel);
|
|
17233
|
+
if (!fields.length) return "";
|
|
17234
|
+
const cur = (f) => `(page.url.searchParams.get(${jsStr("f_" + f.field)}) ?? '')`;
|
|
17235
|
+
const optionList = (f, opts) => [`<option value="" selected={${cur(f)} === ''}>Any</option>`].concat(opts.map((o) => `<option value=${jsStr(o.value)} selected={${cur(f)} === ${jsStr(o.value)}}>${htmlEsc(o.label)}</option>`)).join("\n ");
|
|
17236
|
+
return `
|
|
17237
|
+
<form method="GET" class="sk-facets">
|
|
17238
|
+
<input type="hidden" name="sort" value={page.url.searchParams.get('sort') ?? ''} />
|
|
17239
|
+
${fields.map((f) => {
|
|
17240
|
+
const key = jsStr("f_" + f.field);
|
|
17241
|
+
const opts = enumOpts(f);
|
|
17242
|
+
let control;
|
|
17243
|
+
if (opts.length) {
|
|
17244
|
+
facetControls.select = true;
|
|
17245
|
+
control = `<select name=${key}>\n ${optionList(f, opts)}\n </select>`;
|
|
17246
|
+
} else if (f.type === "boolean") {
|
|
17247
|
+
facetControls.select = true;
|
|
17248
|
+
control = `<select name=${key}>\n ${optionList(f, [{
|
|
17249
|
+
value: "true",
|
|
17250
|
+
label: "Yes"
|
|
17251
|
+
}, {
|
|
17252
|
+
value: "false",
|
|
17253
|
+
label: "No"
|
|
17254
|
+
}])}\n </select>`;
|
|
17255
|
+
} else {
|
|
17256
|
+
facetControls.input = true;
|
|
17257
|
+
control = `<input name=${key} value={${cur(f)}} placeholder="Any" />`;
|
|
17258
|
+
}
|
|
17259
|
+
return ` <label class="sk-facet">\n <span>${htmlEsc(f.label ?? f.field)}</span>\n ${control}\n </label>`;
|
|
17260
|
+
}).join("\n")}
|
|
17261
|
+
<div class="sk-facet-actions">
|
|
17262
|
+
<button type="submit">Filter</button>
|
|
17263
|
+
<a href={page.url.pathname}>Clear</a>
|
|
17264
|
+
</div>
|
|
17265
|
+
</form>
|
|
17266
|
+
`;
|
|
17267
|
+
})();
|
|
17268
|
+
const facetCss = !facetForm ? "" : [
|
|
17269
|
+
" .sk-facets { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin-bottom: 14px; padding: 12px 14px; border: 1px solid var(--sg-border, #cbd5e1); border-radius: 10px; background: var(--sg-surface, #f8fafc); }",
|
|
17270
|
+
" .sk-facet { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }",
|
|
17271
|
+
" .sk-facet span { color: var(--sg-muted, #64748b); }",
|
|
17272
|
+
...facetControls.select && facetControls.input ? [" .sk-facet input, .sk-facet select { font: inherit; padding: 6px 9px; border-radius: 8px; border: 1px solid var(--sg-border, #cbd5e1); background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); }"] : facetControls.select ? [" .sk-facet select { font: inherit; padding: 6px 9px; border-radius: 8px; border: 1px solid var(--sg-border, #cbd5e1); background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); }"] : [" .sk-facet input { font: inherit; padding: 6px 9px; border-radius: 8px; border: 1px solid var(--sg-border, #cbd5e1); background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); }"],
|
|
17273
|
+
" .sk-facet-actions { display: flex; align-items: center; gap: 10px; margin-left: auto; }",
|
|
17274
|
+
" .sk-facet-actions button { font: inherit; padding: 7px 14px; border-radius: 8px; border: 1px solid var(--sg-accent, #4f46e5); background: var(--sg-accent, #4f46e5); color: #fff; cursor: pointer; }",
|
|
17275
|
+
" .sk-facet-actions a { font-size: 13px; color: var(--sg-muted, #64748b); }",
|
|
17276
|
+
""
|
|
17277
|
+
].join("\n");
|
|
16992
17278
|
const row = `(editing as Record<string, unknown>)`;
|
|
16993
17279
|
const fieldBlocks = formFields.map((f) => {
|
|
16994
17280
|
const key = jsStr(f.field);
|
|
@@ -16997,13 +17283,49 @@ ${writeGuard("delete")} const fd = await request.formData()
|
|
|
16997
17283
|
if (normType(f.type) === "boolean") input = `<input type="checkbox" name=${key} checked={!isCreate && !!${row}[${key}]} />`;
|
|
16998
17284
|
else if (f.options && f.options.length) input = `<select name=${key}${req}>${f.options.map((o) => `<option value=${jsStr(String(o.value))} selected={!isCreate && ${row}[${key}] === ${jsStr(String(o.value))}}>${htmlEsc(o.label ?? String(o.value))}</option>`).join("")}</select>`;
|
|
16999
17285
|
else if (isRel(f)) input = `<select name=${key}${req}>\n <option value="">-</option>\n {#each data.${f.field}Options as o (o.value)}<option value={o.value} selected={!isCreate && String(${row}[${key}] ?? '') === o.value}>{o.label}</option>{/each}\n </select>`;
|
|
17000
|
-
else
|
|
17001
|
-
|
|
17286
|
+
else if (f.input?.editorType === "textarea" || f.type === "json") input = `<textarea name=${key}${req}${attrs(f)}>{isCreate ? '' : (${row}[${key}] ?? '')}</textarea>`;
|
|
17287
|
+
else input = `<input type="${f.format === "email" ? "email" : f.format === "url" ? "url" : normType(f.type) === "number" ? "number" : f.type === "date" || f.type === "dateString" ? "date" : "text"}" name=${key} value={isCreate ? '' : (${row}[${key}] ?? '')}${req}${attrs(f)} />`;
|
|
17288
|
+
const hint = f.input?.help;
|
|
17289
|
+
return ` <label class="sk-field${f.input?.span === 2 ? " sk-field--wide" : ""}">
|
|
17002
17290
|
<span>${htmlEsc(f.label ?? f.field)}${f.required ? " *" : ""}</span>
|
|
17003
17291
|
${input}
|
|
17004
|
-
{#if form?.errors?.[${key}]}<em class="sk-err">{form.errors[${key}]}</em>{/if}
|
|
17292
|
+
${hint ? ` {#if !form?.errors?.[${key}]}<small class="sk-hint">${htmlEsc(hint)}</small>{/if}\n` : ""} {#if form?.errors?.[${key}]}<em class="sk-err">{form.errors[${key}]}</em>{/if}
|
|
17005
17293
|
</label>`;
|
|
17006
|
-
})
|
|
17294
|
+
});
|
|
17295
|
+
const blockFor = (name) => {
|
|
17296
|
+
const i = formFields.findIndex((f) => f.field === name);
|
|
17297
|
+
return i === -1 ? null : fieldBlocks[i];
|
|
17298
|
+
};
|
|
17299
|
+
const layout = {
|
|
17300
|
+
columns: grid.formColumns ?? schema.form?.columns,
|
|
17301
|
+
sections: grid.formSections?.length ? grid.formSections : schema.form?.sections
|
|
17302
|
+
};
|
|
17303
|
+
const ssrSections = layout?.sections?.length ? (() => {
|
|
17304
|
+
const assigned = /* @__PURE__ */ new Set();
|
|
17305
|
+
const groups = layout.sections.map((s) => {
|
|
17306
|
+
const items = s.fields.map((name) => {
|
|
17307
|
+
assigned.add(name);
|
|
17308
|
+
return blockFor(name);
|
|
17309
|
+
}).filter(Boolean);
|
|
17310
|
+
return {
|
|
17311
|
+
title: s.title,
|
|
17312
|
+
description: s.description,
|
|
17313
|
+
columns: s.columns,
|
|
17314
|
+
items
|
|
17315
|
+
};
|
|
17316
|
+
}).filter((g) => g.items.length);
|
|
17317
|
+
const rest = formFields.filter((f) => !assigned.has(f.field)).map((f) => blockFor(f.field)).filter(Boolean);
|
|
17318
|
+
return rest.length ? [...groups, {
|
|
17319
|
+
title: void 0,
|
|
17320
|
+
description: void 0,
|
|
17321
|
+
columns: void 0,
|
|
17322
|
+
items: rest
|
|
17323
|
+
}] : groups;
|
|
17324
|
+
})() : null;
|
|
17325
|
+
const formCols = layout?.columns ?? 1;
|
|
17326
|
+
const fieldsMarkup = ssrSections ? ssrSections.map((g) => ` <fieldset class="sk-group" style="--sk-cols: ${g.columns ?? formCols}">
|
|
17327
|
+
${g.title ? ` <legend>${htmlEsc(g.title)}</legend>\n` : ""}${g.description ? ` <p class="sk-group__desc">${htmlEsc(g.description)}</p>\n` : ""}${g.items.join("\n")}
|
|
17328
|
+
</fieldset>`).join("\n") : ` <div class="sk-group" style="--sk-cols: ${formCols}">\n${fieldBlocks.join("\n")}\n </div>`;
|
|
17007
17329
|
const page = `<script lang="ts">
|
|
17008
17330
|
import { SvGrid, renderSnippet, type ColumnDef, type CellContext } from '@svgrid/grid'
|
|
17009
17331
|
import { schemaToColumns } from '@svgrid/enterprise'
|
|
@@ -17072,7 +17394,7 @@ ${wantsFilter ? `
|
|
|
17072
17394
|
<h1>{TITLE}</h1>
|
|
17073
17395
|
<button type="button" class="sk-btn sk-btn--primary" onclick={() => (editing = 'create')}>{NEW_LABEL}</button>
|
|
17074
17396
|
</header>
|
|
17075
|
-
|
|
17397
|
+
${facetForm}
|
|
17076
17398
|
<SvGrid
|
|
17077
17399
|
data={data.rows}
|
|
17078
17400
|
{columns}
|
|
@@ -17091,7 +17413,7 @@ ${wantsFilter ? `
|
|
|
17091
17413
|
<form method="POST" action={isCreate ? '?/create' : '?/update'} class="sk-form" use:enhance={onSubmit}>
|
|
17092
17414
|
<h2>{isCreate ? NEW_LABEL : 'Edit ${htmlEsc(n.label)}'}</h2>
|
|
17093
17415
|
{#if !isCreate}<input type="hidden" name="__id" value={${row}[ID_FIELD] as string} />{/if}
|
|
17094
|
-
${
|
|
17416
|
+
${fieldsMarkup}
|
|
17095
17417
|
<div class="sk-form__actions">
|
|
17096
17418
|
<button type="button" class="sk-btn" onclick={() => (editing = null)}>Cancel</button>
|
|
17097
17419
|
<button type="submit" class="sk-btn sk-btn--primary">Save</button>
|
|
@@ -17105,12 +17427,19 @@ ${fieldBlocks}
|
|
|
17105
17427
|
.sk-head h1 { margin: 0; font-size: 20px; }
|
|
17106
17428
|
.sk-btn { font: inherit; padding: 7px 14px; border-radius: 8px; border: 1px solid var(--sg-border, #cbd5e1); background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); cursor: pointer; }
|
|
17107
17429
|
.sk-btn--primary { background: var(--sg-accent, #4f46e5); border-color: var(--sg-accent, #4f46e5); color: #fff; }
|
|
17108
|
-
.sk-rowact { display: flex; gap: 10px; }
|
|
17430
|
+
${facetCss} .sk-rowact { display: flex; gap: 10px; }
|
|
17109
17431
|
.sk-link { background: none; border: none; padding: 0; font: inherit; color: var(--sg-accent, #4f46e5); cursor: pointer; }
|
|
17110
17432
|
.sk-danger { color: #dc2626; }
|
|
17111
17433
|
.sk-overlay { position: fixed; inset: 0; background: rgba(15, 23, 42, 0.4); display: grid; place-items: center; z-index: 50; }
|
|
17112
17434
|
.sk-form { width: min(480px, 92vw); max-height: 90vh; overflow: auto; background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); border-radius: 12px; padding: 22px; display: flex; flex-direction: column; gap: 12px; box-shadow: 0 24px 60px -20px rgba(15, 23, 42, 0.5); }
|
|
17113
17435
|
.sk-form h2 { margin: 0 0 4px; font-size: 16px; }
|
|
17436
|
+
.sk-group { display: grid; grid-template-columns: repeat(var(--sk-cols, 1), minmax(0, 1fr)); gap: 12px; border: 0; padding: 0; margin: 0; min-width: 0; }
|
|
17437
|
+
.sk-group + .sk-group { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--sg-border, #e2e8f0); }
|
|
17438
|
+
.sk-group legend { grid-column: 1 / -1; padding: 0; font-size: 12px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; color: var(--sg-muted, #64748b); }
|
|
17439
|
+
.sk-group__desc { grid-column: 1 / -1; margin: 0; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
17440
|
+
.sk-field--wide { grid-column: 1 / -1; }
|
|
17441
|
+
.sk-hint { color: var(--sg-muted, #64748b); font-size: 11.5px; }
|
|
17442
|
+
@media (max-width: 560px) { .sk-group { grid-template-columns: 1fr; } }
|
|
17114
17443
|
.sk-field { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
|
|
17115
17444
|
.sk-field span { color: var(--sg-muted, #64748b); }
|
|
17116
17445
|
.sk-field input, .sk-field select { font: inherit; padding: 7px 9px; border-radius: 8px; border: 1px solid var(--sg-border, #cbd5e1); background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); }
|
|
@@ -17182,13 +17511,22 @@ function emitStudioProject(project) {
|
|
|
17182
17511
|
const schema = byName.get(screen.entity);
|
|
17183
17512
|
if (!schema) throw new Error(`emitStudioProject: screen "${screen.title}" references missing entity "${screen.entity}"`);
|
|
17184
17513
|
if (isSsrScreen(project, screen)) {
|
|
17185
|
-
const
|
|
17514
|
+
const kind = sources[screen.entity]?.kind;
|
|
17515
|
+
const srcKind = kind === "sql" ? "sql" : kind === "rest" ? "rest" : "memory";
|
|
17186
17516
|
if (ssrScreenShape(project, screen) === "grid") pages.push(...emitSsrGridScreen(schema, screen, srcKind, accessEnabled, screensByEntity.get(screen.entity) ?? [], byName));
|
|
17187
17517
|
else pages.push(ssrReadServerFile(schema, screen, srcKind, accessEnabled, screensByEntity.get(screen.entity) ?? [], resolve), screenPage(schema, rawByName.get(screen.entity) ?? schema, screen, resolve, (name) => rawByName.get(name), accessEnabled, i18nEnabled, routeById, drillEnabled, true));
|
|
17188
17518
|
continue;
|
|
17189
17519
|
}
|
|
17190
17520
|
pages.push(screenPage(schema, rawByName.get(screen.entity) ?? schema, screen, resolve, (name) => rawByName.get(name), accessEnabled, i18nEnabled, routeById, drillEnabled, false, sources[screen.entity]));
|
|
17191
17521
|
}
|
|
17522
|
+
if (project.screens.some((s) => isSsrScreen(project, s))) for (const screen of project.screens) {
|
|
17523
|
+
if (isSsrScreen(project, screen) || !screen.route) continue;
|
|
17524
|
+
pages.push({
|
|
17525
|
+
path: `src/routes/${screen.route}/+page.ts`,
|
|
17526
|
+
description: `${screen.title} renders in the browser (client data source).`,
|
|
17527
|
+
contents: `// This screen loads its rows in the browser, so it opts out of the root\n// layout's server rendering.\nexport const ssr = false\n`
|
|
17528
|
+
});
|
|
17529
|
+
}
|
|
17192
17530
|
const ssrHelpers = project.screens.some((s) => isSsrScreen(project, s)) ? [ssrQueryHelperFile()] : [];
|
|
17193
17531
|
const actionRouteFiles = [...actionsById.values()].map(({ action, screenId }) => actionRouteFile(action, screenId, accessEnabled));
|
|
17194
17532
|
const nav = [...project.screens].filter((s) => s.nav?.show !== false).sort((a, b) => (a.nav?.order ?? 0) - (b.nav?.order ?? 0)).map((s) => ({
|
|
@@ -19162,6 +19500,23 @@ main().then(() => process.exit(0)).catch((err) => { console.error(err); process.
|
|
|
19162
19500
|
];
|
|
19163
19501
|
}
|
|
19164
19502
|
var appSlug = (title) => (title || "studio-app").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "studio-app";
|
|
19503
|
+
/**
|
|
19504
|
+
* The root `+layout.ts`.
|
|
19505
|
+
*
|
|
19506
|
+
* An app with server-rendered screens leaves SvelteKit's own default in place
|
|
19507
|
+
* (SSR on) and lets each client-only screen opt out in its own `+page.ts` - so
|
|
19508
|
+
* the app reads the way a hand-written SvelteKit app does, instead of announcing
|
|
19509
|
+
* itself as a SPA in the first file anyone opens. With nothing to server-render,
|
|
19510
|
+
* the whole app stays a SPA as before.
|
|
19511
|
+
*/
|
|
19512
|
+
function rootLayoutModule(project) {
|
|
19513
|
+
const anySsr = project.screens.some((s) => isSsrScreen(project, s));
|
|
19514
|
+
return {
|
|
19515
|
+
path: "src/routes/+layout.ts",
|
|
19516
|
+
description: anySsr ? "Root layout options (SSR on; client-only screens opt out per page)." : "Client SPA (in-memory sources persist across navigation).",
|
|
19517
|
+
contents: anySsr ? `// Server rendering stays on (SvelteKit's default). Screens that have to run in\n// the browser - anything on an in-memory source, or a shape that can't render on\n// the server - opt out in their own +page.ts.\nexport const prerender = false\n` : `// In-memory sources are module singletons, so render as a client SPA. Move an\n// entity to SQL / Supabase and its /api route still runs server-side.\nexport const ssr = false\nexport const prerender = false\n`
|
|
19518
|
+
};
|
|
19519
|
+
}
|
|
19165
19520
|
/** The static SvelteKit + Vite scaffolding around the generated screens. */
|
|
19166
19521
|
var SCAFFOLD_STATIC = [
|
|
19167
19522
|
{
|
|
@@ -19184,11 +19539,6 @@ var SCAFFOLD_STATIC = [
|
|
|
19184
19539
|
description: "SvelteKit app types.",
|
|
19185
19540
|
contents: `declare global {\n namespace App {}\n}\n\nexport {}\n`
|
|
19186
19541
|
},
|
|
19187
|
-
{
|
|
19188
|
-
path: "src/routes/+layout.ts",
|
|
19189
|
-
description: "Client SPA (in-memory sources persist across navigation).",
|
|
19190
|
-
contents: `// In-memory sources are module singletons, so render as a client SPA. Move an\n// entity to SQL / Supabase and its /api route still runs server-side.\nexport const ssr = false\nexport const prerender = false\n`
|
|
19191
|
-
},
|
|
19192
19542
|
{
|
|
19193
19543
|
path: ".npmrc",
|
|
19194
19544
|
description: "npm config.",
|
|
@@ -19523,9 +19873,10 @@ function svelteConfig(plan) {
|
|
|
19523
19873
|
/** The runtime npm deps the generated source imports, keyed by package -> range.
|
|
19524
19874
|
* One source of truth for package.json AND the fragment-mode dependency report. */
|
|
19525
19875
|
function runtimeDeps(project, allSource) {
|
|
19876
|
+
const svgrid = `^${SVGRID_VERSION}`;
|
|
19526
19877
|
const dependencies = {
|
|
19527
|
-
"@svgrid/grid":
|
|
19528
|
-
"@svgrid/enterprise":
|
|
19878
|
+
"@svgrid/grid": svgrid,
|
|
19879
|
+
"@svgrid/enterprise": svgrid
|
|
19529
19880
|
};
|
|
19530
19881
|
if (allSource.includes("from '@supabase/supabase-js'")) dependencies["@supabase/supabase-js"] = "^2.45.0";
|
|
19531
19882
|
if (allSource.includes("import pg from 'pg'")) dependencies["pg"] = "^8.11.0";
|
|
@@ -19730,6 +20081,7 @@ function emitStudioAppBundle(project) {
|
|
|
19730
20081
|
},
|
|
19731
20082
|
...plan.files,
|
|
19732
20083
|
...cronScheduleFiles(project, plan),
|
|
20084
|
+
rootLayoutModule(project),
|
|
19733
20085
|
...SCAFFOLD_STATIC,
|
|
19734
20086
|
...envExample(allSource) ? [{
|
|
19735
20087
|
path: ".env.example",
|
|
@@ -20325,14 +20677,14 @@ function crudAppFromSchemas(schemas, opts = {}) {
|
|
|
20325
20677
|
seed
|
|
20326
20678
|
} : source;
|
|
20327
20679
|
}
|
|
20328
|
-
return sanitizeProject({
|
|
20680
|
+
return withSsrDefaults(sanitizeProject({
|
|
20329
20681
|
title: opts.title ?? "My Studio App",
|
|
20330
20682
|
entities: [...schemas],
|
|
20331
20683
|
screens,
|
|
20332
20684
|
dataSource: kind,
|
|
20333
20685
|
dataSources,
|
|
20334
20686
|
...opts.theme ? { theme: opts.theme } : {}
|
|
20335
|
-
});
|
|
20687
|
+
}));
|
|
20336
20688
|
}
|
|
20337
20689
|
//#endregion
|
|
20338
20690
|
//#region src/studio/samples/shared.ts
|
|
@@ -20379,6 +20731,28 @@ function pad(schema, rows, target, fkPools = {}) {
|
|
|
20379
20731
|
}
|
|
20380
20732
|
/** The `id` values of a seed array (for building foreign-key pools). */
|
|
20381
20733
|
var ids = (rows) => rows.map((r) => String(r.id));
|
|
20734
|
+
/**
|
|
20735
|
+
* `field = value` as a form condition (see `EntityField.when`). Helpers rather
|
|
20736
|
+
* than hand-written literals because an operator the filter engine does not know
|
|
20737
|
+
* silently matches everything, which would leave a conditional field permanently
|
|
20738
|
+
* on-screen.
|
|
20739
|
+
*/
|
|
20740
|
+
var whenIs = (field, value) => ({
|
|
20741
|
+
kind: "cmp",
|
|
20742
|
+
column: field,
|
|
20743
|
+
op: "equals",
|
|
20744
|
+
value
|
|
20745
|
+
});
|
|
20746
|
+
/** `field` is any one of `values`. */
|
|
20747
|
+
var whenIsAny = (field, values) => ({
|
|
20748
|
+
kind: "or",
|
|
20749
|
+
parts: values.map((value) => ({
|
|
20750
|
+
kind: "cmp",
|
|
20751
|
+
column: field,
|
|
20752
|
+
op: "equals",
|
|
20753
|
+
value
|
|
20754
|
+
}))
|
|
20755
|
+
});
|
|
20382
20756
|
/** A Kanban board screen: an optional filter panel + a board grouping rows by an
|
|
20383
20757
|
* enum field into draggable columns (the signature "pipeline" view). */
|
|
20384
20758
|
function boardScreen(entity, meta, opts) {
|
|
@@ -20859,6 +21233,38 @@ var deals$1 = {
|
|
|
20859
21233
|
field: "closeDate",
|
|
20860
21234
|
type: "date",
|
|
20861
21235
|
label: "Close date"
|
|
21236
|
+
},
|
|
21237
|
+
{
|
|
21238
|
+
field: "lostReason",
|
|
21239
|
+
type: "enum",
|
|
21240
|
+
label: "Lost to",
|
|
21241
|
+
hidden: { grid: true },
|
|
21242
|
+
options: [
|
|
21243
|
+
{
|
|
21244
|
+
value: "price",
|
|
21245
|
+
label: "Price"
|
|
21246
|
+
},
|
|
21247
|
+
{
|
|
21248
|
+
value: "competitor",
|
|
21249
|
+
label: "Competitor"
|
|
21250
|
+
},
|
|
21251
|
+
{
|
|
21252
|
+
value: "timing",
|
|
21253
|
+
label: "Timing"
|
|
21254
|
+
},
|
|
21255
|
+
{
|
|
21256
|
+
value: "no-budget",
|
|
21257
|
+
label: "No budget"
|
|
21258
|
+
},
|
|
21259
|
+
{
|
|
21260
|
+
value: "other",
|
|
21261
|
+
label: "Other"
|
|
21262
|
+
}
|
|
21263
|
+
],
|
|
21264
|
+
when: {
|
|
21265
|
+
visible: whenIs("stage", "lost"),
|
|
21266
|
+
required: whenIs("stage", "lost")
|
|
21267
|
+
}
|
|
20862
21268
|
}
|
|
20863
21269
|
]
|
|
20864
21270
|
};
|
|
@@ -21175,7 +21581,8 @@ var seed$17 = {
|
|
|
21175
21581
|
value: 15e3,
|
|
21176
21582
|
probability: 0,
|
|
21177
21583
|
owner: "Priya Patel",
|
|
21178
|
-
closeDate: "2026-05-10"
|
|
21584
|
+
closeDate: "2026-05-10",
|
|
21585
|
+
lostReason: "competitor"
|
|
21179
21586
|
},
|
|
21180
21587
|
{
|
|
21181
21588
|
id: "dl5",
|
|
@@ -23787,6 +24194,21 @@ var tickets = {
|
|
|
23787
24194
|
min: 0,
|
|
23788
24195
|
input: { help: "Time to the first agent reply" }
|
|
23789
24196
|
},
|
|
24197
|
+
{
|
|
24198
|
+
field: "resolution",
|
|
24199
|
+
type: "text",
|
|
24200
|
+
label: "Resolution",
|
|
24201
|
+
hidden: { grid: true },
|
|
24202
|
+
input: {
|
|
24203
|
+
editorType: "textarea",
|
|
24204
|
+
span: 2,
|
|
24205
|
+
help: "What fixed it - required to resolve or close"
|
|
24206
|
+
},
|
|
24207
|
+
when: {
|
|
24208
|
+
visible: whenIsAny("status", ["resolved", "closed"]),
|
|
24209
|
+
required: whenIsAny("status", ["resolved", "closed"])
|
|
24210
|
+
}
|
|
24211
|
+
},
|
|
23790
24212
|
{
|
|
23791
24213
|
field: "satisfaction",
|
|
23792
24214
|
type: "number",
|
|
@@ -23796,7 +24218,8 @@ var tickets = {
|
|
|
23796
24218
|
input: {
|
|
23797
24219
|
editorType: "rating",
|
|
23798
24220
|
help: "Customer rating on close"
|
|
23799
|
-
}
|
|
24221
|
+
},
|
|
24222
|
+
when: { visible: whenIsAny("status", ["resolved", "closed"]) }
|
|
23800
24223
|
},
|
|
23801
24224
|
{
|
|
23802
24225
|
field: "estimateHrs",
|
|
@@ -23991,7 +24414,8 @@ var seed$14 = {
|
|
|
23991
24414
|
firstResponseMins: 20,
|
|
23992
24415
|
satisfaction: 5,
|
|
23993
24416
|
estimateHrs: 1,
|
|
23994
|
-
hourlyRate: 60
|
|
24417
|
+
hourlyRate: 60,
|
|
24418
|
+
resolution: "Walked the customer through Export > CSV in the toolbar."
|
|
23995
24419
|
},
|
|
23996
24420
|
{
|
|
23997
24421
|
id: "tk5",
|
|
@@ -24006,7 +24430,8 @@ var seed$14 = {
|
|
|
24006
24430
|
firstResponseMins: 8,
|
|
24007
24431
|
satisfaction: 4,
|
|
24008
24432
|
estimateHrs: 1,
|
|
24009
|
-
hourlyRate: 75
|
|
24433
|
+
hourlyRate: 75,
|
|
24434
|
+
resolution: "Upgraded the account to Business and prorated the invoice."
|
|
24010
24435
|
},
|
|
24011
24436
|
{
|
|
24012
24437
|
id: "tk6",
|
|
@@ -24036,7 +24461,8 @@ var seed$14 = {
|
|
|
24036
24461
|
firstResponseMins: 15,
|
|
24037
24462
|
satisfaction: 4,
|
|
24038
24463
|
estimateHrs: 2,
|
|
24039
|
-
hourlyRate: 90
|
|
24464
|
+
hourlyRate: 90,
|
|
24465
|
+
resolution: "Refunded the duplicate charge; payment retry guard added."
|
|
24040
24466
|
},
|
|
24041
24467
|
{
|
|
24042
24468
|
id: "tk8",
|
|
@@ -35563,14 +35989,31 @@ var claims = {
|
|
|
35563
35989
|
type: "number",
|
|
35564
35990
|
label: "Amount ($)",
|
|
35565
35991
|
min: 0,
|
|
35566
|
-
required: true
|
|
35992
|
+
required: true,
|
|
35993
|
+
when: { disabled: whenIs("status", "paid") }
|
|
35567
35994
|
},
|
|
35568
35995
|
{
|
|
35569
35996
|
field: "deductible",
|
|
35570
35997
|
type: "number",
|
|
35571
35998
|
label: "Deductible ($)",
|
|
35572
35999
|
min: 0,
|
|
35573
|
-
defaultValue: 500
|
|
36000
|
+
defaultValue: 500,
|
|
36001
|
+
when: { disabled: whenIs("status", "paid") }
|
|
36002
|
+
},
|
|
36003
|
+
{
|
|
36004
|
+
field: "denialReason",
|
|
36005
|
+
type: "text",
|
|
36006
|
+
label: "Reason for denial",
|
|
36007
|
+
hidden: { grid: true },
|
|
36008
|
+
input: {
|
|
36009
|
+
editorType: "textarea",
|
|
36010
|
+
span: 2,
|
|
36011
|
+
help: "Required to deny a claim"
|
|
36012
|
+
},
|
|
36013
|
+
when: {
|
|
36014
|
+
visible: whenIs("status", "denied"),
|
|
36015
|
+
required: whenIs("status", "denied")
|
|
36016
|
+
}
|
|
35574
36017
|
},
|
|
35575
36018
|
{
|
|
35576
36019
|
field: "net",
|
|
@@ -35863,7 +36306,8 @@ var seed = {
|
|
|
35863
36306
|
fraudRisk: 5,
|
|
35864
36307
|
tags: ["suspicious", "late-report"],
|
|
35865
36308
|
incidentDate: "2026-01-20",
|
|
35866
|
-
filedAt: "2026-01-28T16:45:00.000Z"
|
|
36309
|
+
filedAt: "2026-01-28T16:45:00.000Z",
|
|
36310
|
+
denialReason: "Loss reported 8 days after the incident, outside the policy window."
|
|
35867
36311
|
},
|
|
35868
36312
|
{
|
|
35869
36313
|
id: "cl5",
|
|
@@ -39372,6 +39816,34 @@ async function introspectSupabaseTable(options) {
|
|
|
39372
39816
|
return buildEntitySchema(o.table, columns);
|
|
39373
39817
|
}
|
|
39374
39818
|
//#endregion
|
|
39819
|
+
//#region src/studio/user-error.ts
|
|
39820
|
+
/**
|
|
39821
|
+
* UserError - a mistake the person at the keyboard can fix (a typo in a
|
|
39822
|
+
* connection string, an empty table selection, a folder that already holds
|
|
39823
|
+
* another app), as opposed to a defect in Studio.
|
|
39824
|
+
*
|
|
39825
|
+
* The CLI prints these plainly and stops. Everything else is treated as a crash
|
|
39826
|
+
* and gets a sanitized bug report plus a prefilled issue link, so ordinary
|
|
39827
|
+
* fat-fingering never looks like something worth reporting.
|
|
39828
|
+
*
|
|
39829
|
+
* `isUserError` is duck-typed rather than an `instanceof` check: the class can
|
|
39830
|
+
* cross a bundle boundary (browser ESM source vs. the Node build), and a
|
|
39831
|
+
* mis-detected user error would send someone to file an issue about their own
|
|
39832
|
+
* typo.
|
|
39833
|
+
*/
|
|
39834
|
+
var UserError = class extends Error {
|
|
39835
|
+
constructor(message, hint) {
|
|
39836
|
+
super(message);
|
|
39837
|
+
this.isUserError = true;
|
|
39838
|
+
this.name = "UserError";
|
|
39839
|
+
if (hint !== void 0) this.hint = hint;
|
|
39840
|
+
}
|
|
39841
|
+
};
|
|
39842
|
+
/** True for anything thrown as a `UserError`, across module instances. */
|
|
39843
|
+
function isUserError(err) {
|
|
39844
|
+
return !!err && typeof err === "object" && err.isUserError === true;
|
|
39845
|
+
}
|
|
39846
|
+
//#endregion
|
|
39375
39847
|
//#region src/studio/init-flow.ts
|
|
39376
39848
|
var SOURCES = [
|
|
39377
39849
|
{
|
|
@@ -39407,6 +39879,28 @@ var DIALECTS = [
|
|
|
39407
39879
|
"mssql",
|
|
39408
39880
|
"sqlite"
|
|
39409
39881
|
];
|
|
39882
|
+
/** Shown above the connection prompt so the expected shape is never a guess. */
|
|
39883
|
+
var CONNECTION_EXAMPLES = {
|
|
39884
|
+
postgres: "postgres://user:password@localhost:5432/mydb",
|
|
39885
|
+
supabase: "postgres://postgres:password@db.abcdefgh.supabase.co:5432/postgres",
|
|
39886
|
+
mysql: "mysql://user:password@localhost:3306/mydb",
|
|
39887
|
+
mssql: "Server=localhost,1433;Database=mydb;User Id=sa;Password=secret;Encrypt=false",
|
|
39888
|
+
sqlite: "./data.db"
|
|
39889
|
+
};
|
|
39890
|
+
/**
|
|
39891
|
+
* Files that mean the target folder already holds an app. Writing the bundle
|
|
39892
|
+
* over one of these replaces it wholesale (they carry no managed markers), so
|
|
39893
|
+
* the flow stops and asks first. A `studio.config.json` is the exception: that
|
|
39894
|
+
* folder IS a Studio app, and re-running init there is a regenerate.
|
|
39895
|
+
*/
|
|
39896
|
+
var CLOBBER_MARKERS = [
|
|
39897
|
+
"package.json",
|
|
39898
|
+
"svelte.config.js",
|
|
39899
|
+
"svelte.config.ts",
|
|
39900
|
+
"vite.config.ts",
|
|
39901
|
+
"src/routes/+layout.svelte",
|
|
39902
|
+
".env"
|
|
39903
|
+
];
|
|
39410
39904
|
/** Ask a numbered-list question; returns the chosen index. */
|
|
39411
39905
|
async function pick(prompts, heading, items, def = 1) {
|
|
39412
39906
|
prompts.say("");
|
|
@@ -39473,7 +39967,7 @@ async function gatherData(flags, prompts, db, fetchText) {
|
|
|
39473
39967
|
hint: d.description
|
|
39474
39968
|
})))].id;
|
|
39475
39969
|
const dataset = getStarterDataset(datasetId);
|
|
39476
|
-
if (!dataset) throw new
|
|
39970
|
+
if (!dataset) throw new UserError(`Unknown dataset "${datasetId}".`, `Try one of: ${starterDatasets.map((d) => d.id).join(", ")}`);
|
|
39477
39971
|
const { entities, seed } = dataset.build();
|
|
39478
39972
|
const kind = source === "pglite" ? "pglite" : "memory";
|
|
39479
39973
|
const sources = {};
|
|
@@ -39491,27 +39985,37 @@ async function gatherData(flags, prompts, db, fetchText) {
|
|
|
39491
39985
|
};
|
|
39492
39986
|
}
|
|
39493
39987
|
async function gatherFromDatabase(flags, prompts, db) {
|
|
39494
|
-
if (!db) throw new
|
|
39988
|
+
if (!db) throw new UserError("Connecting to a database is not available here.");
|
|
39495
39989
|
let dialect = flags.db;
|
|
39496
39990
|
if (!dialect) dialect = DIALECTS[await pick(prompts, "Which database?", DIALECTS.map((d) => ({ label: d })))];
|
|
39497
39991
|
let url = flags.url ?? "";
|
|
39498
39992
|
if (!url) {
|
|
39499
39993
|
if (isFileDialect(dialect)) url = (await prompts.ask("Database file:", "./data.db")).trim();
|
|
39500
|
-
else
|
|
39994
|
+
else {
|
|
39995
|
+
prompts.say("");
|
|
39996
|
+
prompts.say(` Example: ${CONNECTION_EXAMPLES[dialect] ?? CONNECTION_EXAMPLES.postgres}`);
|
|
39997
|
+
url = (await prompts.ask("Connection string (blank to enter host/port instead):", "")).trim() || buildConnectionString(dialect, await askConnectionParts(prompts));
|
|
39998
|
+
}
|
|
39501
39999
|
}
|
|
39502
|
-
if (!url) throw new
|
|
40000
|
+
if (!url) throw new UserError("A connection string is required.");
|
|
39503
40001
|
const driver = await db.ensureDriver(dialect);
|
|
39504
|
-
if (!driver.ok) throw new
|
|
40002
|
+
if (!driver.ok) throw new UserError(driver.message ?? `Could not install the ${dialect} driver.`);
|
|
39505
40003
|
prompts.say(" Connecting...");
|
|
39506
|
-
|
|
39507
|
-
|
|
39508
|
-
|
|
40004
|
+
let execute;
|
|
40005
|
+
let tables;
|
|
40006
|
+
try {
|
|
40007
|
+
execute = await db.connect(dialect, url);
|
|
40008
|
+
tables = await listDatabaseTables(dialect, execute);
|
|
40009
|
+
} catch (e) {
|
|
40010
|
+
throw new UserError(`Could not read the database: ${e instanceof Error ? e.message : String(e)}`, `Check the connection string. Expected shape: ${CONNECTION_EXAMPLES[dialect] ?? CONNECTION_EXAMPLES.postgres}`);
|
|
40011
|
+
}
|
|
40012
|
+
if (tables.length === 0) throw new UserError("Connected, but the database has no tables.", "Check that the connection string points at the right database and schema.");
|
|
39509
40013
|
const counts = await countTableRows(dialect, execute, tables);
|
|
39510
40014
|
prompts.say("");
|
|
39511
40015
|
prompts.say(`Found ${tables.length} ${tables.length === 1 ? "table" : "tables"}:`);
|
|
39512
40016
|
counts.forEach((t, i) => prompts.say(` ${i + 1}. ${t.name}${t.rows == null ? "" : ` (${t.rows} rows)`}`));
|
|
39513
40017
|
const picked = parseSelection(flags.yes ? "all" : await prompts.ask("Which tables? (all, or a comma list of names/numbers)", "all"), tables);
|
|
39514
|
-
if (picked.length === 0) throw new
|
|
40018
|
+
if (picked.length === 0) throw new UserError("No tables picked - nothing to build.");
|
|
39515
40019
|
const schemas = [];
|
|
39516
40020
|
const sources = {};
|
|
39517
40021
|
for (const table of picked) try {
|
|
@@ -39529,7 +40033,7 @@ async function gatherFromDatabase(flags, prompts, db) {
|
|
|
39529
40033
|
} catch (e) {
|
|
39530
40034
|
prompts.say(` ! Skipped ${table}: ${e instanceof Error ? e.message : String(e)}`);
|
|
39531
40035
|
}
|
|
39532
|
-
if (schemas.length === 0) throw new
|
|
40036
|
+
if (schemas.length === 0) throw new UserError("None of the picked tables could be read.");
|
|
39533
40037
|
prompts.say(` Read ${schemas.length} ${schemas.length === 1 ? "table" : "tables"}.`);
|
|
39534
40038
|
return {
|
|
39535
40039
|
schemas,
|
|
@@ -39543,11 +40047,21 @@ async function gatherFromDatabase(flags, prompts, db) {
|
|
|
39543
40047
|
* DbGateway - just the project URL and the anon key.
|
|
39544
40048
|
*/
|
|
39545
40049
|
async function gatherFromSupabase(flags, prompts) {
|
|
40050
|
+
if (!flags.supabaseUrl || !flags.supabaseKey) {
|
|
40051
|
+
prompts.say("");
|
|
40052
|
+
prompts.say(" Both are in your Supabase project's API settings (Project Settings -> API).");
|
|
40053
|
+
prompts.say(" The URL looks like https://abcdefgh.supabase.co");
|
|
40054
|
+
}
|
|
39546
40055
|
const url = (flags.supabaseUrl ?? await prompts.ask("Project URL:", "")).trim();
|
|
39547
40056
|
const key = (flags.supabaseKey ?? await prompts.ask("Anon key:", "")).trim();
|
|
39548
|
-
if (!url || !key) throw new
|
|
40057
|
+
if (!url || !key) throw new UserError("Both the project URL and the anon key are required.");
|
|
39549
40058
|
prompts.say(" Reading the API...");
|
|
39550
|
-
|
|
40059
|
+
let tables;
|
|
40060
|
+
try {
|
|
40061
|
+
tables = await listSupabaseTables(url, key);
|
|
40062
|
+
} catch (e) {
|
|
40063
|
+
throw new UserError(`Could not reach ${url}: ${e instanceof Error ? e.message : String(e)}`, "Check the project URL and anon key in Project Settings -> API.");
|
|
40064
|
+
}
|
|
39551
40065
|
let picked;
|
|
39552
40066
|
if (tables.length) {
|
|
39553
40067
|
prompts.say("");
|
|
@@ -39558,7 +40072,7 @@ async function gatherFromSupabase(flags, prompts) {
|
|
|
39558
40072
|
prompts.say(" Couldn't list the tables (the API doc may be restricted for this key).");
|
|
39559
40073
|
picked = (await prompts.ask("Table names (comma separated):", "")).split(",").map((t) => t.trim()).filter(Boolean);
|
|
39560
40074
|
}
|
|
39561
|
-
if (picked.length === 0) throw new
|
|
40075
|
+
if (picked.length === 0) throw new UserError("No tables picked - nothing to build.");
|
|
39562
40076
|
const schemas = [];
|
|
39563
40077
|
const sources = {};
|
|
39564
40078
|
for (const table of picked) {
|
|
@@ -39579,7 +40093,7 @@ async function gatherFromSupabase(flags, prompts) {
|
|
|
39579
40093
|
key
|
|
39580
40094
|
};
|
|
39581
40095
|
}
|
|
39582
|
-
if (schemas.length === 0) throw new
|
|
40096
|
+
if (schemas.length === 0) throw new UserError("None of the picked tables could be read.");
|
|
39583
40097
|
prompts.say(` Read ${schemas.length} ${schemas.length === 1 ? "table" : "tables"}.`);
|
|
39584
40098
|
return {
|
|
39585
40099
|
schemas,
|
|
@@ -39598,14 +40112,14 @@ async function askConnectionParts(prompts) {
|
|
|
39598
40112
|
};
|
|
39599
40113
|
}
|
|
39600
40114
|
async function gatherFromRest(prompts, fetchText) {
|
|
39601
|
-
if (!fetchText) throw new
|
|
40115
|
+
if (!fetchText) throw new UserError("Reading a REST endpoint is not available here.");
|
|
39602
40116
|
const src = (await prompts.ask("OpenAPI spec (file or URL), or a REST endpoint returning rows:", "")).trim();
|
|
39603
|
-
if (!src) throw new
|
|
40117
|
+
if (!src) throw new UserError("A spec path/URL or endpoint is required.");
|
|
39604
40118
|
const text = await fetchText(src);
|
|
39605
40119
|
if (/"(openapi|swagger|paths)"\s*:/.test(text)) {
|
|
39606
40120
|
const { entities, sources, warnings } = introspectOpenApi(text);
|
|
39607
40121
|
for (const w of warnings) prompts.say(` ! ${w}`);
|
|
39608
|
-
if (entities.length === 0) throw new
|
|
40122
|
+
if (entities.length === 0) throw new UserError("The spec described no collection resources.");
|
|
39609
40123
|
prompts.say(` Read ${entities.length} REST ${entities.length === 1 ? "resource" : "resources"}.`);
|
|
39610
40124
|
return {
|
|
39611
40125
|
schemas: entities,
|
|
@@ -39614,9 +40128,19 @@ async function gatherFromRest(prompts, fetchText) {
|
|
|
39614
40128
|
kind: "rest"
|
|
39615
40129
|
};
|
|
39616
40130
|
}
|
|
39617
|
-
|
|
39618
|
-
|
|
39619
|
-
|
|
40131
|
+
let rows;
|
|
40132
|
+
try {
|
|
40133
|
+
rows = unwrapRows(JSON.parse(text));
|
|
40134
|
+
} catch {
|
|
40135
|
+
throw new UserError(`${src} did not return JSON.`, "Point this at an endpoint that returns rows, or at an OpenAPI (JSON) document.");
|
|
40136
|
+
}
|
|
40137
|
+
if (rows.length === 0) throw new UserError("The endpoint returned no rows to read a shape from.");
|
|
40138
|
+
let url;
|
|
40139
|
+
try {
|
|
40140
|
+
url = new URL(src);
|
|
40141
|
+
} catch {
|
|
40142
|
+
throw new UserError(`"${src}" is not a full URL, so there is no endpoint to call at runtime.`, "Pass the absolute endpoint, for example https://api.example.com/widgets");
|
|
40143
|
+
}
|
|
39620
40144
|
const path = url.pathname.replace(/^\/+|\/+$/g, "");
|
|
39621
40145
|
const suggested = path.split("/").filter(Boolean).pop() || "items";
|
|
39622
40146
|
const name = ((await prompts.ask("Table name:", suggested)).trim() || suggested).replace(/\W+/g, "_");
|
|
@@ -39690,12 +40214,45 @@ async function askTheme(flags, prompts) {
|
|
|
39690
40214
|
mode: dark ? "dark" : "light"
|
|
39691
40215
|
};
|
|
39692
40216
|
}
|
|
40217
|
+
/** Prefix a path with the output folder (`.` writes into the current one). */
|
|
40218
|
+
var inDir = (outDir, path) => (!outDir || outDir === "." ? "" : outDir.replace(/[\\/]+$/, "") + "/") + path;
|
|
40219
|
+
/** Which marker files are present in `outDir` right now. */
|
|
40220
|
+
async function markersIn(outDir, io) {
|
|
40221
|
+
const found = /* @__PURE__ */ new Set();
|
|
40222
|
+
for (const marker of CLOBBER_MARKERS) if (await io.readFile(inDir(outDir, marker)) != null) found.add(marker);
|
|
40223
|
+
return found;
|
|
40224
|
+
}
|
|
40225
|
+
/**
|
|
40226
|
+
* Stop before overwriting somebody else's app.
|
|
40227
|
+
*
|
|
40228
|
+
* `init` defaults to the current folder, and bare `svgrid-studio` in a terminal
|
|
40229
|
+
* runs it, so a mistaken run inside an existing project would replace its
|
|
40230
|
+
* package.json, svelte config and .env without a word. Re-running init on a
|
|
40231
|
+
* folder Studio itself generated is a regenerate, not a clobber, so a
|
|
40232
|
+
* `studio.config.json` waves the check through.
|
|
40233
|
+
*
|
|
40234
|
+
* `before` is the set of markers seen at startup, and only those count. Picking a
|
|
40235
|
+
* database makes Studio install a driver into this folder first, which writes a
|
|
40236
|
+
* package.json - without the snapshot the flow would trip over a file it had
|
|
40237
|
+
* just created itself and refuse to build anything.
|
|
40238
|
+
*/
|
|
40239
|
+
async function confirmTargetFolder(outDir, flags, prompts, io, before) {
|
|
40240
|
+
if (flags.force) return;
|
|
40241
|
+
if (await io.readFile(inDir(outDir, "studio.config.json")) != null) return;
|
|
40242
|
+
const present = await markersIn(outDir, io);
|
|
40243
|
+
const found = CLOBBER_MARKERS.filter((m) => present.has(m) && (before == null || before.has(m)));
|
|
40244
|
+
if (found.length === 0) return;
|
|
40245
|
+
const message = `${!outDir || outDir === "." ? "This folder" : outDir} already has ${found.join(", ")}, and Studio would overwrite ${found.length === 1 ? "it" : "them"}.`;
|
|
40246
|
+
if (flags.yes) throw new UserError(message, "Pick an empty folder with --out <dir>, or pass --force to overwrite anyway.");
|
|
40247
|
+
prompts.say("");
|
|
40248
|
+
prompts.say(` ! ${message}`);
|
|
40249
|
+
if (!isYes(await prompts.ask("Overwrite? (y/N)", "n"), false)) throw new UserError("Stopped without writing anything.", "Re-run with --out <dir> to build somewhere else.");
|
|
40250
|
+
}
|
|
39693
40251
|
/** Merge-write the generated files, preserving edits outside managed regions. */
|
|
39694
40252
|
async function writeAll$1(files, outDir, io) {
|
|
39695
40253
|
const written = [];
|
|
39696
|
-
const prefix = !outDir || outDir === "." ? "" : outDir.replace(/[\\/]+$/, "") + "/";
|
|
39697
40254
|
for (const file of files) {
|
|
39698
|
-
const path =
|
|
40255
|
+
const path = inDir(outDir, file.path);
|
|
39699
40256
|
const existing = await io.readFile(path);
|
|
39700
40257
|
if (skipUserOwned(file, existing != null)) continue;
|
|
39701
40258
|
await io.writeFile(path, mergeManaged(existing, file.contents));
|
|
@@ -39709,12 +40266,15 @@ async function writeAll$1(files, outDir, io) {
|
|
|
39709
40266
|
*/
|
|
39710
40267
|
async function runStudioInit(flags, prompts, db, io, fetchText = null) {
|
|
39711
40268
|
prompts.say("SvGrid Studio - let's build your app.");
|
|
40269
|
+
const candidateDir = flags.out ?? ".";
|
|
40270
|
+
const markersBefore = await markersIn(candidateDir, io);
|
|
39712
40271
|
const data = await gatherData(flags, prompts, db, fetchText);
|
|
39713
40272
|
const perEntity = await askScreens(data.schemas, flags, prompts);
|
|
39714
40273
|
const theme = await askTheme(flags, prompts);
|
|
39715
40274
|
const defaultTitle = flags.title ?? data.title ?? "My app";
|
|
39716
40275
|
const title = flags.yes || flags.title ? defaultTitle : (await prompts.ask("\nApp name:", defaultTitle)).trim() || defaultTitle;
|
|
39717
40276
|
const outDir = flags.out ?? (flags.yes ? "." : (await prompts.ask("Write it where?", ".")).trim() || ".");
|
|
40277
|
+
await confirmTargetFolder(outDir, flags, prompts, io, outDir === candidateDir ? markersBefore : null);
|
|
39718
40278
|
const project = crudAppFromSchemas(data.schemas, {
|
|
39719
40279
|
title,
|
|
39720
40280
|
dataSource: data.kind,
|
|
@@ -39725,7 +40285,7 @@ async function runStudioInit(flags, prompts, db, io, fetchText = null) {
|
|
|
39725
40285
|
...theme ? { theme } : {}
|
|
39726
40286
|
});
|
|
39727
40287
|
const written = await writeAll$1(emitStudioAppBundle(project), outDir, io);
|
|
39728
|
-
const configPath = (
|
|
40288
|
+
const configPath = inDir(outDir, "studio.config.json");
|
|
39729
40289
|
await io.writeFile(configPath, serializeProject(project));
|
|
39730
40290
|
written.push(configPath);
|
|
39731
40291
|
prompts.say("");
|
|
@@ -40145,8 +40705,49 @@ async function runStudioAddApp(schemas, options, io) {
|
|
|
40145
40705
|
//#endregion
|
|
40146
40706
|
//#region src/revoked.ts
|
|
40147
40707
|
var REVOKED_KEYS = /* @__PURE__ */ new Set([]);
|
|
40148
|
-
/**
|
|
40149
|
-
|
|
40708
|
+
/**
|
|
40709
|
+
* Pull the expiry out of a key.
|
|
40710
|
+
*
|
|
40711
|
+
* Keys are `SVENTERPRISE[-KIND]-<SLUG>-<SEATS>-<DATE>-<RANDOM>`, so the date is
|
|
40712
|
+
* always the second-to-last segment regardless of whether a KIND is present.
|
|
40713
|
+
* Two widths are accepted:
|
|
40714
|
+
*
|
|
40715
|
+
* YYYYMM - subscription granularity; expires at the end of that month.
|
|
40716
|
+
* YYYYMMDD - trial granularity; expires at the end of that day.
|
|
40717
|
+
*
|
|
40718
|
+
* The 6-digit form came first, so every key issued before day-precision trials
|
|
40719
|
+
* existed still parses. Anything else (including the older keys whose slug sits
|
|
40720
|
+
* in that position) yields null, which reads as "no expiry encoded".
|
|
40721
|
+
*
|
|
40722
|
+
* Boundaries are UTC so a key does not lapse at a different instant per
|
|
40723
|
+
* timezone. Kept in sync with `tools/issue-license.mjs`.
|
|
40724
|
+
*/
|
|
40725
|
+
function parseLicenseExpiry(key) {
|
|
40726
|
+
if (!key) return null;
|
|
40727
|
+
const segments = key.split("-");
|
|
40728
|
+
if (segments.length < 3) return null;
|
|
40729
|
+
const stamp = segments[segments.length - 2];
|
|
40730
|
+
if (stamp == null) return null;
|
|
40731
|
+
if (/^\d{6}$/.test(stamp)) {
|
|
40732
|
+
const year = Number(stamp.slice(0, 4));
|
|
40733
|
+
const month = Number(stamp.slice(4, 6));
|
|
40734
|
+
if (month < 1 || month > 12) return null;
|
|
40735
|
+
return /* @__PURE__ */ new Date(Date.UTC(year, month, 1) - 1);
|
|
40736
|
+
}
|
|
40737
|
+
if (/^\d{8}$/.test(stamp)) {
|
|
40738
|
+
const year = Number(stamp.slice(0, 4));
|
|
40739
|
+
const month = Number(stamp.slice(4, 6));
|
|
40740
|
+
const day = Number(stamp.slice(6, 8));
|
|
40741
|
+
if (month < 1 || month > 12 || day < 1 || day > 31) return null;
|
|
40742
|
+
const end = new Date(Date.UTC(year, month - 1, day, 23, 59, 59, 999));
|
|
40743
|
+
if (end.getUTCMonth() !== month - 1) return null;
|
|
40744
|
+
return end;
|
|
40745
|
+
}
|
|
40746
|
+
return null;
|
|
40747
|
+
}
|
|
40748
|
+
/** Classify a license key string. Pure and side-effect-free.
|
|
40749
|
+
* `now` is injectable so expiry behaviour is testable without faking clocks. */
|
|
40750
|
+
function checkLicenseKey(key, now = /* @__PURE__ */ new Date()) {
|
|
40150
40751
|
if (key == null || key === "") return {
|
|
40151
40752
|
status: "unset",
|
|
40152
40753
|
valid: false
|
|
@@ -40159,18 +40760,14 @@ function checkLicenseKey(key) {
|
|
|
40159
40760
|
status: "revoked",
|
|
40160
40761
|
valid: false
|
|
40161
40762
|
};
|
|
40162
|
-
|
|
40163
|
-
|
|
40164
|
-
valid: true
|
|
40165
|
-
};
|
|
40166
|
-
if (key.startsWith("SVENTERPRISE-EVAL")) return {
|
|
40167
|
-
status: "eval",
|
|
40168
|
-
valid: true
|
|
40169
|
-
};
|
|
40763
|
+
const expiresAt = parseLicenseExpiry(key) ?? void 0;
|
|
40764
|
+
const expired = expiresAt ? now.getTime() > expiresAt.getTime() : void 0;
|
|
40170
40765
|
return {
|
|
40171
|
-
status: "licensed",
|
|
40172
|
-
valid: true
|
|
40766
|
+
status: key.startsWith("SVENTERPRISE-DEV") ? "dev" : key.startsWith("SVENTERPRISE-EVAL") ? "eval" : "licensed",
|
|
40767
|
+
valid: true,
|
|
40768
|
+
expiresAt,
|
|
40769
|
+
expired
|
|
40173
40770
|
};
|
|
40174
40771
|
}
|
|
40175
40772
|
//#endregion
|
|
40176
|
-
export { CANVAS_COLS, CANVAS_GAP_PX, CANVAS_ROW_PX, CONTAINER_CHILD_KINDS, CONTAINER_DISPLAY_KINDS, CRUD_ACTIONS, DEFAULT_PORT, DRIVER_PACKAGE, FORM_SUBMIT, GRID_API_MEMBERS, GRID_CURATED_PROPS, GRID_EVENTS, GRID_PRESETS, MANAGED_END, MANAGED_START, ON_LOAD, PANE_LAYOUTS, STANDARD_UI_EVENTS, TAB_CHILD_KINDS, TRIGGER_EVENTS, UI_COMPONENT_REGISTRY, addAccordionBlock, addAccordionComponent, addAccordionSection, addBlock, addBlockAt, addComponentBlock, addCrudSuite, addEntity, addFreestandingScreen, addHandlerStep, addScreen, addScreenAction, addScreenFromTemplate, addStateVar, addTab, addTabBlock, addTabComponent, applyGridPreset, blockClassName, blockColumns, blockPalette, blockStyleCss, buildCanvasLayout, buildConnectionString, buildCopilotMessages, buildDockLayout, buildStudioBugReport, canvasOpts, canvasRectOf, changeSlot, checkLicenseKey, clickSlot, compileCondition, compileHandlerSteps, compileStep, compileTriggerStep, compileTriggerSteps, compileValue, componentHandleMembers, componentHandleName, componentHasBindings, countTableRows, createProject, crudAppFromSchemas, crudSuiteScreens, csvToEntity, ctxAmbientDts, ctxCompletions, dashScreen, defaultBlockConfig, defaultEntitySource, defaultScreenFor, defaultStudioTheme, deployCommands, detailScreen, detectDelimiter, disableScreenCode, dockOpts, dockPaneIds, dockPaneTitleOf, duplicateBlock, duplicateScreen, emitEntityModules, emitStudioApp, emitStudioAppBundle, emitStudioFragment, emitStudioProject, enableScreenCode, entityDataSource, entityOf, entityScreenPage, eventSlot, flattenBlocks, formScreen, generateRows, generateValue, getLiveDataSample, getSampleApp, getStarterDataset, getStudioTheme, gridApiSettableProps, gridColumns, gridConfig, gridOpts, gridPresetLabel, gridPropSurface, inferType, insertBlock, introspectDatabase, introspectDrizzle, introspectDrizzleAll, introspectJson, introspectOpenApi, introspectPrisma, introspectPrismaAll, isDarkTheme, isFileDialect, isPaneLayout, isProjectValid, isTenantScoped, linkRelationLabels, listDatabaseTables, listScreen, liveDataSamples, mapSqlType, mergeBlockStyle, mergeManaged, missingEnvKeys, moveBlock, moveHandlerStep, parseConnectionString, parseCsv, parseProject, parseSelection, pickLabelField, prepareEntities, probeConnection, projectFromModelText, reconcileDock, redactConnectionString, refineField, refineFields, removeAccordionBlock, removeAccordionSection, removeBlock, removeEntity, removeScreen, removeScreenAction, removeStateVar, removeTab, removeTabBlock, renameAccordionSection, renameTab, reorderBlock, reorderScreen, reseedScreenDock, resolveDeployTarget, resolveIdField, resolveSchema, resolveSchemas, resolveThemeTokens, resolveThemeTokensFor, roleCanAction, roleCanScreen, rowSelectSlot, runStudioAdd, runStudioAddApp, runStudioInit, runtimeDeps, sampleApps, sanitizeClassName, sanitizeProject, sanitizeStudioProject, scaffold, scaffoldApp, schemaToColumns, schemaToFormFields, screenDataset, screenFromTemplate, screenHandles, screenLayoutOf, seedUsers, serializeProject, setAccordionMultiple, setAuth, setCanvasRect, setComponentBinding, setComponentName, setDataLayer, setDataSource, setDeployTarget, setDockPaneTitle, setEntityDataSource, setHandlerBody, setHandlerSteps, setJob, setLayoutOpts, setScreenDock, setScreenHandlersSource, setScreenLayout, setScreenRenderGrid, setScreenRenderMode, setShell, setTenancy, setTheme, setThemePreset, setTrigger, skipUserOwned, splitOpts, ssrEligible, ssrScreenShape, stackOpts, starterDatasets, starterProject, stateInitExpr, stateTsType, statusPills, stepsToCode, studioDeployInfo, studioThemes, summarizeVerify, syncDockPanes, tenantField, themeStyleString, triggersOf, uiComponentSpec, updateBlock, updateEntity, updateHandlerStep, updateScreen, updateStateVar, validateProject, verifyScaffold };
|
|
40773
|
+
export { CANVAS_COLS, CANVAS_GAP_PX, CANVAS_ROW_PX, CONTAINER_CHILD_KINDS, CONTAINER_DISPLAY_KINDS, CRUD_ACTIONS, DEFAULT_PORT, DRIVER_PACKAGE, FORM_SUBMIT, GRID_API_MEMBERS, GRID_CURATED_PROPS, GRID_EVENTS, GRID_PRESETS, MANAGED_END, MANAGED_START, ON_LOAD, PANE_LAYOUTS, STANDARD_UI_EVENTS, TAB_CHILD_KINDS, TRIGGER_EVENTS, UI_COMPONENT_REGISTRY, UserError, addAccordionBlock, addAccordionComponent, addAccordionSection, addBlock, addBlockAt, addComponentBlock, addCrudSuite, addEntity, addFreestandingScreen, addHandlerStep, addScreen, addScreenAction, addScreenFromTemplate, addStateVar, addTab, addTabBlock, addTabComponent, applyGridPreset, blockClassName, blockColumns, blockPalette, blockStyleCss, buildCanvasLayout, buildConnectionString, buildCopilotMessages, buildDockLayout, buildStudioBugReport, canvasOpts, canvasRectOf, changeSlot, checkLicenseKey, clickSlot, compileCondition, compileHandlerSteps, compileStep, compileTriggerStep, compileTriggerSteps, compileValue, componentHandleMembers, componentHandleName, componentHasBindings, countTableRows, createProject, crudAppFromSchemas, crudSuiteScreens, csvToEntity, ctxAmbientDts, ctxCompletions, dashScreen, defaultBlockConfig, defaultEntitySource, defaultScreenFor, defaultStudioTheme, deployCommands, detailScreen, detectDelimiter, disableScreenCode, dockOpts, dockPaneIds, dockPaneTitleOf, duplicateBlock, duplicateScreen, emitEntityModules, emitStudioApp, emitStudioAppBundle, emitStudioFragment, emitStudioProject, enableScreenCode, entityDataSource, entityOf, entityScreenPage, eventSlot, flattenBlocks, formScreen, generateRows, generateValue, getLiveDataSample, getSampleApp, getStarterDataset, getStudioTheme, gridApiSettableProps, gridColumns, gridConfig, gridOpts, gridPresetLabel, gridPropSurface, inferType, insertBlock, introspectDatabase, introspectDrizzle, introspectDrizzleAll, introspectJson, introspectOpenApi, introspectPrisma, introspectPrismaAll, isDarkTheme, isFileDialect, isPaneLayout, isProjectValid, isTenantScoped, isUserError, linkRelationLabels, listDatabaseTables, listScreen, liveDataSamples, mapSqlType, mergeBlockStyle, mergeManaged, missingEnvKeys, moveBlock, moveHandlerStep, parseConnectionString, parseCsv, parseProject, parseSelection, pickLabelField, prepareEntities, probeConnection, projectFromModelText, reconcileDock, redactConnectionString, refineField, refineFields, removeAccordionBlock, removeAccordionSection, removeBlock, removeEntity, removeScreen, removeScreenAction, removeStateVar, removeTab, removeTabBlock, renameAccordionSection, renameTab, reorderBlock, reorderScreen, reseedScreenDock, resolveDeployTarget, resolveIdField, resolveSchema, resolveSchemas, resolveThemeTokens, resolveThemeTokensFor, roleCanAction, roleCanScreen, rowSelectSlot, runStudioAdd, runStudioAddApp, runStudioInit, runtimeDeps, sampleApps, sanitizeClassName, sanitizeProject, sanitizeStudioProject, scaffold, scaffoldApp, schemaToColumns, schemaToFormFields, screenDataset, screenFromTemplate, screenHandles, screenLayoutOf, seedUsers, serializeProject, setAccordionMultiple, setAuth, setCanvasRect, setComponentBinding, setComponentName, setDataLayer, setDataSource, setDeployTarget, setDockPaneTitle, setEntityDataSource, setHandlerBody, setHandlerSteps, setJob, setLayoutOpts, setScreenDock, setScreenHandlersSource, setScreenLayout, setScreenRenderGrid, setScreenRenderMode, setShell, setTenancy, setTheme, setThemePreset, setTrigger, skipUserOwned, splitOpts, ssrEligible, ssrScreenShape, stackOpts, starterDatasets, starterProject, stateInitExpr, stateTsType, statusPills, stepsToCode, studioDeployInfo, studioThemes, summarizeVerify, syncDockPanes, tenantField, themeStyleString, triggersOf, uiComponentSpec, updateBlock, updateEntity, updateHandlerStep, updateScreen, updateStateVar, validateProject, verifyScaffold, withSsrDefaults };
|