@svgrid/enterprise 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -81
- package/dist/cdn/svgrid-enterprise.svelte-external.js +26642 -11252
- package/dist/designer/assets/GridMenus-BtWVk9Ab.js +7 -0
- package/dist/designer/assets/SvGridChartPanel-DWlBO2SD.js +10 -0
- package/dist/designer/assets/SvGridChartView-8c8Mb4j8.js +1 -0
- package/dist/designer/assets/SvGridChartView-DX9HfkBR.css +1 -0
- package/dist/designer/assets/index-DsDgp9Xq.js +78758 -0
- package/dist/designer/assets/index-tTY_Dx4P.css +1 -0
- package/dist/designer/assets/jszip.min-fkJdmAmj.js +2 -0
- package/dist/designer/assets/pdfmake-DeCsnyl9.js +242 -0
- package/dist/designer/assets/smart.export-BZlSCE8T.js +35 -0
- package/dist/designer/assets/vfs_fonts-eX2NpmfX.js +1 -0
- package/dist/designer/index.html +13 -0
- package/dist/node/studio.js +15304 -2708
- package/package.json +35 -11
- package/src/SvAlertRuleEditor.svelte +294 -0
- package/src/SvAlertsManager.svelte +210 -0
- package/src/SvAlertsPanel.svelte +129 -0
- package/src/SvBoard.svelte +3 -1
- package/src/SvExpressionEditor.svelte +341 -0
- package/src/SvGridAlerts.dom.test.ts +113 -0
- package/src/SvGridAlerts.svelte +265 -0
- package/src/SvGridBoard.svelte +2358 -0
- package/src/SvGridEditPanel.svelte +849 -794
- package/src/SvGridScheduler.svelte +5358 -4410
- package/src/SvPivotDesigner.svelte +3 -3
- package/src/SvRecordDetail.svelte +6 -2
- package/src/SvSchedule.svelte +3 -1
- package/src/{ai-export-pdf.test.ts → ai-export-pdf.dom.test.ts} +77 -74
- package/src/{ai-export-xlsx.test.ts → ai-export-xlsx.dom.test.ts} +90 -87
- package/src/{ai-export.test.ts → ai-export.dom.test.ts} +114 -110
- package/src/alerts/alert-engine-attach.ts +165 -0
- package/src/alerts/alert-engine.test.ts +135 -0
- package/src/alerts/alert-engine.ts +260 -0
- package/src/alerts/alert-formats.test.ts +87 -0
- package/src/alerts/alert-formats.ts +77 -0
- package/src/alerts/alert-observer.test.ts +189 -0
- package/src/alerts/alert-observer.ts +208 -0
- package/src/alerts/alert-scheduler.ts +96 -0
- package/src/alerts/alert-storage.test.ts +54 -0
- package/src/alerts/alert-storage.ts +116 -0
- package/src/alerts/alert-store.svelte.ts +80 -0
- package/src/alerts/alert-types.ts +94 -0
- package/src/alerts.ts +28 -0
- package/src/board.dom.test.ts +941 -0
- package/src/board.ts +36 -0
- package/src/export-ooxml.ts +4 -0
- package/src/export-xls.ts +3 -0
- package/src/export.ts +6 -0
- package/src/expressions/evaluate.test.ts +111 -0
- package/src/expressions/evaluate.ts +237 -0
- package/src/expressions/expression-columns.ts +133 -0
- package/src/expressions/expression-types.ts +84 -0
- package/src/expressions/parse.test.ts +106 -0
- package/src/expressions/parse.ts +610 -0
- package/src/import.test.ts +1 -1
- package/src/import.ts +3 -1
- package/src/index.ts +192 -46
- package/src/install.ts +12 -2
- package/src/pivot-enable.ts +44 -0
- package/src/pivot.test.ts +0 -1
- package/src/scheduler-assignments.test.ts +97 -0
- package/src/scheduler-assignments.ts +134 -0
- package/src/scheduler-axis.test.ts +108 -0
- package/src/scheduler-axis.ts +238 -0
- package/src/scheduler-booking.test.ts +57 -0
- package/src/scheduler-booking.ts +63 -0
- package/src/scheduler-config.ts +179 -0
- package/src/scheduler-dependencies.test.ts +155 -0
- package/src/scheduler-dependencies.ts +223 -0
- package/src/scheduler-freebusy.test.ts +41 -0
- package/src/scheduler-freebusy.ts +36 -0
- package/src/scheduler-heatmap.test.ts +39 -0
- package/src/scheduler-heatmap.ts +55 -0
- package/src/scheduler-resource-tree.test.ts +84 -0
- package/src/scheduler-resource-tree.ts +107 -0
- package/src/scheduler-slots.test.ts +53 -0
- package/src/scheduler-slots.ts +94 -0
- package/src/scheduler-summary.test.ts +47 -0
- package/src/scheduler-summary.ts +81 -0
- package/src/schema-designer.ts +1 -1
- package/src/sources/index.ts +1 -1
- package/src/sources/introspect-supabase.test.ts +13 -1
- package/src/sources/introspect-supabase.ts +20 -0
- package/src/studio/copilot-core.test.ts +45 -0
- package/src/studio/copilot-core.ts +65 -0
- package/src/studio/deploy-cli.test.ts +56 -0
- package/src/studio/deploy-cli.ts +100 -0
- package/src/studio/emit-project.test.ts +800 -24
- package/src/studio/emit-project.ts +1307 -135
- package/src/studio/emit-schema.test.ts +17 -0
- package/src/studio/emit-schema.ts +182 -41
- package/src/studio/index.ts +59 -1
- package/src/studio/init-flow.test.ts +239 -0
- package/src/studio/init-flow.ts +358 -0
- package/src/studio/introspect-openapi.test.ts +84 -0
- package/src/studio/introspect-openapi.ts +252 -0
- package/src/studio/project.test.ts +57 -1
- package/src/studio/project.ts +302 -8
- package/src/studio/samples/crm.ts +282 -258
- package/src/studio/samples/datasets.test.ts +84 -0
- package/src/studio/samples/datasets.ts +340 -0
- package/src/studio/samples/fleet.ts +213 -186
- package/src/studio/samples/insurance.ts +223 -195
- package/src/studio/samples/inventory.ts +213 -182
- package/src/studio/samples/live-data.test.ts +99 -98
- package/src/studio/samples/live-data.ts +8 -10
- package/src/studio/samples/projects.ts +212 -190
- package/src/studio/samples/samples.test.ts +268 -216
- package/src/studio/samples/shared.ts +22 -145
- package/src/studio/samples/starter.ts +251 -0
- package/src/studio/samples/support.ts +209 -184
- package/src/studio/screen-suites.test.ts +226 -0
- package/src/studio/screen-suites.ts +445 -0
- package/src/studio/ui-components-surface.test.ts +185 -0
- package/src/studio/ui-components.generated.ts +7154 -0
- package/src/studio/ui-components.ts +273 -3
- package/src/sveltekit/index.ts +1 -0
- package/src/sveltekit/sql-source.test.ts +13 -0
- package/src/sveltekit/sql-source.ts +12 -7
- package/src/sveltekit/transport-scope.test.ts +125 -0
- package/src/sveltekit/transport.ts +76 -2
- package/src/upgrade-prompt.ts +2 -2
- package/src/watermark.ts +2 -2
- package/src/ai.test.ts +0 -522
- package/src/ai.ts +0 -1388
|
@@ -91,4 +91,21 @@ describe('emitStudioApp', () => {
|
|
|
91
91
|
it('throws on an empty entity set', () => {
|
|
92
92
|
expect(() => emitStudioApp([])).toThrow(/no schemas/)
|
|
93
93
|
})
|
|
94
|
+
|
|
95
|
+
it('types a chips/tags field as string[] (it holds + seeds an array)', () => {
|
|
96
|
+
const files = emitStudioApp([
|
|
97
|
+
{
|
|
98
|
+
name: 'contacts',
|
|
99
|
+
label: 'Contact',
|
|
100
|
+
idField: 'id',
|
|
101
|
+
fields: [
|
|
102
|
+
{ field: 'id', type: 'text', primaryKey: true },
|
|
103
|
+
{ field: 'tags', type: 'text', input: { editorType: 'chips' } },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
])
|
|
107
|
+
const schemas = files.find((f) => f.path === 'src/lib/schemas.ts')!.contents
|
|
108
|
+
expect(schemas).toMatch(/tags: string\[\]/)
|
|
109
|
+
expect(schemas).not.toMatch(/tags: string(?!\[)/) // not the scalar `string`
|
|
110
|
+
})
|
|
94
111
|
})
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* build. It targets the template's conventions: `$lib/EntityScreen.svelte`,
|
|
11
11
|
* `$lib/schemas`, `$lib/data`, and the `st-*` / `.home` CSS classes.
|
|
12
12
|
*/
|
|
13
|
-
import { resolveIdField, titleCase, type EntityField, type
|
|
13
|
+
import { resolveIdField, titleCase, type EntityField, type EntitySchema, type ValidationRuleSpec } from '../schema.js'
|
|
14
14
|
import type { GeneratedFile } from './scaffold.js'
|
|
15
|
-
import type { EntityDataSource, RestSource, ShellConfig, ShellStyle, SqlDialectKind, EntityTriggers, TriggerEvent } from './project.js'
|
|
15
|
+
import type { EntityDataSource, RestAdapterConfig, RestSource, ShellConfig, ShellStyle, SqlDialectKind, SqlSource, EntityTriggers, TriggerEvent } from './project.js'
|
|
16
16
|
import { sanitizeClassName, compileTriggerSteps, TRIGGER_EVENTS } from './project.js'
|
|
17
17
|
|
|
18
18
|
/** Build the createKitHandlers `hooks: {...}` option from an entity's triggers.
|
|
@@ -48,7 +48,11 @@ const camel = (name: string): string => {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** Entity field type -> a TS property type for the generated row type. */
|
|
51
|
-
function tsType(
|
|
51
|
+
function tsType(field: EntityField): string {
|
|
52
|
+
// A chips/tags editor stores multiple values (string[]) - its seed rows + form
|
|
53
|
+
// value are arrays, so the row type must be string[] even on a `text` field.
|
|
54
|
+
if (field.input?.editorType === 'chips') return 'string[]'
|
|
55
|
+
const type = field.type
|
|
52
56
|
if (type === 'number') return 'number'
|
|
53
57
|
if (type === 'boolean') return 'boolean'
|
|
54
58
|
if (type === 'json') return 'unknown'
|
|
@@ -82,8 +86,10 @@ export function namesFor(schema: EntitySchema): Names {
|
|
|
82
86
|
export const lookupVar = (schema: EntitySchema, field: string) => `${camel(schema.name)}${pascal(field)}Lookup`
|
|
83
87
|
|
|
84
88
|
function rowType(schema: EntitySchema, derived: Set<string>): string {
|
|
85
|
-
//
|
|
86
|
-
|
|
89
|
+
// Runtime-filled columns are optional in the row type: relation labels (`derived`)
|
|
90
|
+
// and computed / no-code-formula fields (not present on seed rows / inserts).
|
|
91
|
+
const optional = (f: EntityField) => derived.has(f.field) || !!f.formula || !!f.computed
|
|
92
|
+
const lines = schema.fields.map((f) => ` ${f.field}${optional(f) ? '?' : ''}: ${tsType(f)}`)
|
|
87
93
|
return `export type ${pascal(schema.name)} = {\n${lines.join('\n')}\n}`
|
|
88
94
|
}
|
|
89
95
|
|
|
@@ -293,6 +299,23 @@ function sqlDialectExpr(dialect?: SqlDialectKind): string | null {
|
|
|
293
299
|
|
|
294
300
|
const sq = (s: string): string => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
|
|
295
301
|
|
|
302
|
+
/** The generator name for a REST adapter kind. */
|
|
303
|
+
export function restAdapterImport(a: RestAdapterConfig): string {
|
|
304
|
+
return a.kind === 'dummyjson' ? 'dummyJsonAdapter' : a.kind === 'jsonServer' ? 'jsonServerAdapter' : 'offsetLimitAdapter'
|
|
305
|
+
}
|
|
306
|
+
/** The adapter spread emitted into the createRestDataSource(...) call - it supplies
|
|
307
|
+
* a `buildQuery` (real server paging/sort) + `parse` (row/total unwrap). */
|
|
308
|
+
function restAdapterExpr(a: RestAdapterConfig, T: string): string {
|
|
309
|
+
if (a.kind === 'dummyjson') return `...dummyJsonAdapter<${T}>(${a.rowsKey ? sq(a.rowsKey) : ''})`
|
|
310
|
+
if (a.kind === 'jsonServer') return `...jsonServerAdapter<${T}>()`
|
|
311
|
+
const fields: string[] = []
|
|
312
|
+
for (const k of ['offsetParam', 'limitParam', 'sortByParam', 'orderParam', 'searchParam', 'rowsKey', 'totalKey'] as const) {
|
|
313
|
+
const v = a[k]
|
|
314
|
+
if (v) fields.push(`${k}: ${sq(v)}`)
|
|
315
|
+
}
|
|
316
|
+
return `...offsetLimitAdapter<${T}>(${fields.length ? `{ ${fields.join(', ')} }` : ''})`
|
|
317
|
+
}
|
|
318
|
+
|
|
296
319
|
/** The `createRestDataSource(...)` call for one entity's REST binding. */
|
|
297
320
|
function restStoreExpr(src: RestSource, storeName: string, T: string, schemaVar: string): string {
|
|
298
321
|
const opts: string[] = [`url: ${sq(buildRestUrl(src))}`, `schema: ${schemaVar}`]
|
|
@@ -301,7 +324,11 @@ function restStoreExpr(src: RestSource, storeName: string, T: string, schemaVar:
|
|
|
301
324
|
if (headers.length) opts.push(`headers: { ${headers.map((h) => `${sq(h.name)}: ${sq(h.value ?? '')}`).join(', ')} }`)
|
|
302
325
|
const query = src.params.filter((p) => p.location === 'query' && p.value !== undefined && p.value !== '')
|
|
303
326
|
if (query.length) opts.push(`query: { ${query.map((q) => `${sq(q.name)}: ${sq(q.value ?? '')}`).join(', ')} }`)
|
|
304
|
-
|
|
327
|
+
// An adapter provides both buildQuery (server paging/sort) + parse, so it wins over
|
|
328
|
+
// the legacy manual rowsPath/totalPath (which only unwraps a single fetched page).
|
|
329
|
+
if (src.adapter) {
|
|
330
|
+
opts.push(restAdapterExpr(src.adapter, T))
|
|
331
|
+
} else if (src.rowsPath || src.totalPath) {
|
|
305
332
|
const rowsExpr = src.rowsPath ? dottedAccess('body', src.rowsPath) : 'body'
|
|
306
333
|
const totalExpr = src.totalPath ? dottedAccess('body', src.totalPath) : `(${rowsExpr})?.length`
|
|
307
334
|
opts.push(`parse: (body: any) => ({ rows: ${rowsExpr} ?? [], rowCount: Number(${totalExpr} ?? 0) })`)
|
|
@@ -314,7 +341,7 @@ type DataModuleNeeds = {
|
|
|
314
341
|
supabaseUrl?: string
|
|
315
342
|
supabaseKey?: string
|
|
316
343
|
/** SQL-bound entities get a connected `+server.ts` API route each. */
|
|
317
|
-
sqlRoutes: Array<{ schema: EntitySchema; table: string; dialect?: SqlDialectKind }>
|
|
344
|
+
sqlRoutes: Array<{ schema: EntitySchema; table: string; dialect?: SqlDialectKind; dbSchema?: string }>
|
|
318
345
|
/** Embedded-Postgres (PGlite) entities: one shared client + a table each. */
|
|
319
346
|
pglite?: boolean
|
|
320
347
|
pgliteTables?: Array<{ schema: EntitySchema; table: string; seed?: Record<string, unknown>[] }>
|
|
@@ -335,17 +362,19 @@ const SQL_DRIVERS: Record<'postgres' | 'mysql' | 'mssql' | 'sqlite' | 'turso', {
|
|
|
335
362
|
* the route imports the shared access policy and rejects unauthorized writes -
|
|
336
363
|
* server-enforced, so a tampered client can't bypass it. When audit is on, every
|
|
337
364
|
* successful write is recorded. */
|
|
338
|
-
function sqlRouteFile(schema: EntitySchema, table: string, dialect?: SqlDialectKind, feat: { access?: boolean; audit?: boolean; screenIds?: string[]; triggers?: EntityTriggers } = {}): GeneratedFile {
|
|
365
|
+
function sqlRouteFile(schema: EntitySchema, table: string, dialect?: SqlDialectKind, feat: { access?: boolean; audit?: boolean; screenIds?: string[]; triggers?: EntityTriggers; dbSchema?: string; tenantField?: string } = {}): GeneratedFile {
|
|
339
366
|
const n = namesFor(schema)
|
|
340
367
|
const key = (dialect === 'supabase' ? 'postgres' : (dialect ?? 'postgres')) as 'postgres' | 'mysql' | 'mssql' | 'sqlite' | 'turso'
|
|
341
368
|
const driver = SQL_DRIVERS[key]
|
|
342
369
|
const dialectLiteral = sqlDialectExpr(dialect)
|
|
343
370
|
const accessImport = feat.access ? `\nimport { authorizeAction, getServerRole } from '$lib/access'` : ''
|
|
344
371
|
const auditImport = feat.audit ? `\nimport { recordAudit } from '$lib/audit'` : ''
|
|
372
|
+
const tenantImport = feat.tenantField ? `\nimport { requireTenant } from '$lib/server/tenant'` : ''
|
|
345
373
|
// Every connected route validates writes against the schema server-side, and
|
|
346
374
|
// (when enabled) authorizes them by role + records an audit entry.
|
|
347
375
|
const opts = [`schema: ${n.schemaVar}`, `source`, `validate: true`]
|
|
348
376
|
if (feat.access) opts.push(`// Server-enforced RBAC: the caller's role comes from the session (event.locals). Reads\n // are allowed only if the role can open one of this entity's own screens.\n authorize: ({ action, event }) => authorizeAction(getServerRole(event), action, ${JSON.stringify(feat.screenIds ?? [])})`)
|
|
377
|
+
if (feat.tenantField) opts.push(`// Multi-tenancy, enforced server-side: reads are filtered to the caller's tenant,\n // creates are stamped with it, and update/delete re-read the target under the\n // scope first - so a guessed id cannot reach another tenant's row.\n scope: ({ event }) => ({ field: ${JSON.stringify(feat.tenantField)}, value: requireTenant(event) })`)
|
|
349
378
|
if (feat.audit) opts.push(`// Record every successful write to the audit trail.\n audit: (e) => recordAudit({ entity: ${JSON.stringify(schema.name)}, action: e.action, recordId: e.id, values: e.values as Record<string, unknown> | undefined, actor: String(e.event.locals?.role ?? e.event.locals?.user ?? 'system') })`)
|
|
350
379
|
// Server-enforced business rules: the entity's triggers compiled to lifecycle hooks.
|
|
351
380
|
const hooks = triggerHooksOpt(feat.triggers)
|
|
@@ -357,13 +386,13 @@ function sqlRouteFile(schema: EntitySchema, table: string, dialect?: SqlDialectK
|
|
|
357
386
|
contents: `${driver.imports}
|
|
358
387
|
import { env } from '$env/dynamic/private'
|
|
359
388
|
import { createKitHandlers, createSqlDataSource } from '@svgrid/enterprise'
|
|
360
|
-
import { ${n.schemaVar}, type ${n.type} } from '$lib/schemas'${accessImport}${auditImport}
|
|
389
|
+
import { ${n.schemaVar}, type ${n.type} } from '$lib/schemas'${accessImport}${auditImport}${tenantImport}
|
|
361
390
|
|
|
362
391
|
${driver.setup}
|
|
363
392
|
|
|
364
393
|
const source = createSqlDataSource<${n.type}>({
|
|
365
394
|
schema: ${n.schemaVar},
|
|
366
|
-
table: ${JSON.stringify(table)},${dialectLiteral ? `\n dialect: ${dialectLiteral},` : ''}
|
|
395
|
+
table: ${JSON.stringify(table)},${feat.dbSchema ? `\n dbSchema: ${JSON.stringify(feat.dbSchema)},` : ''}${dialectLiteral ? `\n dialect: ${dialectLiteral},` : ''}
|
|
367
396
|
execute: async (text, params) => {
|
|
368
397
|
${driver.exec}
|
|
369
398
|
},
|
|
@@ -418,10 +447,89 @@ async function _pgSeed(table: string, rows: Record<string, unknown>[], cols: str
|
|
|
418
447
|
`
|
|
419
448
|
}
|
|
420
449
|
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
// db/schema.sql + db/seed.sql - plain SQL a user runs against their database
|
|
452
|
+
// before first deploy, for SQL entities WITHOUT the Drizzle data layer (which
|
|
453
|
+
// owns DDL via migrations when active). This closes the "tables must already
|
|
454
|
+
// exist" cliff on every dialect - including MSSQL, which Drizzle can't cover.
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
type DdlDialect = 'postgres' | 'mysql' | 'sqlite' | 'mssql'
|
|
458
|
+
|
|
459
|
+
/** Normalize a source dialect to a DDL family (supabase = postgres, turso = sqlite). */
|
|
460
|
+
const ddlDialect = (d?: SqlDialectKind): DdlDialect =>
|
|
461
|
+
d === 'mysql' ? 'mysql' : d === 'sqlite' || d === 'turso' ? 'sqlite' : d === 'mssql' ? 'mssql' : 'postgres'
|
|
462
|
+
|
|
463
|
+
/** Column types per dialect (default column type = the `text` entry). */
|
|
464
|
+
const DDL_COL_TYPES: Record<DdlDialect, Record<string, string>> = {
|
|
465
|
+
postgres: { text: 'text', number: 'double precision', boolean: 'boolean', date: 'date', dateString: 'date', datetime: 'timestamptz', json: 'jsonb' },
|
|
466
|
+
mysql: { text: 'varchar(255)', number: 'double', boolean: 'tinyint(1)', date: 'date', dateString: 'date', datetime: 'datetime', json: 'json' },
|
|
467
|
+
sqlite: { text: 'text', number: 'real', boolean: 'integer', date: 'text', dateString: 'text', datetime: 'text', json: 'text' },
|
|
468
|
+
mssql: { text: 'nvarchar(255)', number: 'float', boolean: 'bit', date: 'date', dateString: 'date', datetime: 'datetime2', json: 'nvarchar(max)' },
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Identifier quoter per dialect (doubles the quote char to escape, like sql.ts). */
|
|
472
|
+
function ddlQuoter(dialect: DdlDialect): (name: string) => string {
|
|
473
|
+
const q = dialect === 'mysql' ? '`' : '"'
|
|
474
|
+
return (name) => `${q}${name.replaceAll(q, q + q)}${q}`
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** A SQL literal for a seed value, per dialect. */
|
|
478
|
+
function ddlLiteral(v: unknown, dialect: DdlDialect): string {
|
|
479
|
+
if (v == null || v === '') return 'NULL'
|
|
480
|
+
if (typeof v === 'number') return Number.isFinite(v) ? String(v) : 'NULL'
|
|
481
|
+
if (typeof v === 'boolean') return dialect === 'postgres' ? (v ? 'TRUE' : 'FALSE') : v ? '1' : '0'
|
|
482
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v)
|
|
483
|
+
const escaped = s.replaceAll("'", "''")
|
|
484
|
+
return dialect === 'mssql' ? `N'${escaped}'` : `'${escaped}'`
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** `db/schema.sql` + `db/seed.sql` for the project's SQL-bound entities. */
|
|
488
|
+
export function sqlDdlFiles(schemas: EntitySchema[], sources: Record<string, EntityDataSource>): GeneratedFile[] {
|
|
489
|
+
const { entries, seed } = prepareEntities(schemas)
|
|
490
|
+
const sqlEntries = entries
|
|
491
|
+
.map((e) => ({ ...e, src: sources[e.schema.name] }))
|
|
492
|
+
.filter((e): e is Prepared & { src: SqlSource } => e.src?.kind === 'sql')
|
|
493
|
+
if (sqlEntries.length === 0) return []
|
|
494
|
+
|
|
495
|
+
const create: string[] = []
|
|
496
|
+
const inserts: string[] = []
|
|
497
|
+
for (const { schema, infos, src } of sqlEntries) {
|
|
498
|
+
const dialect = ddlDialect(src.dialect)
|
|
499
|
+
const q = ddlQuoter(dialect)
|
|
500
|
+
const types = DDL_COL_TYPES[dialect]
|
|
501
|
+
const pk = resolveIdField(schema)
|
|
502
|
+
// Relation display fields are filled at runtime (labels), not stored.
|
|
503
|
+
const display = new Set(infos.map((i) => i.displayField))
|
|
504
|
+
const cols = schema.fields.filter((f) => !display.has(f.field) && !f.formula && !f.computed)
|
|
505
|
+
const colDefs = cols.map((f) => ` ${q(f.field)} ${types[f.type] ?? types.text}${f.field === pk ? ' PRIMARY KEY' : ''}`)
|
|
506
|
+
const body = `CREATE TABLE ${dialect === 'mssql' ? '' : 'IF NOT EXISTS '}${q(src.table)} (\n${colDefs.join(',\n')}\n)`
|
|
507
|
+
create.push(dialect === 'mssql' ? `IF OBJECT_ID(N'${src.table.replaceAll("'", "''")}', N'U') IS NULL\n${body};` : `${body};`)
|
|
508
|
+
|
|
509
|
+
const rows = seed.get(schema.name) ?? []
|
|
510
|
+
if (rows.length) {
|
|
511
|
+
const names = cols.map((f) => q(f.field)).join(', ')
|
|
512
|
+
const values = rows.map((r) => ` (${cols.map((f) => ddlLiteral(r[f.field], dialect)).join(', ')})`).join(',\n')
|
|
513
|
+
inserts.push(`INSERT INTO ${q(src.table)} (${names}) VALUES\n${values};`)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const header = (what: string) =>
|
|
518
|
+
`-- ${what} for the app's SQL entities. Generated by SvGrid Studio.\n` +
|
|
519
|
+
`-- Run against the database your DATABASE_URL points at (psql/mysql/sqlite3/sqlcmd,\n` +
|
|
520
|
+
`-- or your provider's SQL console) before first use. Tip: the Drizzle data layer\n` +
|
|
521
|
+
`-- replaces this file with real, versioned migrations.\n\n`
|
|
522
|
+
return [
|
|
523
|
+
{ path: 'db/schema.sql', description: 'CREATE TABLE statements for the SQL entities (run once against your database).', contents: header('Table definitions') + create.join('\n\n') + '\n' },
|
|
524
|
+
...(inserts.length ? [{ path: 'db/seed.sql', description: 'Optional sample rows for the SQL entities.', contents: header('Sample seed rows (optional)') + inserts.join('\n\n') + '\n' }] : []),
|
|
525
|
+
]
|
|
526
|
+
}
|
|
527
|
+
|
|
421
528
|
function dataModule(
|
|
422
529
|
entries: Prepared[],
|
|
423
530
|
seed: Map<string, Array<Record<string, unknown>>>,
|
|
424
531
|
sources?: Record<string, EntityDataSource>,
|
|
532
|
+
supabaseConn?: { url?: string; key?: string },
|
|
425
533
|
): { file: GeneratedFile; needs: DataModuleNeeds } {
|
|
426
534
|
const schemas = entries.map((e) => e.schema)
|
|
427
535
|
// Each type gets its own `type` keyword - SvelteKit enables verbatimModuleSyntax.
|
|
@@ -442,15 +550,17 @@ function dataModule(
|
|
|
442
550
|
switch (src.kind) {
|
|
443
551
|
case 'rest':
|
|
444
552
|
entImports.add('createRestDataSource')
|
|
553
|
+
if (src.adapter) entImports.add(restAdapterImport(src.adapter))
|
|
445
554
|
return restStoreExpr(src, store, T, sv)
|
|
446
555
|
case 'sql':
|
|
447
556
|
// SQL runs server-side: the client talks to a generated /api/<entity> route.
|
|
448
557
|
entImports.add('createKitDataSource')
|
|
449
|
-
needs.sqlRoutes.push({ schema: e.schema, table: src.table, dialect: src.dialect })
|
|
558
|
+
needs.sqlRoutes.push({ schema: e.schema, table: src.table, dialect: src.dialect, dbSchema: src.schema })
|
|
450
559
|
return `const ${store} = createKitDataSource<${T}>({ endpoint: '/api/${namesFor(e.schema).route}' })`
|
|
451
560
|
case 'supabase':
|
|
452
561
|
entImports.add('createSupabaseDataSource')
|
|
453
562
|
needs.supabase = true
|
|
563
|
+
// Per-entity url/key overrides the project-level connection for the .env hint.
|
|
454
564
|
if (src.url && src.key && !needs.supabaseUrl) { needs.supabaseUrl = src.url; needs.supabaseKey = src.key }
|
|
455
565
|
return `const ${store} = createSupabaseDataSource<${T}>({ client: supabaseClient, table: ${sq(src.table)}, schema: ${sv} })`
|
|
456
566
|
case 'pglite': {
|
|
@@ -495,6 +605,11 @@ function dataModule(
|
|
|
495
605
|
})
|
|
496
606
|
.join('\n')
|
|
497
607
|
|
|
608
|
+
// Project-level connection fills the .env hint when no entity carries its own url/key.
|
|
609
|
+
if (needs.supabase && !needs.supabaseUrl && (supabaseConn?.url || supabaseConn?.key)) {
|
|
610
|
+
needs.supabaseUrl = supabaseConn.url
|
|
611
|
+
needs.supabaseKey = supabaseConn.key
|
|
612
|
+
}
|
|
498
613
|
const connImportLine = needs.supabase ? `\nimport { supabaseClient } from './connections'` : ''
|
|
499
614
|
const pgliteImportLine = needs.pglite ? `\nimport { PGlite } from '@electric-sql/pglite'` : ''
|
|
500
615
|
const pgliteBoot = needs.pglite ? pgliteBootstrap(needs.pgliteTables!, seed) + '\n' : ''
|
|
@@ -523,27 +638,30 @@ export const nextId = (prefix: string) => \`\${prefix}\${++seq}\`
|
|
|
523
638
|
return { file, needs }
|
|
524
639
|
}
|
|
525
640
|
|
|
526
|
-
/** The
|
|
641
|
+
/** The Supabase client wiring. Reads the project URL + anon key from PUBLIC_ env vars
|
|
642
|
+
* (never inlined into source); the anon key is public / browser-safe, so this is a
|
|
643
|
+
* secret-hygiene win and matches SvelteKit convention. The designer-entered values are
|
|
644
|
+
* shown as a comment so they can be pasted straight into `.env` / `.env.example`. */
|
|
527
645
|
function connectionsModule(needs: DataModuleNeeds): GeneratedFile {
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
? `/**
|
|
532
|
-
* Supabase client. The anon key is public (browser-safe) - access is protected
|
|
533
|
-
* by your Row Level Security policies. Requires \`@supabase/supabase-js\`
|
|
534
|
-
* (\`npm i @supabase/supabase-js\`).
|
|
535
|
-
*/
|
|
536
|
-
export const supabaseClient = createClient(${sq(needs.supabaseUrl!)}, ${sq(needs.supabaseKey!)}) as unknown as SupabaseClientLike`
|
|
537
|
-
: `/**
|
|
538
|
-
* TODO: connect Supabase. Add your project URL + anon key in the Studio designer,
|
|
539
|
-
* or replace this null stub with a real client:
|
|
540
|
-
* \`import { createClient } from '@supabase/supabase-js'; export const supabaseClient = createClient(url, anonKey)\`.
|
|
541
|
-
*/
|
|
542
|
-
export const supabaseClient = null as unknown as SupabaseClientLike`
|
|
646
|
+
const hint = needs.supabaseUrl || needs.supabaseKey
|
|
647
|
+
? `// From the Studio designer - paste into .env:\n// PUBLIC_SUPABASE_URL=${needs.supabaseUrl ?? ''}\n// PUBLIC_SUPABASE_ANON_KEY=${needs.supabaseKey ?? ''}\n`
|
|
648
|
+
: `// Set PUBLIC_SUPABASE_URL and PUBLIC_SUPABASE_ANON_KEY in .env (see .env.example).\n`
|
|
543
649
|
return {
|
|
544
650
|
path: 'src/lib/connections.ts',
|
|
545
|
-
description: 'Supabase client for Supabase-bound entities.',
|
|
546
|
-
contents:
|
|
651
|
+
description: 'Supabase client for Supabase-bound entities (reads PUBLIC_SUPABASE_* env vars).',
|
|
652
|
+
contents: `import { createClient } from '@supabase/supabase-js'
|
|
653
|
+
import { env } from '$env/dynamic/public'
|
|
654
|
+
import type { SupabaseClientLike } from '@svgrid/enterprise'
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Supabase client. The anon key is public (browser-safe) - access is protected by
|
|
658
|
+
* your Row Level Security policies. Requires \`@supabase/supabase-js\`.
|
|
659
|
+
*/
|
|
660
|
+
${hint}export const supabaseClient = createClient(
|
|
661
|
+
env.PUBLIC_SUPABASE_URL ?? '',
|
|
662
|
+
env.PUBLIC_SUPABASE_ANON_KEY ?? '',
|
|
663
|
+
) as unknown as SupabaseClientLike
|
|
664
|
+
`,
|
|
547
665
|
}
|
|
548
666
|
}
|
|
549
667
|
|
|
@@ -580,7 +698,7 @@ export function entityScreenPage(schema: EntitySchema, route?: string, title?: s
|
|
|
580
698
|
|
|
581
699
|
export type NavItem = { href: string; label: string; id?: string }
|
|
582
700
|
|
|
583
|
-
export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: ShellConfig; title?: string; themeVars?: Record<string, string>; lightVars?: Record<string, string>; darkVars?: Record<string, string>; dark?: boolean; access?: boolean; auth?: boolean; authRoutes?: string[]; authAccount?: boolean; i18n?: boolean; appClass?: string } = {}): GeneratedFile {
|
|
701
|
+
export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: ShellConfig; title?: string; themeVars?: Record<string, string>; lightVars?: Record<string, string>; darkVars?: Record<string, string>; dark?: boolean; access?: boolean; auth?: boolean; supabaseAuth?: boolean; authRoutes?: string[]; authAccount?: boolean; i18n?: boolean; appClass?: string } = {}): GeneratedFile {
|
|
584
702
|
// Nav is the app's own screens; `/` just redirects to the first one, so no separate
|
|
585
703
|
// "Home" link (it would duplicate the first screen).
|
|
586
704
|
const links = nav
|
|
@@ -621,7 +739,7 @@ export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: Shel
|
|
|
621
739
|
|
|
622
740
|
// i18n: translate nav labels via `nav.<id>` keys (Home has no id -> literal).
|
|
623
741
|
const navLabel = opts.i18n ? `{item.id ? $t('nav.' + item.id, item.label) : item.label}` : '{item.label}'
|
|
624
|
-
const anchor = `<a class="sv-app__link" class:is-active={
|
|
742
|
+
const anchor = `<a class="sv-app__link" class:is-active={page.url.pathname === item.href} href={item.href}>${navLabel}</a>`
|
|
625
743
|
// When RBAC is on, hide nav links the current role can't open.
|
|
626
744
|
const linkGate = opts.access
|
|
627
745
|
? `{#if !item.id || canScreen($currentRole, item.id)}${anchor}{/if}`
|
|
@@ -736,7 +854,9 @@ export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: Shel
|
|
|
736
854
|
<button type="button" class="sv-app__menu-item" role="menuitem" onclick={() => (menuOpen = false)}>Settings</button>`}
|
|
737
855
|
${opts.auth
|
|
738
856
|
? `<form method="POST" action="/logout" class="sv-app__signout"><button type="submit" class="sv-app__menu-item sv-app__menu-item--danger" role="menuitem">Sign out</button></form>`
|
|
739
|
-
:
|
|
857
|
+
: opts.supabaseAuth
|
|
858
|
+
? '' // Supabase Auth: the real sign-out lives on SvAuthGate's own bar, so no placeholder here.
|
|
859
|
+
: `<button type="button" class="sv-app__menu-item sv-app__menu-item--danger" role="menuitem" onclick={() => (menuOpen = false)}>Sign out</button>`}
|
|
740
860
|
</div>
|
|
741
861
|
{/if}
|
|
742
862
|
</div>
|
|
@@ -744,9 +864,17 @@ export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: Shel
|
|
|
744
864
|
{#if bellOpen || menuOpen}<button type="button" class="sv-app__scrim" tabindex="-1" aria-label="Close menus" onclick={() => { bellOpen = false; menuOpen = false }}></button>{/if}
|
|
745
865
|
</div>\n `
|
|
746
866
|
: ''
|
|
867
|
+
// Supabase Auth: gate the whole app behind a client-side sign-in. SvAuthGate shows
|
|
868
|
+
// the sign-in form when signed out, and the app + a "signed in as … / Sign out" bar
|
|
869
|
+
// when signed in. Reads the shared client from $lib/connections.
|
|
870
|
+
const content = opts.supabaseAuth
|
|
871
|
+
? `<SvAuthGate client={supabaseClient as unknown as SupabaseAuthClientLike} title={\`Sign in to \${brand}\`}>
|
|
872
|
+
{@render children()}
|
|
873
|
+
</SvAuthGate>`
|
|
874
|
+
: `{@render children()}`
|
|
747
875
|
const mainMarkup = `<main class="sv-app__main">
|
|
748
876
|
${toolbarMarkup}<div class="sv-app__content">
|
|
749
|
-
{
|
|
877
|
+
${content}
|
|
750
878
|
</div>
|
|
751
879
|
</main>`
|
|
752
880
|
|
|
@@ -842,14 +970,14 @@ export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: Shel
|
|
|
842
970
|
contents: `<script lang="ts">
|
|
843
971
|
import '../app.css'
|
|
844
972
|
import '../custom.css'
|
|
845
|
-
import { page } from '$app/
|
|
973
|
+
import { page } from '$app/state'${opts.access ? `\n import { currentRole, canScreen } from '$lib/access'` : ''}${opts.i18n ? `\n import { t, currentLocale, locales } from '$lib/i18n'` : ''}${opts.auth ? `\n import type { LayoutData } from './$types'` : ''}${opts.supabaseAuth ? `\n import { SvAuthGate, type SupabaseAuthClientLike } from '@svgrid/enterprise'\n import { supabaseClient } from '$lib/connections'` : ''}
|
|
846
974
|
|
|
847
975
|
let { children${opts.auth ? ', data' : ''} }${opts.auth ? ": { children: import('svelte').Snippet; data: LayoutData }" : ''} = $props()
|
|
848
976
|
const nav = ${JSON.stringify(links)}
|
|
849
977
|
const brand = ${JSON.stringify(brand)}${footConst}${logoConst}
|
|
850
978
|
let navOpen = $state(false)${opts.auth && opts.access ? `\n // Seed the client role store from the signed-in session (server-resolved).\n $effect(() => { if (data?.role) currentRole.set(data.role as never) })` : ''}
|
|
851
979
|
// Close the mobile drawer whenever the route changes.
|
|
852
|
-
$effect(() => { void
|
|
980
|
+
$effect(() => { void page.url.pathname; navOpen = false })${hasSwitch ? `
|
|
853
981
|
// Light/dark switcher: defaults to the mode picked in Studio, then honours the
|
|
854
982
|
// visitor's saved choice. Applies via [data-theme] on <html> (see the token
|
|
855
983
|
// sets in <svelte:head>), and persists per browser.
|
|
@@ -888,11 +1016,11 @@ export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: Shel
|
|
|
888
1016
|
return () => window.removeEventListener('keydown', onKey)
|
|
889
1017
|
})
|
|
890
1018
|
// Close both menus on route change.
|
|
891
|
-
$effect(() => { void
|
|
1019
|
+
$effect(() => { void page.url.pathname; bellOpen = false; menuOpen = false })` : ''}
|
|
892
1020
|
</script>
|
|
893
1021
|
${themeHead}
|
|
894
1022
|
|
|
895
|
-
${opts.auth ? `{#if ${JSON.stringify(opts.authRoutes ?? ['/login'])}.includes(
|
|
1023
|
+
${opts.auth ? `{#if ${JSON.stringify(opts.authRoutes ?? ['/login'])}.includes(page.url.pathname)}
|
|
896
1024
|
{@render children()}
|
|
897
1025
|
{:else}
|
|
898
1026
|
${body}
|
|
@@ -1011,13 +1139,26 @@ export function prepareEntities(schemas: EntitySchema[]): { entries: Prepared[];
|
|
|
1011
1139
|
/** Emit the shared entity modules: `src/lib/schemas.ts` + `src/lib/data.ts` (+ `connections.ts`). */
|
|
1012
1140
|
export function emitEntityModules(
|
|
1013
1141
|
schemas: EntitySchema[],
|
|
1014
|
-
opts: { sources?: Record<string, EntityDataSource>; accessEnabled?: boolean; auditEnabled?: boolean; screensByEntity?: Map<string, string[]>; triggers?: Record<string, EntityTriggers
|
|
1142
|
+
opts: { sources?: Record<string, EntityDataSource>; accessEnabled?: boolean; auditEnabled?: boolean; screensByEntity?: Map<string, string[]>; triggers?: Record<string, EntityTriggers>; supabaseConn?: { url?: string; key?: string }; supabaseAuth?: boolean; tenantField?: string; tenantScoped?: (entity: string) => boolean } = {},
|
|
1015
1143
|
): { files: GeneratedFile[]; prepared: EntitySchema[] } {
|
|
1016
1144
|
const { entries, seed } = prepareEntities(schemas)
|
|
1017
|
-
const { file: data, needs } = dataModule(entries, seed, opts.sources)
|
|
1145
|
+
const { file: data, needs } = dataModule(entries, seed, opts.sources, opts.supabaseConn)
|
|
1018
1146
|
const files: GeneratedFile[] = [schemasModule(entries), data]
|
|
1019
|
-
|
|
1020
|
-
|
|
1147
|
+
// Supabase Auth needs the shared client even with no Supabase-bound entity; seed
|
|
1148
|
+
// the .env paste hint from the project connection when nothing else did.
|
|
1149
|
+
if ((needs.supabase || opts.supabaseAuth) && !needs.supabaseUrl && (opts.supabaseConn?.url || opts.supabaseConn?.key)) {
|
|
1150
|
+
needs.supabaseUrl = opts.supabaseConn.url; needs.supabaseKey = opts.supabaseConn.key
|
|
1151
|
+
}
|
|
1152
|
+
if (needs.supabase || opts.supabaseAuth) files.push(connectionsModule(needs))
|
|
1153
|
+
for (const r of needs.sqlRoutes) files.push(sqlRouteFile(r.schema, r.table, r.dialect, {
|
|
1154
|
+
access: opts.accessEnabled,
|
|
1155
|
+
audit: opts.auditEnabled,
|
|
1156
|
+
screenIds: opts.screensByEntity?.get(r.schema.name) ?? [],
|
|
1157
|
+
triggers: opts.triggers?.[r.schema.name],
|
|
1158
|
+
dbSchema: r.dbSchema,
|
|
1159
|
+
// A shared (reference-data) entity stays global - no scope on its route.
|
|
1160
|
+
tenantField: opts.tenantField && opts.tenantScoped?.(r.schema.name) !== false ? opts.tenantField : undefined,
|
|
1161
|
+
}))
|
|
1021
1162
|
return { files, prepared: entries.map((e) => e.schema) }
|
|
1022
1163
|
}
|
|
1023
1164
|
|
package/src/studio/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { inferType, introspectJson, introspectDrizzle, introspectDrizzleAll } fr
|
|
|
8
8
|
export { refineField, refineFields } from '../sources/field-inference.js'
|
|
9
9
|
export { parseCsv, detectDelimiter, csvToEntity, type CsvEntity } from './csv.js'
|
|
10
10
|
export { introspectPrisma, introspectPrismaAll } from './introspect-prisma.js'
|
|
11
|
+
export { introspectOpenApi, type OpenApiImport } from './introspect-openapi.js'
|
|
11
12
|
export {
|
|
12
13
|
introspectDatabase,
|
|
13
14
|
listDatabaseTables,
|
|
@@ -39,9 +40,37 @@ export {
|
|
|
39
40
|
} from './scaffold.js'
|
|
40
41
|
export { scaffoldApp, type ScaffoldAppOptions } from './scaffold-app.js'
|
|
41
42
|
export { emitStudioApp, emitEntityModules, entityScreenPage, prepareEntities } from './emit-schema.js'
|
|
42
|
-
export { emitStudioProject, emitStudioAppBundle, studioDeployInfo, screenHandles, screenDataset, ctxCompletions, componentHandleMembers, ctxAmbientDts, GRID_API_MEMBERS, type BlockHandle, type HandleTier } from './emit-project.js'
|
|
43
|
+
export { emitStudioProject, emitStudioAppBundle, emitStudioFragment, runtimeDeps, studioDeployInfo, screenHandles, screenDataset, ctxCompletions, componentHandleMembers, ctxAmbientDts, GRID_API_MEMBERS, type BlockHandle, type HandleTier } from './emit-project.js'
|
|
43
44
|
export { sampleApps, getSampleApp, type SampleApp } from './samples/index.js'
|
|
45
|
+
export { starterProject } from './samples/starter.js'
|
|
44
46
|
export { liveDataSamples, getLiveDataSample } from './samples/live-data.js'
|
|
47
|
+
export { starterDatasets, getStarterDataset, type StarterDataset } from './samples/datasets.js'
|
|
48
|
+
export {
|
|
49
|
+
runStudioInit,
|
|
50
|
+
parseSelection,
|
|
51
|
+
type PromptIO,
|
|
52
|
+
type DbGateway,
|
|
53
|
+
type FetchText,
|
|
54
|
+
type InitFlags,
|
|
55
|
+
type InitResult,
|
|
56
|
+
} from './init-flow.js'
|
|
57
|
+
export {
|
|
58
|
+
crudSuiteScreens,
|
|
59
|
+
addCrudSuite,
|
|
60
|
+
crudAppFromSchemas,
|
|
61
|
+
listScreen,
|
|
62
|
+
formScreen,
|
|
63
|
+
detailScreen,
|
|
64
|
+
dashScreen,
|
|
65
|
+
statusPills,
|
|
66
|
+
gridConfig,
|
|
67
|
+
type CrudScreenKind,
|
|
68
|
+
type CrudEditingMode,
|
|
69
|
+
type CrudSuiteOptions,
|
|
70
|
+
type CrudAppOptions,
|
|
71
|
+
type GridOpts,
|
|
72
|
+
type Tile,
|
|
73
|
+
} from './screen-suites.js'
|
|
45
74
|
export {
|
|
46
75
|
sanitizeStudioProject,
|
|
47
76
|
buildStudioBugReport,
|
|
@@ -132,10 +161,23 @@ export {
|
|
|
132
161
|
type TriggerStep,
|
|
133
162
|
type TriggerEvent,
|
|
134
163
|
type EntityTriggers,
|
|
164
|
+
setJob,
|
|
165
|
+
type ScheduledJob,
|
|
166
|
+
type ScheduledJobKind,
|
|
167
|
+
setTenancy,
|
|
168
|
+
tenantField,
|
|
169
|
+
isTenantScoped,
|
|
170
|
+
type TenancyConfig,
|
|
135
171
|
clickSlot,
|
|
172
|
+
ssrScreenShape,
|
|
173
|
+
ssrEligible,
|
|
174
|
+
setScreenRenderMode,
|
|
175
|
+
eventSlot,
|
|
136
176
|
rowSelectSlot,
|
|
137
177
|
changeSlot,
|
|
138
178
|
FORM_SUBMIT,
|
|
179
|
+
GRID_EVENTS,
|
|
180
|
+
type GridEventDef,
|
|
139
181
|
addStateVar,
|
|
140
182
|
updateStateVar,
|
|
141
183
|
removeStateVar,
|
|
@@ -263,6 +305,7 @@ export {
|
|
|
263
305
|
type EntityDataSource,
|
|
264
306
|
type MemorySource,
|
|
265
307
|
type RestSource,
|
|
308
|
+
type RestAdapterConfig,
|
|
266
309
|
type SqlSource,
|
|
267
310
|
type SupabaseSource,
|
|
268
311
|
type PgliteSource,
|
|
@@ -288,10 +331,25 @@ export {
|
|
|
288
331
|
export {
|
|
289
332
|
UI_COMPONENT_REGISTRY,
|
|
290
333
|
uiComponentSpec,
|
|
334
|
+
gridPropSurface,
|
|
335
|
+
gridApiSettableProps,
|
|
336
|
+
GRID_CURATED_PROPS,
|
|
337
|
+
STANDARD_UI_EVENTS,
|
|
291
338
|
type UiPropType,
|
|
339
|
+
type UiPropGroup,
|
|
340
|
+
type UiComponentEvent,
|
|
292
341
|
type UiComponentProp,
|
|
293
342
|
type UiComponentSpec,
|
|
294
343
|
} from './ui-components.js'
|
|
344
|
+
export { buildCopilotMessages, projectFromModelText, type CopilotMessages } from './copilot-core.js'
|
|
345
|
+
export {
|
|
346
|
+
resolveDeployTarget,
|
|
347
|
+
deployCommands,
|
|
348
|
+
missingEnvKeys,
|
|
349
|
+
type DeployProvider,
|
|
350
|
+
type DeployResolution,
|
|
351
|
+
type DeployPlanCommands,
|
|
352
|
+
} from './deploy-cli.js'
|
|
295
353
|
export {
|
|
296
354
|
runStudioAdd,
|
|
297
355
|
runStudioAddApp,
|