@svgrid/enterprise 2.5.2 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SvAlertRuleEditor.svelte.d.ts +13 -0
- package/dist/SvAlertsManager.svelte.d.ts +14 -0
- package/dist/SvAlertsPanel.svelte.d.ts +9 -0
- package/dist/SvAuthGate.svelte.d.ts +22 -0
- package/dist/SvBoard.svelte.d.ts +35 -0
- package/dist/SvExportMenu.svelte.d.ts +61 -0
- package/dist/SvExpressionEditor.svelte.d.ts +12 -0
- package/dist/SvFileInput.svelte.d.ts +20 -0
- package/dist/SvGridAlerts.svelte.d.ts +77 -0
- package/dist/SvGridBoard.svelte.d.ts +30 -0
- package/dist/SvGridEditPanel.svelte.d.ts +73 -0
- package/dist/SvGridMasterDetail.svelte.d.ts +49 -0
- package/dist/SvGridScheduler.svelte.d.ts +30 -0
- package/dist/SvImportDialog.svelte.d.ts +88 -0
- package/dist/SvLookupInput.svelte.d.ts +21 -0
- package/dist/SvPivotDesigner.svelte.d.ts +176 -0
- package/dist/SvRecordDetail.svelte.d.ts +58 -0
- package/dist/SvSchedule.svelte.d.ts +29 -0
- package/dist/SvSchemaChart.svelte.d.ts +61 -0
- package/dist/SvSchemaDashboard.svelte.d.ts +46 -0
- package/dist/alerts/alert-engine-attach.d.ts +79 -0
- package/dist/alerts/alert-engine-attach.js +106 -0
- package/dist/alerts/alert-engine.d.ts +42 -0
- package/dist/alerts/alert-engine.js +196 -0
- package/dist/alerts/alert-formats.d.ts +26 -0
- package/dist/alerts/alert-formats.js +58 -0
- package/dist/alerts/alert-observer.d.ts +54 -0
- package/dist/alerts/alert-observer.js +156 -0
- package/dist/alerts/alert-scheduler.d.ts +30 -0
- package/dist/alerts/alert-scheduler.js +54 -0
- package/dist/alerts/alert-storage.d.ts +31 -0
- package/dist/alerts/alert-storage.js +92 -0
- package/dist/alerts/alert-store.svelte.d.ts +24 -0
- package/dist/alerts/alert-store.svelte.js +69 -0
- package/dist/alerts/alert-types.d.ts +86 -0
- package/dist/alerts/alert-types.js +1 -0
- package/dist/alerts.d.ts +2 -0
- package/{src/alerts.ts → dist/alerts.js} +9 -9
- package/dist/board.d.ts +7 -0
- package/{src/board.ts → dist/board.js} +13 -14
- package/dist/cdn/svgrid-enterprise.svelte-external.js +2987 -2981
- package/dist/designer/assets/{index-CM5xvaOy.js → index-BF0UH638.js} +3 -3
- package/dist/designer/assets/{src-D-ET9Mmo.js → src-BPJruwB9.js} +184 -184
- package/dist/designer/index.html +2 -2
- package/dist/edit-panel.d.ts +105 -0
- package/dist/edit-panel.js +350 -0
- package/dist/export-conditional.d.ts +31 -0
- package/dist/export-conditional.js +65 -0
- package/dist/export-ooxml.d.ts +93 -0
- package/dist/export-ooxml.js +385 -0
- package/dist/export-pdf.d.ts +107 -0
- package/dist/export-pdf.js +144 -0
- package/dist/export-print.d.ts +55 -0
- package/dist/export-print.js +91 -0
- package/{src/export-serialize.ts → dist/export-serialize.d.ts} +11 -24
- package/dist/export-serialize.js +11 -0
- package/dist/export-xls.d.ts +26 -0
- package/dist/export-xls.js +157 -0
- package/dist/export.d.ts +323 -0
- package/dist/export.js +1021 -0
- package/dist/expressions/evaluate.d.ts +17 -0
- package/dist/expressions/evaluate.js +214 -0
- package/dist/expressions/expression-columns.d.ts +61 -0
- package/dist/expressions/expression-columns.js +72 -0
- package/dist/expressions/expression-types.d.ts +125 -0
- package/dist/expressions/expression-types.js +1 -0
- package/dist/expressions/parse.d.ts +25 -0
- package/dist/expressions/parse.js +537 -0
- package/dist/import.d.ts +232 -0
- package/dist/import.js +846 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +91 -0
- package/dist/install.d.ts +70 -0
- package/dist/install.js +61 -0
- package/dist/license-core.d.ts +37 -0
- package/dist/license-core.js +81 -0
- package/dist/license.d.ts +30 -0
- package/dist/license.js +139 -0
- package/dist/master-detail.d.ts +23 -0
- package/dist/master-detail.js +35 -0
- package/dist/node/studio.js +1 -1
- package/dist/pivot-chart.d.ts +28 -0
- package/dist/pivot-chart.js +71 -0
- package/dist/pivot-designer.d.ts +69 -0
- package/dist/pivot-designer.js +38 -0
- package/dist/pivot-enable.d.ts +6 -0
- package/{src/pivot-enable.ts → dist/pivot-enable.js} +13 -18
- package/dist/pivot.d.ts +111 -0
- package/dist/pivot.js +353 -0
- package/dist/print.d.ts +33 -0
- package/dist/print.js +61 -0
- package/dist/revoked.d.ts +1 -0
- package/{src/revoked.ts → dist/revoked.js} +5 -6
- package/dist/scheduler-assignments.d.ts +64 -0
- package/dist/scheduler-assignments.js +92 -0
- package/dist/scheduler-axis.d.ts +81 -0
- package/dist/scheduler-axis.js +180 -0
- package/{src/scheduler-booking.ts → dist/scheduler-booking.d.ts} +10 -43
- package/dist/scheduler-booking.js +50 -0
- package/dist/scheduler-config.d.ts +177 -0
- package/dist/scheduler-config.js +1 -0
- package/dist/scheduler-dependencies.d.ts +89 -0
- package/dist/scheduler-dependencies.js +179 -0
- package/dist/scheduler-freebusy.d.ts +19 -0
- package/{src/scheduler-freebusy.ts → dist/scheduler-freebusy.js} +9 -19
- package/{src/scheduler-heatmap.ts → dist/scheduler-heatmap.d.ts} +12 -35
- package/dist/scheduler-heatmap.js +41 -0
- package/dist/scheduler-resource-tree.d.ts +48 -0
- package/dist/scheduler-resource-tree.js +78 -0
- package/dist/scheduler-slots.d.ts +37 -0
- package/dist/scheduler-slots.js +80 -0
- package/dist/scheduler-summary.d.ts +41 -0
- package/dist/scheduler-summary.js +54 -0
- package/dist/scheduler.d.ts +7 -0
- package/{src/scheduler.ts → dist/scheduler.js} +13 -14
- package/dist/scheduling.d.ts +132 -0
- package/dist/scheduling.js +228 -0
- package/dist/schema-designer.d.ts +42 -0
- package/dist/schema-designer.js +117 -0
- package/dist/schema.d.ts +369 -0
- package/dist/schema.js +216 -0
- package/dist/smart-shim.d.ts +62 -0
- package/dist/smart-shim.js +34 -0
- package/dist/sources/aggregate.d.ts +43 -0
- package/dist/sources/aggregate.js +60 -0
- package/dist/sources/auth-supabase.d.ts +69 -0
- package/dist/sources/auth-supabase.js +39 -0
- package/dist/sources/dashboard.d.ts +65 -0
- package/dist/sources/dashboard.js +95 -0
- package/dist/sources/field-inference.d.ts +23 -0
- package/dist/sources/field-inference.js +52 -0
- package/dist/sources/filters.d.ts +35 -0
- package/dist/sources/filters.js +52 -0
- package/dist/sources/index.d.ts +18 -0
- package/dist/sources/index.js +18 -0
- package/dist/sources/introspect-supabase.d.ts +30 -0
- package/dist/sources/introspect-supabase.js +134 -0
- package/dist/sources/realtime-supabase.d.ts +37 -0
- package/dist/sources/realtime-supabase.js +52 -0
- package/dist/sources/relation-lookup.d.ts +50 -0
- package/dist/sources/relation-lookup.js +77 -0
- package/dist/sources/rest-adapters.d.ts +48 -0
- package/dist/sources/rest-adapters.js +85 -0
- package/dist/sources/rest.d.ts +38 -0
- package/dist/sources/rest.js +86 -0
- package/dist/sources/schema-from-columns.d.ts +37 -0
- package/dist/sources/schema-from-columns.js +48 -0
- package/dist/sources/supabase.d.ts +33 -0
- package/dist/sources/supabase.js +74 -0
- package/dist/sources/with-entity-rules.d.ts +25 -0
- package/dist/sources/with-entity-rules.js +47 -0
- package/dist/sources/with-relation-labels.d.ts +26 -0
- package/dist/sources/with-relation-labels.js +38 -0
- package/dist/staged-editing.d.ts +85 -0
- package/dist/staged-editing.js +0 -0
- package/dist/studio/bug-report.d.ts +61 -0
- package/dist/studio/bug-report.js +126 -0
- package/dist/studio/cli.d.ts +56 -0
- package/dist/studio/cli.js +70 -0
- package/dist/studio/copilot-core.d.ts +22 -0
- package/{src/studio/copilot-core.ts → dist/studio/copilot-core.js} +34 -38
- package/dist/studio/csv.d.ts +36 -0
- package/dist/studio/csv.js +173 -0
- package/dist/studio/db-connect-string.d.ts +45 -0
- package/dist/studio/db-connect-string.js +113 -0
- package/dist/studio/deploy-cli.d.ts +35 -0
- package/dist/studio/deploy-cli.js +81 -0
- package/dist/studio/emit-project.d.ts +77 -0
- package/dist/studio/emit-project.js +5389 -0
- package/dist/studio/emit-schema.d.ts +110 -0
- package/dist/studio/emit-schema.js +1128 -0
- package/dist/studio/index.d.ts +35 -0
- package/dist/studio/index.js +38 -0
- package/dist/studio/init-flow.d.ts +58 -0
- package/dist/studio/init-flow.js +451 -0
- package/dist/studio/introspect-db.d.ts +47 -0
- package/dist/studio/introspect-db.js +255 -0
- package/dist/studio/introspect-openapi.d.ts +19 -0
- package/dist/studio/introspect-openapi.js +244 -0
- package/dist/studio/introspect-prisma.d.ts +25 -0
- package/dist/studio/introspect-prisma.js +164 -0
- package/dist/studio/introspect.d.ts +35 -0
- package/dist/studio/introspect.js +306 -0
- package/dist/studio/project.d.ts +1327 -0
- package/dist/studio/project.js +1798 -0
- package/dist/studio/sample-data.d.ts +19 -0
- package/dist/studio/sample-data.js +207 -0
- package/dist/studio/samples/ats.d.ts +2 -0
- package/dist/studio/samples/ats.js +194 -0
- package/dist/studio/samples/clinic.d.ts +2 -0
- package/dist/studio/samples/clinic.js +171 -0
- package/dist/studio/samples/crm.d.ts +2 -0
- package/dist/studio/samples/crm.js +280 -0
- package/dist/studio/samples/datasets.d.ts +38 -0
- package/dist/studio/samples/datasets.js +296 -0
- package/dist/studio/samples/ecommerce.d.ts +2 -0
- package/dist/studio/samples/ecommerce.js +178 -0
- package/dist/studio/samples/events.d.ts +2 -0
- package/dist/studio/samples/events.js +192 -0
- package/dist/studio/samples/fleet.d.ts +2 -0
- package/dist/studio/samples/fleet.js +203 -0
- package/dist/studio/samples/gym.d.ts +2 -0
- package/dist/studio/samples/gym.js +205 -0
- package/dist/studio/samples/hr.d.ts +2 -0
- package/dist/studio/samples/hr.js +186 -0
- package/dist/studio/samples/index.d.ts +13 -0
- package/dist/studio/samples/index.js +44 -0
- package/dist/studio/samples/insurance.d.ts +2 -0
- package/dist/studio/samples/insurance.js +220 -0
- package/dist/studio/samples/inventory.d.ts +2 -0
- package/dist/studio/samples/inventory.js +204 -0
- package/dist/studio/samples/invoicing.d.ts +2 -0
- package/dist/studio/samples/invoicing.js +154 -0
- package/dist/studio/samples/library.d.ts +2 -0
- package/dist/studio/samples/library.js +171 -0
- package/dist/studio/samples/live-data.d.ts +5 -0
- package/dist/studio/samples/live-data.js +261 -0
- package/dist/studio/samples/projects.d.ts +2 -0
- package/dist/studio/samples/projects.js +203 -0
- package/dist/studio/samples/realestate.d.ts +2 -0
- package/dist/studio/samples/realestate.js +187 -0
- package/dist/studio/samples/restaurant.d.ts +2 -0
- package/dist/studio/samples/restaurant.js +180 -0
- package/dist/studio/samples/school.d.ts +2 -0
- package/dist/studio/samples/school.js +188 -0
- package/dist/studio/samples/shared.d.ts +142 -0
- package/dist/studio/samples/shared.js +164 -0
- package/dist/studio/samples/starter.d.ts +4 -0
- package/dist/studio/samples/starter.js +237 -0
- package/dist/studio/samples/subscriptions.d.ts +2 -0
- package/dist/studio/samples/subscriptions.js +175 -0
- package/dist/studio/samples/support.d.ts +2 -0
- package/dist/studio/samples/support.js +209 -0
- package/dist/studio/scaffold-app.d.ts +22 -0
- package/{src/studio/scaffold-app.ts → dist/studio/scaffold-app.js} +45 -63
- package/dist/studio/scaffold.d.ts +63 -0
- package/dist/studio/scaffold.js +346 -0
- package/dist/studio/screen-suites.d.ts +193 -0
- package/dist/studio/screen-suites.js +297 -0
- package/dist/studio/themes.d.ts +34 -0
- package/dist/studio/themes.js +41 -0
- package/dist/studio/ui-components.d.ts +89 -0
- package/dist/studio/ui-components.generated.d.ts +29 -0
- package/dist/studio/ui-components.generated.js +7139 -0
- package/dist/studio/ui-components.js +673 -0
- package/dist/studio/user-error.d.ts +23 -0
- package/{src/studio/user-error.ts → dist/studio/user-error.js} +10 -13
- package/dist/studio/verify.d.ts +34 -0
- package/dist/studio/verify.js +47 -0
- package/dist/sveltekit/in-memory.d.ts +14 -0
- package/dist/sveltekit/in-memory.js +113 -0
- package/dist/sveltekit/index.d.ts +6 -0
- package/dist/sveltekit/index.js +5 -0
- package/dist/sveltekit/query-plan.d.ts +44 -0
- package/dist/sveltekit/query-plan.js +76 -0
- package/dist/sveltekit/sql-source.d.ts +47 -0
- package/dist/sveltekit/sql-source.js +106 -0
- package/dist/sveltekit/sql.d.ts +48 -0
- package/dist/sveltekit/sql.js +73 -0
- package/dist/sveltekit/transport.d.ts +209 -0
- package/dist/sveltekit/transport.js +219 -0
- package/{src/sveltekit/types.ts → dist/sveltekit/types.d.ts} +8 -11
- package/dist/sveltekit/types.js +1 -0
- package/dist/upgrade-prompt.d.ts +16 -0
- package/dist/upgrade-prompt.js +141 -0
- package/dist/version.d.ts +13 -0
- package/{src/version.ts → dist/version.js} +1 -1
- package/dist/watermark.d.ts +2 -0
- package/dist/watermark.js +146 -0
- package/package.json +26 -24
- package/src/SvBoard.dom.test.ts +0 -67
- package/src/SvGridAlerts.dom.test.ts +0 -113
- package/src/SvGridEditPanel.dom.test.ts +0 -320
- package/src/SvRecordDetail.dom.test.ts +0 -137
- package/src/SvSchedule.dom.test.ts +0 -57
- package/src/ai-export-pdf.dom.test.ts +0 -77
- package/src/ai-export-xlsx.dom.test.ts +0 -90
- package/src/ai-export.dom.test.ts +0 -114
- package/src/alerts/alert-engine-attach.ts +0 -165
- package/src/alerts/alert-engine.test.ts +0 -135
- package/src/alerts/alert-engine.ts +0 -260
- package/src/alerts/alert-formats.test.ts +0 -87
- package/src/alerts/alert-formats.ts +0 -77
- package/src/alerts/alert-observer.test.ts +0 -189
- package/src/alerts/alert-observer.ts +0 -208
- package/src/alerts/alert-scheduler.ts +0 -96
- package/src/alerts/alert-storage.test.ts +0 -54
- package/src/alerts/alert-storage.ts +0 -116
- package/src/alerts/alert-store.svelte.ts +0 -80
- package/src/alerts/alert-types.ts +0 -94
- package/src/board.dom.test.ts +0 -941
- package/src/edit-panel.test.ts +0 -322
- package/src/edit-panel.ts +0 -379
- package/src/export-conditional.test.ts +0 -60
- package/src/export-conditional.ts +0 -94
- package/src/export-ooxml.test.ts +0 -152
- package/src/export-ooxml.ts +0 -489
- package/src/export-pdf.test.ts +0 -138
- package/src/export-pdf.ts +0 -245
- package/src/export-print.test.ts +0 -66
- package/src/export-print.ts +0 -132
- package/src/export-serialize.test.ts +0 -56
- package/src/export-xls.ts +0 -199
- package/src/export-xlsx-roundtrip.test.ts +0 -120
- package/src/export-xlsx.test.ts +0 -150
- package/src/export.test.ts +0 -349
- package/src/export.ts +0 -1464
- package/src/expressions/evaluate.test.ts +0 -111
- package/src/expressions/evaluate.ts +0 -237
- package/src/expressions/expression-columns.ts +0 -133
- package/src/expressions/expression-types.ts +0 -84
- package/src/expressions/parse.test.ts +0 -106
- package/src/expressions/parse.ts +0 -610
- package/src/import-automap.test.ts +0 -131
- package/src/import-hardening.test.ts +0 -158
- package/src/import.test.ts +0 -415
- package/src/import.ts +0 -1044
- package/src/index.ts +0 -569
- package/src/install.ts +0 -154
- package/src/license-core.test.ts +0 -23
- package/src/license-core.ts +0 -107
- package/src/license-expiry.test.ts +0 -127
- package/src/license.ts +0 -137
- package/src/master-detail.test.ts +0 -61
- package/src/master-detail.ts +0 -42
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -178
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js +0 -3875
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js +0 -21
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js +0 -1938
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js +0 -205
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js +0 -18
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js +0 -264
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js +0 -697
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -134
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -55
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -2
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -46843
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -237
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -2
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -4
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -7
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -4
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -5
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -5
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -5
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -6
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -83
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -559
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -24
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -122
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -2
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -89
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -241
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js +0 -4938
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js +0 -495
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js +0 -190
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js +0 -8
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js.map +0 -1
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js +0 -138
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js.map +0 -1
- package/src/pivot-chart.test.ts +0 -86
- package/src/pivot-chart.ts +0 -112
- package/src/pivot-designer.ts +0 -101
- package/src/pivot.test.ts +0 -335
- package/src/pivot.ts +0 -578
- package/src/print.ts +0 -107
- package/src/scheduler-assignments.test.ts +0 -97
- package/src/scheduler-assignments.ts +0 -134
- package/src/scheduler-axis.test.ts +0 -108
- package/src/scheduler-axis.ts +0 -238
- package/src/scheduler-booking.test.ts +0 -57
- package/src/scheduler-config.ts +0 -179
- package/src/scheduler-dependencies.test.ts +0 -155
- package/src/scheduler-dependencies.ts +0 -223
- package/src/scheduler-freebusy.test.ts +0 -41
- package/src/scheduler-heatmap.test.ts +0 -39
- package/src/scheduler-resource-tree.test.ts +0 -84
- package/src/scheduler-resource-tree.ts +0 -107
- package/src/scheduler-slots.test.ts +0 -53
- package/src/scheduler-slots.ts +0 -94
- package/src/scheduler-summary.test.ts +0 -47
- package/src/scheduler-summary.ts +0 -81
- package/src/scheduling.test.ts +0 -194
- package/src/scheduling.ts +0 -293
- package/src/schema-designer.test.ts +0 -121
- package/src/schema-designer.ts +0 -142
- package/src/schema.test.ts +0 -268
- package/src/schema.ts +0 -562
- package/src/smart-shim.ts +0 -107
- package/src/sources/aggregate.test.ts +0 -79
- package/src/sources/aggregate.ts +0 -102
- package/src/sources/auth-supabase.test.ts +0 -80
- package/src/sources/auth-supabase.ts +0 -87
- package/src/sources/dashboard.kpi.test.ts +0 -50
- package/src/sources/dashboard.test.ts +0 -61
- package/src/sources/dashboard.ts +0 -136
- package/src/sources/field-inference.test.ts +0 -60
- package/src/sources/field-inference.ts +0 -63
- package/src/sources/filters.test.ts +0 -58
- package/src/sources/filters.ts +0 -70
- package/src/sources/index.ts +0 -67
- package/src/sources/introspect-supabase.test.ts +0 -112
- package/src/sources/introspect-supabase.ts +0 -139
- package/src/sources/realtime-supabase.test.ts +0 -93
- package/src/sources/realtime-supabase.ts +0 -99
- package/src/sources/relation-lookup.test.ts +0 -99
- package/src/sources/relation-lookup.ts +0 -128
- package/src/sources/rest-adapters.test.ts +0 -95
- package/src/sources/rest-adapters.ts +0 -120
- package/src/sources/rest.test.ts +0 -104
- package/src/sources/rest.ts +0 -129
- package/src/sources/schema-from-columns.test.ts +0 -106
- package/src/sources/schema-from-columns.ts +0 -88
- package/src/sources/supabase.test.ts +0 -110
- package/src/sources/supabase.ts +0 -99
- package/src/sources/with-entity-rules.test.ts +0 -104
- package/src/sources/with-entity-rules.ts +0 -78
- package/src/sources/with-relation-labels.test.ts +0 -75
- package/src/sources/with-relation-labels.ts +0 -73
- package/src/staged-editing.ts +0 -0
- package/src/studio/bug-report.test.ts +0 -101
- package/src/studio/bug-report.ts +0 -165
- package/src/studio/cli.test.ts +0 -187
- package/src/studio/cli.ts +0 -117
- package/src/studio/copilot-core.test.ts +0 -45
- package/src/studio/csv.test.ts +0 -90
- package/src/studio/csv.ts +0 -165
- package/src/studio/db-connect-string.test.ts +0 -94
- package/src/studio/db-connect-string.ts +0 -128
- package/src/studio/deploy-cli.test.ts +0 -56
- package/src/studio/deploy-cli.ts +0 -100
- package/src/studio/emit-project.test.ts +0 -3324
- package/src/studio/emit-project.ts +0 -5377
- package/src/studio/emit-schema.test.ts +0 -111
- package/src/studio/emit-schema.ts +0 -1181
- package/src/studio/index.ts +0 -380
- package/src/studio/init-flow.test.ts +0 -514
- package/src/studio/init-flow.ts +0 -556
- package/src/studio/introspect-db.test.ts +0 -186
- package/src/studio/introspect-db.ts +0 -312
- package/src/studio/introspect-openapi.test.ts +0 -84
- package/src/studio/introspect-openapi.ts +0 -252
- package/src/studio/introspect-prisma.test.ts +0 -99
- package/src/studio/introspect-prisma.ts +0 -175
- package/src/studio/introspect.test.ts +0 -172
- package/src/studio/introspect.ts +0 -310
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -1
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -3
- package/src/studio/pipeline.test.ts +0 -42
- package/src/studio/project-robust.test.ts +0 -157
- package/src/studio/project.test.ts +0 -929
- package/src/studio/project.ts +0 -2394
- package/src/studio/sample-data.test.ts +0 -58
- package/src/studio/sample-data.ts +0 -203
- package/src/studio/samples/ats.ts +0 -202
- package/src/studio/samples/clinic.ts +0 -179
- package/src/studio/samples/crm.ts +0 -291
- package/src/studio/samples/datasets.test.ts +0 -84
- package/src/studio/samples/datasets.ts +0 -340
- package/src/studio/samples/ecommerce.ts +0 -187
- package/src/studio/samples/events.ts +0 -201
- package/src/studio/samples/fleet.ts +0 -213
- package/src/studio/samples/gym.ts +0 -215
- package/src/studio/samples/hr.ts +0 -195
- package/src/studio/samples/index.ts +0 -55
- package/src/studio/samples/insurance.ts +0 -229
- package/src/studio/samples/inventory.ts +0 -213
- package/src/studio/samples/invoicing.ts +0 -162
- package/src/studio/samples/library.ts +0 -180
- package/src/studio/samples/live-data.test.ts +0 -99
- package/src/studio/samples/live-data.ts +0 -306
- package/src/studio/samples/projects.ts +0 -212
- package/src/studio/samples/realestate.ts +0 -196
- package/src/studio/samples/restaurant.ts +0 -189
- package/src/studio/samples/samples.test.ts +0 -304
- package/src/studio/samples/school.ts +0 -197
- package/src/studio/samples/seed-floor.test.ts +0 -25
- package/src/studio/samples/shared.ts +0 -237
- package/src/studio/samples/starter.ts +0 -251
- package/src/studio/samples/subscriptions.ts +0 -183
- package/src/studio/samples/support.ts +0 -216
- package/src/studio/scaffold-app.test.ts +0 -91
- package/src/studio/scaffold.test.ts +0 -192
- package/src/studio/scaffold.ts +0 -390
- package/src/studio/screen-suites.test.ts +0 -269
- package/src/studio/screen-suites.ts +0 -448
- package/src/studio/themes.ts +0 -62
- package/src/studio/ui-components-surface.test.ts +0 -185
- package/src/studio/ui-components.generated.ts +0 -7154
- package/src/studio/ui-components.ts +0 -742
- package/src/studio/verify.test.ts +0 -47
- package/src/studio/verify.ts +0 -79
- package/src/sveltekit/in-memory.test.ts +0 -104
- package/src/sveltekit/in-memory.ts +0 -129
- package/src/sveltekit/index.ts +0 -24
- package/src/sveltekit/query-plan.test.ts +0 -109
- package/src/sveltekit/query-plan.ts +0 -125
- package/src/sveltekit/sql-source.test.ts +0 -198
- package/src/sveltekit/sql-source.ts +0 -171
- package/src/sveltekit/sql.test.ts +0 -80
- package/src/sveltekit/sql.ts +0 -131
- package/src/sveltekit/transport-scope.test.ts +0 -125
- package/src/sveltekit/transport.test.ts +0 -201
- package/src/sveltekit/transport.ts +0 -373
- package/src/upgrade-prompt.test.ts +0 -71
- package/src/upgrade-prompt.ts +0 -162
- package/src/version.test.ts +0 -17
- package/src/watermark.test.ts +0 -97
- package/src/watermark.ts +0 -156
- /package/{src → dist}/SvAlertRuleEditor.svelte +0 -0
- /package/{src → dist}/SvAlertsManager.svelte +0 -0
- /package/{src → dist}/SvAlertsPanel.svelte +0 -0
- /package/{src → dist}/SvAuthGate.svelte +0 -0
- /package/{src → dist}/SvBoard.svelte +0 -0
- /package/{src → dist}/SvExportMenu.svelte +0 -0
- /package/{src → dist}/SvExpressionEditor.svelte +0 -0
- /package/{src → dist}/SvFileInput.svelte +0 -0
- /package/{src → dist}/SvGridAlerts.svelte +0 -0
- /package/{src → dist}/SvGridBoard.svelte +0 -0
- /package/{src → dist}/SvGridEditPanel.svelte +0 -0
- /package/{src → dist}/SvGridMasterDetail.svelte +0 -0
- /package/{src → dist}/SvGridScheduler.svelte +0 -0
- /package/{src → dist}/SvImportDialog.svelte +0 -0
- /package/{src → dist}/SvLookupInput.svelte +0 -0
- /package/{src → dist}/SvPivotDesigner.svelte +0 -0
- /package/{src → dist}/SvRecordDetail.svelte +0 -0
- /package/{src → dist}/SvSchedule.svelte +0 -0
- /package/{src → dist}/SvSchemaChart.svelte +0 -0
- /package/{src → dist}/SvSchemaDashboard.svelte +0 -0
- /package/{src → dist}/pdfmake-shims.d.ts +0 -0
- /package/{src → dist}/smart.export.d.ts +0 -0
- /package/{src → dist}/smart.export.js +0 -0
- /package/{src → dist}/studio/HANDLERS-DESIGN.md +0 -0
- /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +0 -0
- /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +0 -0
- /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js.map +0 -0
package/src/import.ts
DELETED
|
@@ -1,1044 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pro Import feature
|
|
3
|
-
* ------------------
|
|
4
|
-
*
|
|
5
|
-
* Read Excel (.xlsx), CSV, TSV, or JSON in the browser and produce a typed
|
|
6
|
-
* preview of the parsed rows, including per-cell validation errors. The
|
|
7
|
-
* caller decides whether to commit the parsed rows into the grid via
|
|
8
|
-
* `api.addRows(...)`, or to render an editing modal so the user can fix
|
|
9
|
-
* issues first.
|
|
10
|
-
*
|
|
11
|
-
* Two flavours of usage:
|
|
12
|
-
*
|
|
13
|
-
* 1. Read + preview, then commit explicitly:
|
|
14
|
-
*
|
|
15
|
-
* const result = await api.importData({ file, format: 'auto' })
|
|
16
|
-
* // result.headers, result.rows, result.errors
|
|
17
|
-
* if (result.errors.length === 0) api.addRows(result.rows, 'bottom')
|
|
18
|
-
*
|
|
19
|
-
* 2. Read and commit in one go (no preview UX):
|
|
20
|
-
*
|
|
21
|
-
* await api.importData({ file, format: 'auto', commit: true })
|
|
22
|
-
*
|
|
23
|
-
* Both go through the same polite license soft-gate as `exportData` and
|
|
24
|
-
* `print`, so unlicensed evaluation works but emits a one-time nudge.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import type { RowData, SvGridApi, TableFeatures } from '@svgrid/grid'
|
|
28
|
-
import { assertEnterpriseLicensed } from './license'
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Public types
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
export type ImportFormat = 'xlsx' | 'csv' | 'tsv' | 'json' | 'auto'
|
|
35
|
-
|
|
36
|
-
export type ImportColumnMap = Record<string, string>
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Declared data type per target field. When `columnTypes` is set on
|
|
40
|
-
* `ImportOptions`, the parser tries to coerce each non-empty cell into
|
|
41
|
-
* the declared shape and emits an `ImportRowError` when it can't -
|
|
42
|
-
* stricter than the ad-hoc "looks like a number" fallback.
|
|
43
|
-
*/
|
|
44
|
-
export type ImportFieldType =
|
|
45
|
-
| 'string'
|
|
46
|
-
| 'number'
|
|
47
|
-
| 'integer'
|
|
48
|
-
| 'boolean'
|
|
49
|
-
| 'date' // returns ISO yyyy-mm-dd
|
|
50
|
-
| 'datetime' // returns ISO yyyy-mm-ddThh:mm:ss
|
|
51
|
-
| 'json' // parses the cell as JSON (object / array / primitive)
|
|
52
|
-
|
|
53
|
-
export type ImportColumnTypes = Record<string, ImportFieldType>
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* The slice of a grid column the auto-mapper needs: its target `field`,
|
|
57
|
-
* a human `header` to match source headers against, and the `format`
|
|
58
|
-
* config to infer a column type from. Shaped to accept the output of
|
|
59
|
-
* `api.getColumns()` directly.
|
|
60
|
-
*/
|
|
61
|
-
export type ImportGridColumn = {
|
|
62
|
-
field?: string
|
|
63
|
-
header?: string
|
|
64
|
-
format?: { type?: string } | null
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Progress ticks emitted during a large import so a UI can show a bar. */
|
|
68
|
-
export type ImportProgress = {
|
|
69
|
-
/** Which stage is running. */
|
|
70
|
-
phase: 'parse' | 'map'
|
|
71
|
-
/** 0..1 completion of the current phase. */
|
|
72
|
-
ratio: number
|
|
73
|
-
/** Rows processed so far in this phase. */
|
|
74
|
-
done: number
|
|
75
|
-
/** Total rows this phase will process (0 when unknown, e.g. mid-parse). */
|
|
76
|
-
total: number
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export type ImportRowError = {
|
|
80
|
-
/** 0-based row index in the SOURCE file (excluding the header). */
|
|
81
|
-
rowIndex: number
|
|
82
|
-
/** Target field name (the grid's column field), or '*' for whole-row errors. */
|
|
83
|
-
field: string
|
|
84
|
-
message: string
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export type ImportValidator<TData> = (
|
|
88
|
-
row: TData,
|
|
89
|
-
rowIndex: number,
|
|
90
|
-
) => Array<{ field: string; message: string }>
|
|
91
|
-
|
|
92
|
-
export type ImportOptions<TData> = {
|
|
93
|
-
/** The file to read. A `File`/`Blob` works for xlsx; a string is treated as
|
|
94
|
-
* inline CSV/TSV/JSON text. */
|
|
95
|
-
file: File | Blob | string
|
|
96
|
-
/** When 'auto', the format is sniffed from `file.name`'s extension (Files
|
|
97
|
-
* only), otherwise the function inspects the first characters of the
|
|
98
|
-
* text payload. */
|
|
99
|
-
format?: ImportFormat
|
|
100
|
-
/**
|
|
101
|
-
* Map source-header -> target-field. Missing entries fall back to the
|
|
102
|
-
* source header verbatim (lowercased + trimmed). Pass `null` for a
|
|
103
|
-
* source header you want to drop on the floor.
|
|
104
|
-
*/
|
|
105
|
-
columnMap?: ImportColumnMap
|
|
106
|
-
/**
|
|
107
|
-
* Declared data types per target field. When set, the importer uses
|
|
108
|
-
* strict per-type coercion (`'2024-03-15'` -> ISO date; `'$1,234'`
|
|
109
|
-
* -> 1234 for `number` fields) and emits an `ImportRowError` whenever
|
|
110
|
-
* a value can't be coerced. Fields not listed fall back to the
|
|
111
|
-
* built-in best-effort coercion (currency / number / date sniffing
|
|
112
|
-
* by value shape).
|
|
113
|
-
*/
|
|
114
|
-
columnTypes?: ImportColumnTypes
|
|
115
|
-
/** Per-row validator. Returned errors land in `result.errors`. */
|
|
116
|
-
validator?: ImportValidator<TData>
|
|
117
|
-
/**
|
|
118
|
-
* When true, source headers are fuzzy-matched to the grid's own columns
|
|
119
|
-
* (by header label or field name) to build the `columnMap` automatically,
|
|
120
|
-
* and `columnTypes` are inferred from each matched column's `format`
|
|
121
|
-
* (currency / number / percent -> number, date -> date, datetime ->
|
|
122
|
-
* datetime). Anything you pass explicitly in `columnMap` / `columnTypes`
|
|
123
|
-
* wins over the guess. This is what powers "drop a file and it lines up
|
|
124
|
-
* with my grid" in `SvImportDialog`.
|
|
125
|
-
*/
|
|
126
|
-
autoMap?: boolean
|
|
127
|
-
/** When true, the parsed rows are appended to the grid via
|
|
128
|
-
* `api.addRows(...)` automatically. Defaults to false (preview mode). */
|
|
129
|
-
commit?: boolean
|
|
130
|
-
/** Where to insert when `commit` is true. Defaults to 'bottom'. */
|
|
131
|
-
commitAt?: 'top' | 'bottom' | number
|
|
132
|
-
/**
|
|
133
|
-
* Drop duplicate rows by this key field, keeping the LAST occurrence
|
|
134
|
-
* (upsert-style, so a later row in the file wins). Runs after mapping,
|
|
135
|
-
* so pass the TARGET field name. Rows with a null/undefined key are kept.
|
|
136
|
-
*/
|
|
137
|
-
dedupeBy?: keyof TData | (string & {})
|
|
138
|
-
// --- Enterprise guard-rails ----------------------------------------------
|
|
139
|
-
/** Reject the import if the source has more than this many data rows.
|
|
140
|
-
* Guards against a runaway upload locking up the tab. */
|
|
141
|
-
maxRows?: number
|
|
142
|
-
/**
|
|
143
|
-
* What to do when the source exceeds `maxRows`. `'reject'` (default)
|
|
144
|
-
* throws so no data is silently lost; `'truncate'` keeps the first
|
|
145
|
-
* `maxRows` rows and sets `result.truncated` so you can warn the user.
|
|
146
|
-
* Only affects `maxRows` - `maxBytes` always rejects (a partial binary /
|
|
147
|
-
* CSV can't be truncated safely).
|
|
148
|
-
*/
|
|
149
|
-
overLimit?: 'reject' | 'truncate'
|
|
150
|
-
/** Reject a File/Blob larger than this many bytes before it is read. */
|
|
151
|
-
maxBytes?: number
|
|
152
|
-
/** Stop collecting validation errors past this many (keeps memory bounded
|
|
153
|
-
* on a pathological file). `result.errorsTruncated` flags when it hit. */
|
|
154
|
-
maxErrors?: number
|
|
155
|
-
/** Text encoding for CSV/TSV/JSON read from a File/Blob. Default 'utf-8'.
|
|
156
|
-
* Set e.g. 'windows-1252' / 'latin1' for legacy Excel CSV exports. */
|
|
157
|
-
encoding?: string
|
|
158
|
-
/** Abort a long parse/map (wire to an AbortController). Throws an
|
|
159
|
-
* AbortError, matching the export API. */
|
|
160
|
-
signal?: AbortSignal
|
|
161
|
-
/** Progress callback for large files (parse + map phases). */
|
|
162
|
-
onProgress?: (progress: ImportProgress) => void
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export type ImportResult<TData> = {
|
|
166
|
-
/** Source headers as found in the file, in their original order. */
|
|
167
|
-
headers: string[]
|
|
168
|
-
/** Parsed + mapped rows. Order matches the source. */
|
|
169
|
-
rows: TData[]
|
|
170
|
-
/** All validation errors (zero-length if the file passed clean). */
|
|
171
|
-
errors: ImportRowError[]
|
|
172
|
-
/** Rows skipped because they were entirely blank. */
|
|
173
|
-
skipped: number
|
|
174
|
-
/** Total source rows the parser saw (including blanks and bad rows). */
|
|
175
|
-
total: number
|
|
176
|
-
/** Detected format (resolved from 'auto'). */
|
|
177
|
-
format: Exclude<ImportFormat, 'auto'>
|
|
178
|
-
/** True when `maxErrors` capped the error list (there were more). */
|
|
179
|
-
errorsTruncated?: boolean
|
|
180
|
-
/** Rows dropped by `dedupeBy` (0 when not deduping). */
|
|
181
|
-
deduped?: number
|
|
182
|
-
/** True when `maxRows` + `overLimit:'truncate'` dropped trailing rows.
|
|
183
|
-
* `total` still reports the original source row count. */
|
|
184
|
-
truncated?: boolean
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
|
-
// Lazy peer dependency: jszip (xlsx only)
|
|
189
|
-
// ---------------------------------------------------------------------------
|
|
190
|
-
|
|
191
|
-
let jszipPromise: Promise<unknown> | null = null
|
|
192
|
-
async function getJSZip(): Promise<any> {
|
|
193
|
-
if (typeof window === 'undefined') {
|
|
194
|
-
throw new Error('@svgrid/enterprise: importData requires a browser environment')
|
|
195
|
-
}
|
|
196
|
-
const g = globalThis as unknown as { JSZip?: unknown }
|
|
197
|
-
if (g.JSZip) return g.JSZip
|
|
198
|
-
if (!jszipPromise) {
|
|
199
|
-
jszipPromise = (async () => {
|
|
200
|
-
let mod: unknown
|
|
201
|
-
try {
|
|
202
|
-
// Optional peer, loaded on demand (keeps consumer type-checks clean when absent).
|
|
203
|
-
// @ts-ignore - "jszip" is an optional peerDependency
|
|
204
|
-
mod = await import('jszip')
|
|
205
|
-
} catch {
|
|
206
|
-
throw new Error(
|
|
207
|
-
'@svgrid/enterprise: xlsx import requires the "jszip" peer dependency. ' +
|
|
208
|
-
'Install it with: pnpm add jszip',
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
const J = (mod as { default?: unknown }).default ?? mod
|
|
212
|
-
g.JSZip = J
|
|
213
|
-
return J
|
|
214
|
-
})()
|
|
215
|
-
}
|
|
216
|
-
return jszipPromise
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// Format sniffing
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
function sniffFormat(
|
|
224
|
-
file: File | Blob | string,
|
|
225
|
-
declared: ImportFormat,
|
|
226
|
-
): Exclude<ImportFormat, 'auto'> {
|
|
227
|
-
if (declared !== 'auto') return declared
|
|
228
|
-
// File: trust the extension.
|
|
229
|
-
if (file instanceof File && file.name) {
|
|
230
|
-
const ext = file.name.toLowerCase().split('.').pop() ?? ''
|
|
231
|
-
if (ext === 'xlsx') return 'xlsx'
|
|
232
|
-
if (ext === 'csv') return 'csv'
|
|
233
|
-
if (ext === 'tsv' || ext === 'tab') return 'tsv'
|
|
234
|
-
if (ext === 'json') return 'json'
|
|
235
|
-
}
|
|
236
|
-
// String: sniff the first non-whitespace char.
|
|
237
|
-
if (typeof file === 'string') {
|
|
238
|
-
const trimmed = file.trimStart()
|
|
239
|
-
if (trimmed.startsWith('[') || trimmed.startsWith('{')) return 'json'
|
|
240
|
-
// Tab-rich heads are tsv; otherwise default to csv.
|
|
241
|
-
const firstLine = trimmed.slice(0, 200).split(/\r?\n/)[0] ?? ''
|
|
242
|
-
if ((firstLine.match(/\t/g)?.length ?? 0) >= 1) return 'tsv'
|
|
243
|
-
return 'csv'
|
|
244
|
-
}
|
|
245
|
-
// Blob without a filename / extension: assume xlsx (the most common case
|
|
246
|
-
// the importer is asked to handle).
|
|
247
|
-
return 'xlsx'
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ---------------------------------------------------------------------------
|
|
251
|
-
// CSV / TSV parser
|
|
252
|
-
// ---------------------------------------------------------------------------
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* RFC 4180-flavour CSV / TSV parser. Handles:
|
|
256
|
-
* - quoted fields with embedded commas / tabs / newlines
|
|
257
|
-
* - escaped quotes ("") inside quoted fields
|
|
258
|
-
* - both \r\n and \n line endings
|
|
259
|
-
*
|
|
260
|
-
* Doesn't aim to be the world's fastest parser - it walks the string
|
|
261
|
-
* character-by-character. For files in the 10-100k row range this is
|
|
262
|
-
* fine; for million-row imports, do the import server-side.
|
|
263
|
-
*/
|
|
264
|
-
function parseDelimited(text: string, sep: ',' | '\t'): string[][] {
|
|
265
|
-
const rows: string[][] = []
|
|
266
|
-
let row: string[] = []
|
|
267
|
-
let field = ''
|
|
268
|
-
let i = 0
|
|
269
|
-
let inQuotes = false
|
|
270
|
-
// Strip a UTF-8 BOM if present so the first header isn't named "ID".
|
|
271
|
-
if (text.charCodeAt(0) === 0xfeff) i = 1
|
|
272
|
-
while (i < text.length) {
|
|
273
|
-
const c = text[i]!
|
|
274
|
-
if (inQuotes) {
|
|
275
|
-
if (c === '"') {
|
|
276
|
-
if (text[i + 1] === '"') { field += '"'; i += 2; continue }
|
|
277
|
-
inQuotes = false
|
|
278
|
-
i += 1
|
|
279
|
-
continue
|
|
280
|
-
}
|
|
281
|
-
field += c
|
|
282
|
-
i += 1
|
|
283
|
-
continue
|
|
284
|
-
}
|
|
285
|
-
if (c === '"') { inQuotes = true; i += 1; continue }
|
|
286
|
-
if (c === sep) { row.push(field); field = ''; i += 1; continue }
|
|
287
|
-
if (c === '\r') {
|
|
288
|
-
if (text[i + 1] === '\n') i += 1
|
|
289
|
-
row.push(field); rows.push(row); row = []; field = ''; i += 1; continue
|
|
290
|
-
}
|
|
291
|
-
if (c === '\n') {
|
|
292
|
-
row.push(field); rows.push(row); row = []; field = ''; i += 1; continue
|
|
293
|
-
}
|
|
294
|
-
field += c
|
|
295
|
-
i += 1
|
|
296
|
-
}
|
|
297
|
-
// Flush the trailing field / row (no trailing newline).
|
|
298
|
-
if (field.length > 0 || row.length > 0) {
|
|
299
|
-
row.push(field)
|
|
300
|
-
rows.push(row)
|
|
301
|
-
}
|
|
302
|
-
return rows
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// ---------------------------------------------------------------------------
|
|
306
|
-
// XLSX parser (minimal)
|
|
307
|
-
// ---------------------------------------------------------------------------
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Minimal SpreadsheetML reader. Supports the .xlsx shape Excel and Google
|
|
311
|
-
* Sheets produce by default: one default sheet, optional shared-strings
|
|
312
|
-
* table, inline strings, numbers, and dates expressed as serial numbers.
|
|
313
|
-
*
|
|
314
|
-
* The grid does NOT try to be a fully-featured Excel reader. Formulas
|
|
315
|
-
* are read as their cached value when present. Multi-sheet imports
|
|
316
|
-
* require the caller to pre-extract the desired sheet (or for a future
|
|
317
|
-
* release).
|
|
318
|
-
*/
|
|
319
|
-
async function parseXlsx(file: File | Blob): Promise<string[][]> {
|
|
320
|
-
const J = await getJSZip()
|
|
321
|
-
const buf = await (file as Blob).arrayBuffer()
|
|
322
|
-
const zip = await J.loadAsync(buf)
|
|
323
|
-
|
|
324
|
-
// Read sharedStrings.xml if it exists.
|
|
325
|
-
let sharedStrings: string[] = []
|
|
326
|
-
const ssEntry = zip.file('xl/sharedStrings.xml')
|
|
327
|
-
if (ssEntry) {
|
|
328
|
-
const ssText = await ssEntry.async('string')
|
|
329
|
-
sharedStrings = parseSharedStrings(ssText)
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Read the FIRST sheet. Workbook.xml -> rels mapping is involved; for the
|
|
333
|
-
// 99% case the first sheet lives at xl/worksheets/sheet1.xml.
|
|
334
|
-
const sheetEntry =
|
|
335
|
-
zip.file('xl/worksheets/sheet1.xml') ??
|
|
336
|
-
zip.file('xl/worksheets/sheet 1.xml')
|
|
337
|
-
if (!sheetEntry) {
|
|
338
|
-
throw new Error(
|
|
339
|
-
'@svgrid/enterprise: could not locate sheet1.xml in the .xlsx archive',
|
|
340
|
-
)
|
|
341
|
-
}
|
|
342
|
-
const sheetText = await sheetEntry.async('string')
|
|
343
|
-
return parseSheetXml(sheetText, sharedStrings)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/** Pull every `<t>...</t>` out of sharedStrings.xml in document order. */
|
|
347
|
-
function parseSharedStrings(xml: string): string[] {
|
|
348
|
-
const out: string[] = []
|
|
349
|
-
// A shared-string item may be a plain <si><t>X</t></si> or a rich-text
|
|
350
|
-
// <si><r><t>X</t></r><r><t>Y</t></r></si>. Concatenate every <t> inside
|
|
351
|
-
// each <si>.
|
|
352
|
-
const itemRe = /<si\b[^>]*>([\s\S]*?)<\/si>/g
|
|
353
|
-
const tRe = /<t\b[^>]*>([\s\S]*?)<\/t>/g
|
|
354
|
-
let m: RegExpExecArray | null
|
|
355
|
-
while ((m = itemRe.exec(xml)) !== null) {
|
|
356
|
-
let combined = ''
|
|
357
|
-
let n: RegExpExecArray | null
|
|
358
|
-
tRe.lastIndex = 0
|
|
359
|
-
while ((n = tRe.exec(m[1] ?? '')) !== null) {
|
|
360
|
-
combined += decodeXmlEntities(n[1] ?? '')
|
|
361
|
-
}
|
|
362
|
-
out.push(combined)
|
|
363
|
-
}
|
|
364
|
-
return out
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function decodeXmlEntities(s: string): string {
|
|
368
|
-
return s
|
|
369
|
-
.replace(/&/g, '&')
|
|
370
|
-
.replace(/</g, '<')
|
|
371
|
-
.replace(/>/g, '>')
|
|
372
|
-
.replace(/"/g, '"')
|
|
373
|
-
.replace(/'/g, "'")
|
|
374
|
-
.replace(/ /g, '\n')
|
|
375
|
-
.replace(/	/g, '\t')
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Convert a sheet's XML body into a dense `string[][]`. Holes in the
|
|
380
|
-
* cell stream (e.g. row jumps from col B to col E) are filled with
|
|
381
|
-
* empty strings so the result is rectangular.
|
|
382
|
-
*/
|
|
383
|
-
function parseSheetXml(xml: string, sharedStrings: string[]): string[][] {
|
|
384
|
-
const rows: string[][] = []
|
|
385
|
-
const rowRe = /<row\b[^>]*>([\s\S]*?)<\/row>/g
|
|
386
|
-
const cellRe = /<c\b([^>]*)>([\s\S]*?)<\/c>/g
|
|
387
|
-
const inlineStrRe = /<is>\s*([\s\S]*?)\s*<\/is>/
|
|
388
|
-
const tRe = /<t\b[^>]*>([\s\S]*?)<\/t>/g
|
|
389
|
-
const vRe = /<v>([\s\S]*?)<\/v>/
|
|
390
|
-
let rowMatch: RegExpExecArray | null
|
|
391
|
-
while ((rowMatch = rowRe.exec(xml)) !== null) {
|
|
392
|
-
const rowBody = rowMatch[1] ?? ''
|
|
393
|
-
const cells: string[] = []
|
|
394
|
-
let cellMatch: RegExpExecArray | null
|
|
395
|
-
cellRe.lastIndex = 0
|
|
396
|
-
while ((cellMatch = cellRe.exec(rowBody)) !== null) {
|
|
397
|
-
const attrs = cellMatch[1] ?? ''
|
|
398
|
-
const body = cellMatch[2] ?? ''
|
|
399
|
-
const ref = /r="([A-Z]+)\d+"/.exec(attrs)?.[1] ?? ''
|
|
400
|
-
const colIndex = colRefToIndex(ref)
|
|
401
|
-
const type = /t="([^"]+)"/.exec(attrs)?.[1] ?? 'n'
|
|
402
|
-
// Pad holes with empty strings so column alignment survives.
|
|
403
|
-
while (cells.length < colIndex) cells.push('')
|
|
404
|
-
let value = ''
|
|
405
|
-
if (type === 's') {
|
|
406
|
-
const idx = Number(vRe.exec(body)?.[1] ?? '-1')
|
|
407
|
-
value = sharedStrings[idx] ?? ''
|
|
408
|
-
} else if (type === 'inlineStr' || type === 'str') {
|
|
409
|
-
const inlineBody = inlineStrRe.exec(body)?.[1] ?? body
|
|
410
|
-
let parts = ''
|
|
411
|
-
let n: RegExpExecArray | null
|
|
412
|
-
tRe.lastIndex = 0
|
|
413
|
-
while ((n = tRe.exec(inlineBody)) !== null) parts += decodeXmlEntities(n[1] ?? '')
|
|
414
|
-
value = parts || decodeXmlEntities(vRe.exec(body)?.[1] ?? '')
|
|
415
|
-
} else if (type === 'b') {
|
|
416
|
-
value = vRe.exec(body)?.[1] === '1' ? 'true' : 'false'
|
|
417
|
-
} else {
|
|
418
|
-
// Numeric or date - we surface as the raw number string. The
|
|
419
|
-
// caller's column type then handles parsing.
|
|
420
|
-
value = decodeXmlEntities(vRe.exec(body)?.[1] ?? '')
|
|
421
|
-
}
|
|
422
|
-
cells.push(value)
|
|
423
|
-
}
|
|
424
|
-
rows.push(cells)
|
|
425
|
-
}
|
|
426
|
-
return rows
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/** Excel column reference ("A", "B", ..., "AA", ...) to a zero-based index. */
|
|
430
|
-
function colRefToIndex(ref: string): number {
|
|
431
|
-
if (!ref) return 0
|
|
432
|
-
let n = 0
|
|
433
|
-
for (let i = 0; i < ref.length; i += 1) {
|
|
434
|
-
n = n * 26 + (ref.charCodeAt(i) - 64)
|
|
435
|
-
}
|
|
436
|
-
return Math.max(0, n - 1)
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// ---------------------------------------------------------------------------
|
|
440
|
-
// Coerce raw string values into typed JS values based on a sample of other
|
|
441
|
-
// values in the same column. We don't ask the consumer for a schema - it
|
|
442
|
-
// would defeat the "drop a file in and go" UX - but we DO want
|
|
443
|
-
// "$1,234.56" to become 1234.56 and "true" to become true.
|
|
444
|
-
// ---------------------------------------------------------------------------
|
|
445
|
-
|
|
446
|
-
function inferAndCoerce(value: string): unknown {
|
|
447
|
-
if (value === '') return ''
|
|
448
|
-
const trimmed = value.trim()
|
|
449
|
-
if (trimmed === '') return ''
|
|
450
|
-
// Boolean
|
|
451
|
-
if (trimmed === 'true' || trimmed === 'TRUE') return true
|
|
452
|
-
if (trimmed === 'false' || trimmed === 'FALSE') return false
|
|
453
|
-
// Currency / number with grouping. Strip $, commas, whitespace.
|
|
454
|
-
if (/^[-+]?\$?\s?[\d,]+(?:\.\d+)?$/.test(trimmed)) {
|
|
455
|
-
const n = parseFloat(trimmed.replace(/[$,\s]/g, ''))
|
|
456
|
-
if (!Number.isNaN(n)) return n
|
|
457
|
-
}
|
|
458
|
-
// Bare integer / float
|
|
459
|
-
if (/^[-+]?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(trimmed)) {
|
|
460
|
-
const n = parseFloat(trimmed)
|
|
461
|
-
if (!Number.isNaN(n)) return n
|
|
462
|
-
}
|
|
463
|
-
// ISO date (yyyy-mm-dd, optionally with time) -> keep as ISO string. We
|
|
464
|
-
// don't return a Date because most grid columns prefer strings on the
|
|
465
|
-
// wire (sort + filter + format paths are already date-aware).
|
|
466
|
-
if (/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:Z|[+-]\d{2}:?\d{2})?)?$/.test(trimmed)) {
|
|
467
|
-
return trimmed
|
|
468
|
-
}
|
|
469
|
-
return trimmed
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// ---------------------------------------------------------------------------
|
|
473
|
-
// Map source rows -> typed records.
|
|
474
|
-
// ---------------------------------------------------------------------------
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Field names we refuse to write, because assigning them from untrusted file
|
|
478
|
-
* headers would let a malicious upload walk the prototype chain (prototype
|
|
479
|
-
* pollution). Applies to the default snake_case fallback AND anything a
|
|
480
|
-
* columnMap points at.
|
|
481
|
-
*/
|
|
482
|
-
const UNSAFE_FIELDS = new Set(['__proto__', 'prototype', 'constructor'])
|
|
483
|
-
|
|
484
|
-
/** Resolve each source header to a target field (or null = drop the column),
|
|
485
|
-
* honouring the columnMap and refusing blank + pollution-prone names. */
|
|
486
|
-
function computeFields(
|
|
487
|
-
headers: string[],
|
|
488
|
-
columnMap: ImportColumnMap | undefined,
|
|
489
|
-
): Array<string | null> {
|
|
490
|
-
return headers.map((h) => {
|
|
491
|
-
let field: string | null
|
|
492
|
-
if (columnMap && Object.prototype.hasOwnProperty.call(columnMap, h)) {
|
|
493
|
-
field = columnMap[h] ?? null
|
|
494
|
-
} else {
|
|
495
|
-
field = h.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '')
|
|
496
|
-
}
|
|
497
|
-
if (field == null || field === '' || UNSAFE_FIELDS.has(field)) return null
|
|
498
|
-
return field
|
|
499
|
-
})
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/** Build one typed record from a row of cells. Pure; no side effects on the
|
|
503
|
-
* shared error list (returns its own). */
|
|
504
|
-
function buildRecord(
|
|
505
|
-
cells: string[],
|
|
506
|
-
headerCount: number,
|
|
507
|
-
fields: Array<string | null>,
|
|
508
|
-
columnTypes: ImportColumnTypes | undefined,
|
|
509
|
-
outRowIndex: number,
|
|
510
|
-
): { rec: Record<string, unknown>; errors: ImportRowError[] } {
|
|
511
|
-
const rec: Record<string, unknown> = {}
|
|
512
|
-
const errors: ImportRowError[] = []
|
|
513
|
-
for (let i = 0; i < headerCount; i += 1) {
|
|
514
|
-
const field = fields[i]
|
|
515
|
-
if (field == null) continue
|
|
516
|
-
const raw = cells[i] ?? ''
|
|
517
|
-
const declaredType = columnTypes?.[field]
|
|
518
|
-
if (declaredType) {
|
|
519
|
-
const result = coerceTyped(raw, declaredType)
|
|
520
|
-
if (result.ok) {
|
|
521
|
-
rec[field] = result.value
|
|
522
|
-
} else {
|
|
523
|
-
errors.push({ rowIndex: outRowIndex, field, message: result.message })
|
|
524
|
-
// Park the raw string so downstream code still sees SOMETHING.
|
|
525
|
-
rec[field] = raw
|
|
526
|
-
}
|
|
527
|
-
} else {
|
|
528
|
-
rec[field] = inferAndCoerce(raw)
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return { rec, errors }
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/** Push `source` errors into `target`, stopping at `maxErrors`. Returns true
|
|
535
|
-
* when it had to truncate. */
|
|
536
|
-
function collectErrors(
|
|
537
|
-
target: ImportRowError[],
|
|
538
|
-
source: ImportRowError[],
|
|
539
|
-
maxErrors: number | undefined,
|
|
540
|
-
): boolean {
|
|
541
|
-
for (const e of source) {
|
|
542
|
-
if (maxErrors != null && target.length >= maxErrors) return true
|
|
543
|
-
target.push(e)
|
|
544
|
-
}
|
|
545
|
-
return false
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/** Drop duplicate rows by `key`, keeping the last occurrence in place. */
|
|
549
|
-
function dedupeRows<TData>(rows: TData[], key: string): { rows: TData[]; deduped: number } {
|
|
550
|
-
const idx = new Map<unknown, number>()
|
|
551
|
-
const out: TData[] = []
|
|
552
|
-
for (const r of rows) {
|
|
553
|
-
const k = (r as Record<string, unknown>)?.[key]
|
|
554
|
-
if (k == null) { out.push(r); continue }
|
|
555
|
-
const at = idx.get(k)
|
|
556
|
-
if (at !== undefined) out[at] = r
|
|
557
|
-
else { idx.set(k, out.length); out.push(r) }
|
|
558
|
-
}
|
|
559
|
-
return { rows: out, deduped: rows.length - out.length }
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function isBlankRow(cells: string[]): boolean {
|
|
563
|
-
return cells.every((c) => c === undefined || c.trim() === '')
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
function abortError(): Error {
|
|
567
|
-
const e = new Error('@svgrid/enterprise: import cancelled')
|
|
568
|
-
e.name = 'AbortError'
|
|
569
|
-
return e
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/** Yield to the event loop so a long map doesn't block paint / input. */
|
|
573
|
-
function nextTick(): Promise<void> {
|
|
574
|
-
return new Promise((resolve) => setTimeout(resolve, 0))
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function buildRecords<TData>(
|
|
578
|
-
matrix: string[][],
|
|
579
|
-
columnMap: ImportColumnMap | undefined,
|
|
580
|
-
columnTypes: ImportColumnTypes | undefined,
|
|
581
|
-
maxErrors?: number,
|
|
582
|
-
): { headers: string[]; rows: TData[]; skipped: number; errors: ImportRowError[]; errorsTruncated: boolean } {
|
|
583
|
-
if (matrix.length === 0) return { headers: [], rows: [], skipped: 0, errors: [], errorsTruncated: false }
|
|
584
|
-
const headers = (matrix[0] ?? []).map((h) => h.trim())
|
|
585
|
-
const fields = computeFields(headers, columnMap)
|
|
586
|
-
const dataRows = matrix.slice(1)
|
|
587
|
-
const out: TData[] = []
|
|
588
|
-
const errors: ImportRowError[] = []
|
|
589
|
-
let skipped = 0
|
|
590
|
-
let errorsTruncated = false
|
|
591
|
-
// Skipped (blank) rows don't count toward the output row index used
|
|
592
|
-
// in errors, so the indices match what the consumer's preview UI shows.
|
|
593
|
-
let outRowIndex = 0
|
|
594
|
-
for (const cells of dataRows) {
|
|
595
|
-
if (isBlankRow(cells)) { skipped += 1; continue }
|
|
596
|
-
const { rec, errors: rowErrs } = buildRecord(cells, headers.length, fields, columnTypes, outRowIndex)
|
|
597
|
-
if (rowErrs.length && collectErrors(errors, rowErrs, maxErrors)) errorsTruncated = true
|
|
598
|
-
out.push(rec as unknown as TData)
|
|
599
|
-
outRowIndex += 1
|
|
600
|
-
}
|
|
601
|
-
return { headers, rows: out, skipped, errors, errorsTruncated }
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Strict per-type coercion used when the consumer declares
|
|
606
|
-
* `columnTypes`. Returns either `{ ok: true, value }` or
|
|
607
|
-
* `{ ok: false, message }` describing why the value couldn't be
|
|
608
|
-
* shaped into the declared type.
|
|
609
|
-
*
|
|
610
|
-
* Empty values for required types (number, date) error rather than
|
|
611
|
-
* silently coerce to 0 / null - the consumer can drop the row in
|
|
612
|
-
* their validator if blanks should be tolerated.
|
|
613
|
-
*/
|
|
614
|
-
function coerceTyped(raw: string, type: ImportFieldType):
|
|
615
|
-
| { ok: true; value: unknown }
|
|
616
|
-
| { ok: false; message: string }
|
|
617
|
-
{
|
|
618
|
-
const trimmed = raw.trim()
|
|
619
|
-
// Empty cells map to `null` for most types and the empty string for
|
|
620
|
-
// `string` - this is the only case where typed import returns a
|
|
621
|
-
// null. Validator step is the right place to insist on "required".
|
|
622
|
-
if (trimmed === '') {
|
|
623
|
-
if (type === 'string') return { ok: true, value: '' }
|
|
624
|
-
return { ok: true, value: null }
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
switch (type) {
|
|
628
|
-
case 'string':
|
|
629
|
-
return { ok: true, value: trimmed }
|
|
630
|
-
|
|
631
|
-
case 'boolean': {
|
|
632
|
-
const low = trimmed.toLowerCase()
|
|
633
|
-
if (low === 'true' || low === '1' || low === 'yes' || low === 'y') return { ok: true, value: true }
|
|
634
|
-
if (low === 'false' || low === '0' || low === 'no' || low === 'n') return { ok: true, value: false }
|
|
635
|
-
return { ok: false, message: `not a boolean: ${trimmed}` }
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
case 'number': {
|
|
639
|
-
const stripped = trimmed.replace(/[$,\s]/g, '')
|
|
640
|
-
const n = Number(stripped)
|
|
641
|
-
if (!Number.isFinite(n)) return { ok: false, message: `not a number: ${trimmed}` }
|
|
642
|
-
return { ok: true, value: n }
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
case 'integer': {
|
|
646
|
-
const stripped = trimmed.replace(/[$,\s]/g, '')
|
|
647
|
-
const n = Number(stripped)
|
|
648
|
-
if (!Number.isFinite(n) || !Number.isInteger(n)) {
|
|
649
|
-
return { ok: false, message: `not an integer: ${trimmed}` }
|
|
650
|
-
}
|
|
651
|
-
return { ok: true, value: n }
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
case 'date': {
|
|
655
|
-
// Accept ISO dates and a handful of common day-first / month-first
|
|
656
|
-
// shapes. Returns ISO yyyy-mm-dd.
|
|
657
|
-
const iso = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})/)
|
|
658
|
-
if (iso) return { ok: true, value: `${iso[1]}-${iso[2]}-${iso[3]}` }
|
|
659
|
-
// mm/dd/yyyy or dd/mm/yyyy - we don't try to disambiguate; the
|
|
660
|
-
// demo can declare the locale via its own validator if it cares.
|
|
661
|
-
const slash = trimmed.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})/)
|
|
662
|
-
if (slash) {
|
|
663
|
-
const [, a, b, y] = slash
|
|
664
|
-
// Default to mm/dd/yyyy because it's the dominant locale where
|
|
665
|
-
// Excel exports without an explicit ISO format. Consumers
|
|
666
|
-
// wanting EU-style can pre-normalise the file.
|
|
667
|
-
return { ok: true, value: `${y}-${a!.padStart(2, '0')}-${b!.padStart(2, '0')}` }
|
|
668
|
-
}
|
|
669
|
-
const parsed = Date.parse(trimmed)
|
|
670
|
-
if (!Number.isNaN(parsed)) {
|
|
671
|
-
return { ok: true, value: new Date(parsed).toISOString().slice(0, 10) }
|
|
672
|
-
}
|
|
673
|
-
return { ok: false, message: `not a date: ${trimmed}` }
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
case 'datetime': {
|
|
677
|
-
// Accept anything Date.parse handles.
|
|
678
|
-
const parsed = Date.parse(trimmed)
|
|
679
|
-
if (Number.isNaN(parsed)) return { ok: false, message: `not a datetime: ${trimmed}` }
|
|
680
|
-
return { ok: true, value: new Date(parsed).toISOString().slice(0, 19) }
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
case 'json': {
|
|
684
|
-
try {
|
|
685
|
-
return { ok: true, value: JSON.parse(trimmed) }
|
|
686
|
-
} catch {
|
|
687
|
-
return { ok: false, message: `not valid JSON: ${trimmed.slice(0, 30)}...` }
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// ---------------------------------------------------------------------------
|
|
694
|
-
// Parse-once + map, so a UI (SvImportDialog) can read a file ONCE and then
|
|
695
|
-
// re-map / re-validate as the user tweaks the column mapping without paying
|
|
696
|
-
// the parse (or unzip, for xlsx) cost on every keystroke.
|
|
697
|
-
// ---------------------------------------------------------------------------
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* Read a file (or inline text) into a raw `string[][]` matrix - header row
|
|
701
|
-
* first - without any mapping, coercion, or validation. The cheap half of
|
|
702
|
-
* `importData`: parse the bytes once, then feed the matrix to
|
|
703
|
-
* `mapImportMatrix` as many times as the mapping UI needs.
|
|
704
|
-
*/
|
|
705
|
-
export async function readImportMatrix(
|
|
706
|
-
file: File | Blob | string,
|
|
707
|
-
format: ImportFormat = 'auto',
|
|
708
|
-
opts: { maxBytes?: number; encoding?: string; signal?: AbortSignal } = {},
|
|
709
|
-
): Promise<{ format: Exclude<ImportFormat, 'auto'>; matrix: string[][] }> {
|
|
710
|
-
if (opts.signal?.aborted) throw abortError()
|
|
711
|
-
// Guard the byte size BEFORE reading, so an oversized upload never lands
|
|
712
|
-
// in memory.
|
|
713
|
-
if (opts.maxBytes != null && file instanceof Blob && file.size > opts.maxBytes) {
|
|
714
|
-
throw new Error(
|
|
715
|
-
`@svgrid/enterprise: file is ${file.size.toLocaleString()} bytes, ` +
|
|
716
|
-
`over the ${opts.maxBytes.toLocaleString()}-byte import limit.`,
|
|
717
|
-
)
|
|
718
|
-
}
|
|
719
|
-
const fmt = sniffFormat(file, format)
|
|
720
|
-
let matrix: string[][]
|
|
721
|
-
if (fmt === 'xlsx') {
|
|
722
|
-
if (typeof file === 'string') {
|
|
723
|
-
throw new Error(
|
|
724
|
-
'@svgrid/enterprise: xlsx import expects a File or Blob, not a string. ' +
|
|
725
|
-
'Use format: "csv" or "tsv" for inline text.',
|
|
726
|
-
)
|
|
727
|
-
}
|
|
728
|
-
matrix = await parseXlsx(file)
|
|
729
|
-
} else if (fmt === 'json') {
|
|
730
|
-
const text = typeof file === 'string' ? file : await readText(file, opts.encoding)
|
|
731
|
-
matrix = jsonToMatrix(text)
|
|
732
|
-
} else {
|
|
733
|
-
const sep = fmt === 'tsv' ? '\t' : ','
|
|
734
|
-
const text = typeof file === 'string' ? file : await readText(file, opts.encoding)
|
|
735
|
-
matrix = parseDelimited(text, sep)
|
|
736
|
-
}
|
|
737
|
-
if (opts.signal?.aborted) throw abortError()
|
|
738
|
-
return { format: fmt, matrix }
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
type MapMatrixOptions<TData> = {
|
|
742
|
-
columnMap?: ImportColumnMap
|
|
743
|
-
columnTypes?: ImportColumnTypes
|
|
744
|
-
validator?: ImportValidator<TData>
|
|
745
|
-
maxErrors?: number
|
|
746
|
-
dedupeBy?: keyof TData | (string & {})
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
type MapMatrixResult<TData> = {
|
|
750
|
-
headers: string[]
|
|
751
|
-
rows: TData[]
|
|
752
|
-
errors: ImportRowError[]
|
|
753
|
-
skipped: number
|
|
754
|
-
total: number
|
|
755
|
-
errorsTruncated: boolean
|
|
756
|
-
deduped: number
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/** Run the validator over built rows, capping + flagging truncation. */
|
|
760
|
-
function applyValidator<TData>(
|
|
761
|
-
rows: TData[],
|
|
762
|
-
errors: ImportRowError[],
|
|
763
|
-
validator: ImportValidator<TData> | undefined,
|
|
764
|
-
maxErrors: number | undefined,
|
|
765
|
-
startIndex = 0,
|
|
766
|
-
): boolean {
|
|
767
|
-
if (!validator) return false
|
|
768
|
-
let truncated = false
|
|
769
|
-
for (let i = startIndex; i < rows.length; i += 1) {
|
|
770
|
-
const errs = validator(rows[i]!, i)
|
|
771
|
-
if (errs.length) {
|
|
772
|
-
const mapped = errs.map((e) => ({ rowIndex: i, field: e.field, message: e.message }))
|
|
773
|
-
if (collectErrors(errors, mapped, maxErrors)) { truncated = true; break }
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
return truncated
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Apply a column map + declared types + validator to an already-parsed
|
|
781
|
-
* matrix. The pure, synchronous half of `importData` - no I/O, no license
|
|
782
|
-
* gate - so a preview UI can call it on every mapping change. For very large
|
|
783
|
-
* files prefer `mapImportMatrixAsync`, which yields to keep the UI live.
|
|
784
|
-
*/
|
|
785
|
-
export function mapImportMatrix<TData extends RowData>(
|
|
786
|
-
matrix: string[][],
|
|
787
|
-
opts: MapMatrixOptions<TData> = {},
|
|
788
|
-
): MapMatrixResult<TData> {
|
|
789
|
-
const built = buildRecords<TData>(matrix, opts.columnMap, opts.columnTypes, opts.maxErrors)
|
|
790
|
-
const errors = built.errors
|
|
791
|
-
// Type-coercion errors come first; the validator runs on the rows we
|
|
792
|
-
// managed to build, so its errors share the same `rowIndex` basis.
|
|
793
|
-
let errorsTruncated = built.errorsTruncated
|
|
794
|
-
if (applyValidator(built.rows, errors, opts.validator, opts.maxErrors)) errorsTruncated = true
|
|
795
|
-
let rows = built.rows
|
|
796
|
-
let deduped = 0
|
|
797
|
-
if (opts.dedupeBy) {
|
|
798
|
-
const r = dedupeRows(rows, opts.dedupeBy as string)
|
|
799
|
-
rows = r.rows
|
|
800
|
-
deduped = r.deduped
|
|
801
|
-
}
|
|
802
|
-
return {
|
|
803
|
-
headers: built.headers,
|
|
804
|
-
rows,
|
|
805
|
-
errors,
|
|
806
|
-
skipped: built.skipped,
|
|
807
|
-
total: matrix.length > 0 ? matrix.length - 1 : 0,
|
|
808
|
-
errorsTruncated,
|
|
809
|
-
deduped,
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* Non-blocking sibling of `mapImportMatrix`: yields to the event loop every
|
|
815
|
-
* few thousand rows and reports progress, so mapping a 100k-row file never
|
|
816
|
-
* freezes the tab. Honors an AbortSignal. Used by `importData` and the
|
|
817
|
-
* SvImportDialog preview.
|
|
818
|
-
*/
|
|
819
|
-
export async function mapImportMatrixAsync<TData extends RowData>(
|
|
820
|
-
matrix: string[][],
|
|
821
|
-
opts: MapMatrixOptions<TData> & {
|
|
822
|
-
signal?: AbortSignal
|
|
823
|
-
onProgress?: (progress: ImportProgress) => void
|
|
824
|
-
/** Rows per chunk before yielding. Default 5000. */
|
|
825
|
-
chunkSize?: number
|
|
826
|
-
} = {},
|
|
827
|
-
): Promise<MapMatrixResult<TData>> {
|
|
828
|
-
const headers = (matrix[0] ?? []).map((h) => h.trim())
|
|
829
|
-
const fields = computeFields(headers, opts.columnMap)
|
|
830
|
-
const dataRows = matrix.slice(1)
|
|
831
|
-
const total = dataRows.length
|
|
832
|
-
const chunk = Math.max(1, opts.chunkSize ?? 5000)
|
|
833
|
-
const rows: TData[] = []
|
|
834
|
-
const errors: ImportRowError[] = []
|
|
835
|
-
let skipped = 0
|
|
836
|
-
let outRowIndex = 0
|
|
837
|
-
let errorsTruncated = false
|
|
838
|
-
|
|
839
|
-
for (let i = 0; i < total; i += 1) {
|
|
840
|
-
if (opts.signal?.aborted) throw abortError()
|
|
841
|
-
const cells = dataRows[i]!
|
|
842
|
-
if (isBlankRow(cells)) { skipped += 1 } else {
|
|
843
|
-
const { rec, errors: rowErrs } = buildRecord(cells, headers.length, fields, opts.columnTypes, outRowIndex)
|
|
844
|
-
if (rowErrs.length && collectErrors(errors, rowErrs, opts.maxErrors)) errorsTruncated = true
|
|
845
|
-
rows.push(rec as unknown as TData)
|
|
846
|
-
outRowIndex += 1
|
|
847
|
-
}
|
|
848
|
-
if ((i + 1) % chunk === 0) {
|
|
849
|
-
opts.onProgress?.({ phase: 'map', ratio: total ? (i + 1) / total : 1, done: i + 1, total })
|
|
850
|
-
await nextTick()
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
if (applyValidator(rows, errors, opts.validator, opts.maxErrors)) errorsTruncated = true
|
|
855
|
-
|
|
856
|
-
let finalRows = rows
|
|
857
|
-
let deduped = 0
|
|
858
|
-
if (opts.dedupeBy) {
|
|
859
|
-
const r = dedupeRows(rows, opts.dedupeBy as string)
|
|
860
|
-
finalRows = r.rows
|
|
861
|
-
deduped = r.deduped
|
|
862
|
-
}
|
|
863
|
-
opts.onProgress?.({ phase: 'map', ratio: 1, done: total, total })
|
|
864
|
-
return {
|
|
865
|
-
headers,
|
|
866
|
-
rows: finalRows,
|
|
867
|
-
errors,
|
|
868
|
-
skipped,
|
|
869
|
-
total: matrix.length > 0 ? matrix.length - 1 : 0,
|
|
870
|
-
errorsTruncated,
|
|
871
|
-
deduped,
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
/** Fold a header / field down to letters + digits so "Unit Price", "unit_price"
|
|
876
|
-
* and "UnitPrice" all collapse to the same key. */
|
|
877
|
-
function normalizeHeader(s: string): string {
|
|
878
|
-
return s.toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Fuzzy-match each source header to one of the grid's columns (by header
|
|
883
|
-
* label first, then field name) and return a `source header -> target
|
|
884
|
-
* field` map. Headers with no confident match are left OUT of the map, so
|
|
885
|
-
* `buildRecords` falls back to its snake_case default for them.
|
|
886
|
-
*/
|
|
887
|
-
export function autoMapColumns(
|
|
888
|
-
headers: string[],
|
|
889
|
-
columns: ReadonlyArray<ImportGridColumn>,
|
|
890
|
-
): ImportColumnMap {
|
|
891
|
-
const byKey = new Map<string, string>()
|
|
892
|
-
for (const c of columns) {
|
|
893
|
-
const field = c.field ?? c.header
|
|
894
|
-
if (!field) continue
|
|
895
|
-
// Field name is the strongest signal, so register header first then let
|
|
896
|
-
// the field key overwrite on a collision.
|
|
897
|
-
if (c.header) byKey.set(normalizeHeader(c.header), field)
|
|
898
|
-
if (c.field) byKey.set(normalizeHeader(c.field), c.field)
|
|
899
|
-
}
|
|
900
|
-
const map: ImportColumnMap = {}
|
|
901
|
-
for (const h of headers) {
|
|
902
|
-
const hit = byKey.get(normalizeHeader(h))
|
|
903
|
-
if (hit) map[h] = hit
|
|
904
|
-
}
|
|
905
|
-
return map
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/**
|
|
909
|
-
* Infer strict import types from each grid column's `format`, so a currency
|
|
910
|
-
* / number / percent column parses "$1,234" -> 1234 and a date column parses
|
|
911
|
-
* "2026-03-04" -> an ISO date. Columns with no informative format are left
|
|
912
|
-
* out (best-effort coercion applies).
|
|
913
|
-
*/
|
|
914
|
-
export function inferImportColumnTypes(
|
|
915
|
-
columns: ReadonlyArray<ImportGridColumn>,
|
|
916
|
-
): ImportColumnTypes {
|
|
917
|
-
const types: ImportColumnTypes = {}
|
|
918
|
-
for (const c of columns) {
|
|
919
|
-
if (!c.field) continue
|
|
920
|
-
const t = c.format?.type
|
|
921
|
-
if (t === 'number' || t === 'currency' || t === 'percent') types[c.field] = 'number'
|
|
922
|
-
else if (t === 'date') types[c.field] = 'date'
|
|
923
|
-
else if (t === 'datetime') types[c.field] = 'datetime'
|
|
924
|
-
}
|
|
925
|
-
return types
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// ---------------------------------------------------------------------------
|
|
929
|
-
// Public entry point
|
|
930
|
-
// ---------------------------------------------------------------------------
|
|
931
|
-
|
|
932
|
-
/**
|
|
933
|
-
* Read a file (or inline text), parse, optionally validate + commit. The
|
|
934
|
-
* sister function of `exportGrid`.
|
|
935
|
-
*/
|
|
936
|
-
export async function importData<
|
|
937
|
-
TFeatures extends TableFeatures,
|
|
938
|
-
TData extends RowData,
|
|
939
|
-
>(
|
|
940
|
-
api: SvGridApi<TFeatures, TData>,
|
|
941
|
-
opts: ImportOptions<TData>,
|
|
942
|
-
): Promise<ImportResult<TData>> {
|
|
943
|
-
assertEnterpriseLicensed('Import')
|
|
944
|
-
const { format, matrix } = await readImportMatrix(opts.file, opts.format ?? 'auto', {
|
|
945
|
-
maxBytes: opts.maxBytes,
|
|
946
|
-
encoding: opts.encoding,
|
|
947
|
-
signal: opts.signal,
|
|
948
|
-
})
|
|
949
|
-
|
|
950
|
-
// Enforce the row cap before we do the O(rows) mapping work: either reject
|
|
951
|
-
// outright, or truncate to the first maxRows and flag it.
|
|
952
|
-
const dataCount = matrix.length > 0 ? matrix.length - 1 : 0
|
|
953
|
-
let workMatrix = matrix
|
|
954
|
-
let truncated = false
|
|
955
|
-
if (opts.maxRows != null && dataCount > opts.maxRows) {
|
|
956
|
-
if (opts.overLimit === 'truncate') {
|
|
957
|
-
workMatrix = matrix.slice(0, opts.maxRows + 1) // header + first maxRows
|
|
958
|
-
truncated = true
|
|
959
|
-
} else {
|
|
960
|
-
throw new Error(
|
|
961
|
-
`@svgrid/enterprise: import has ${dataCount.toLocaleString()} rows, ` +
|
|
962
|
-
`over the maxRows limit of ${opts.maxRows.toLocaleString()}.`,
|
|
963
|
-
)
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// Grid-aware auto-mapping: derive the columnMap + columnTypes from the
|
|
968
|
-
// grid's own columns, but let anything the caller passed win.
|
|
969
|
-
let columnMap = opts.columnMap
|
|
970
|
-
let columnTypes = opts.columnTypes
|
|
971
|
-
if (opts.autoMap) {
|
|
972
|
-
const gridCols = api.getColumns() as ReadonlyArray<ImportGridColumn>
|
|
973
|
-
const headers = (workMatrix[0] ?? []).map((h) => h.trim())
|
|
974
|
-
columnMap = { ...autoMapColumns(headers, gridCols), ...(opts.columnMap ?? {}) }
|
|
975
|
-
columnTypes = { ...inferImportColumnTypes(gridCols), ...(opts.columnTypes ?? {}) }
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const { headers, rows, errors, skipped, errorsTruncated, deduped } =
|
|
979
|
-
await mapImportMatrixAsync<TData>(workMatrix, {
|
|
980
|
-
columnMap,
|
|
981
|
-
columnTypes,
|
|
982
|
-
validator: opts.validator,
|
|
983
|
-
maxErrors: opts.maxErrors,
|
|
984
|
-
dedupeBy: opts.dedupeBy,
|
|
985
|
-
signal: opts.signal,
|
|
986
|
-
onProgress: opts.onProgress,
|
|
987
|
-
})
|
|
988
|
-
|
|
989
|
-
if (opts.commit && errors.length === 0 && rows.length > 0) {
|
|
990
|
-
api.addRows(rows, opts.commitAt ?? 'bottom')
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// `total` reports the ORIGINAL source row count even when truncated, so the
|
|
994
|
-
// caller can say "imported X of Y".
|
|
995
|
-
return { headers, rows, errors, skipped, total: dataCount, format, errorsTruncated, deduped, truncated }
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// ---------------------------------------------------------------------------
|
|
999
|
-
// Helpers
|
|
1000
|
-
// ---------------------------------------------------------------------------
|
|
1001
|
-
|
|
1002
|
-
async function readText(blob: Blob, encoding?: string): Promise<string> {
|
|
1003
|
-
// The default UTF-8 path is fastest; a declared non-UTF-8 encoding (legacy
|
|
1004
|
-
// Windows-1252 CSVs, etc.) routes through TextDecoder.
|
|
1005
|
-
if (encoding && encoding.toLowerCase() !== 'utf-8' && encoding.toLowerCase() !== 'utf8') {
|
|
1006
|
-
const buf = await blob.arrayBuffer()
|
|
1007
|
-
return new TextDecoder(encoding).decode(buf)
|
|
1008
|
-
}
|
|
1009
|
-
return blob.text()
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
/** Convert JSON text containing an array of objects into a `string[][]`
|
|
1013
|
-
* matrix shaped like CSV: header row, then one row per record. */
|
|
1014
|
-
function jsonToMatrix(text: string): string[][] {
|
|
1015
|
-
const parsed = JSON.parse(text)
|
|
1016
|
-
if (!Array.isArray(parsed)) {
|
|
1017
|
-
throw new Error('@svgrid/enterprise: JSON import expects a top-level array')
|
|
1018
|
-
}
|
|
1019
|
-
if (parsed.length === 0) return []
|
|
1020
|
-
// Collect the union of keys across the first ~50 records so we don't
|
|
1021
|
-
// miss a column present only on later rows.
|
|
1022
|
-
const headers: string[] = []
|
|
1023
|
-
const seen = new Set<string>()
|
|
1024
|
-
const sample = parsed.slice(0, 50) as Record<string, unknown>[]
|
|
1025
|
-
for (const r of sample) {
|
|
1026
|
-
if (r && typeof r === 'object') {
|
|
1027
|
-
for (const k of Object.keys(r)) {
|
|
1028
|
-
if (!seen.has(k)) { seen.add(k); headers.push(k) }
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
const rows: string[][] = [headers.slice()]
|
|
1033
|
-
for (const r of parsed as Array<Record<string, unknown>>) {
|
|
1034
|
-
const row: string[] = []
|
|
1035
|
-
for (const h of headers) {
|
|
1036
|
-
const v = r?.[h]
|
|
1037
|
-
if (v == null) row.push('')
|
|
1038
|
-
else if (typeof v === 'object') row.push(JSON.stringify(v))
|
|
1039
|
-
else row.push(String(v))
|
|
1040
|
-
}
|
|
1041
|
-
rows.push(row)
|
|
1042
|
-
}
|
|
1043
|
-
return rows
|
|
1044
|
-
}
|