Files
windmill/frontend/src/lib/components/dbManagerDrawerModel.svelte.ts
T
GuilhemandClaude Opus 4.8 c000bbca28 fix(frontend): scope raw-app, flow and script editors to the session workspace (#10015)
* fix(frontend): scope raw-app/flow/script editors to the session workspace

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): scope flow and script editor operations to the session workspace

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): scope flow preview, inline-script creation and datatable schema to the session workspace

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Codex review — thread session workspace through flow resource pickers, script fetch, preview cancel/recording and path collision check

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Claude review — pass session workspace to preview FlowStatusViewer and align FlowChatManager guards

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Pi review — show acting workspace in script-not-found message and fetch picked script from it in EditorBar

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Codex review round 2 — thread session workspace into flow step test, raw-app inline runnable, inline editor toolbars and MCP OAuth path

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Codex review round 3 — thread session workspace into dynamic-input helpers and the flow-preview argument side panel (history/saved-inputs/captures)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Codex review round 4 — thread session workspace into nested flow/script drawers, flow chat inputs and the flow input side tabs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Codex review round 5 — thread session workspace into script-module fork/reload and key the raw-app schema cache by workspace

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Codex review round 6 — key the DB manager schema cache by acting workspace

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): address Codex review round 7 — thread session workspace into resource-valued arg pickers and the editor variable/resource helper drawers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): scope the flow asset explorer's ResourceEditorDrawer to the acting workspace

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: thread acting workspace through flow asset explore controls

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: thread acting workspace through SQL REPL, secret args, helper forms, S3 inputs, saved inputs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:53:43 +02:00

224 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from 'zod'
import { useSearchParams } from '$lib/svelte5UtilsKit.svelte'
import type { DbInput, DbType } from './dbTypes'
import { isDbType } from './dbTypes'
/**
* Single URL param `dbm` encodes the full DB manager state:
* firstSegment~path~schema.table
*
* firstSegment:
* datatable database with datatable:// resource (resourceType always postgresql)
* ducklake ducklake connection
* postgresql, mysql, … regular database (the segment IS the resource type)
*
* schema.table (third segment, optional):
* schema.table both
* .table table only
* schema. schema only
* (omitted) neither
*
* Default schemas (omitted from URL, restored on parse):
* datatable → public
* ducklake → main
*
* Examples:
* datatable~main~.customers (schema "public" implied)
* ducklake~main~.orders (schema "main" implied)
* postgresql~$res:u/user/my_pg~public.customers
*/
const dbManagerSchema = z.object({
dbm: z.string().nullable()
})
interface ParsedDbm {
type: 'database' | 'datatable' | 'ducklake'
path: string
resType?: string
schema?: string
table?: string
}
function parseDbm(raw: unknown): ParsedDbm | null {
if (!raw || typeof raw !== 'string') return null
const parts = raw.split('~')
if (parts.length < 2 || !parts[1]) return null
const firstSeg = parts[0]
const path = parts[1]
const schemaTable = parts[2] ?? ''
let type: ParsedDbm['type']
let resType: string | undefined
if (firstSeg === 'datatable') {
type = 'datatable'
} else if (firstSeg === 'ducklake') {
type = 'ducklake'
} else if (isDbType(firstSeg)) {
type = 'database'
resType = firstSeg
} else {
return null
}
let schema: string | undefined
let table: string | undefined
if (schemaTable) {
const dotIdx = schemaTable.indexOf('.')
if (dotIdx === 0) {
table = schemaTable.slice(1) || undefined
} else if (dotIdx === schemaTable.length - 1) {
schema = schemaTable.slice(0, -1) || undefined
} else if (dotIdx > 0) {
schema = schemaTable.slice(0, dotIdx)
table = schemaTable.slice(dotIdx + 1)
}
}
// Restore default schema when omitted for datatable/ducklake
if (!schema && table && type in defaultSchemas) {
schema = defaultSchemas[type]
}
return { type, path, resType, schema, table }
}
const defaultSchemas: Record<string, string> = { datatable: 'public', ducklake: 'main' }
function buildDbm(p: ParsedDbm): string {
const firstSeg = p.type === 'database' ? p.resType! : p.type
const schema = p.schema === defaultSchemas[p.type] ? undefined : p.schema
let schemaTable = ''
if (schema && p.table) {
schemaTable = `${schema}.${p.table}`
} else if (p.table) {
schemaTable = `.${p.table}`
} else if (schema) {
schemaTable = `${schema}.`
}
return schemaTable ? `${firstSeg}~${p.path}~${schemaTable}` : `${firstSeg}~${p.path}`
}
export interface DbManagerUriState {
readonly input: DbInput | undefined
readonly effectiveInput: DbInput | undefined
readonly isDatatableInput: boolean
selectedDatatable: string | undefined
selectedSchema: string | undefined
selectedTable: string | undefined
readonly open: boolean
/** Workspace the drawer's DB operations run against — the acting workspace of
* the editor that opened it, else the navigation workspace. */
workspace: string | undefined
openDrawer: (nInput: DbInput, workspace?: string) => void
closeDrawer: () => void
}
export function useDbManagerUriState(): DbManagerUriState {
const params = useSearchParams(dbManagerSchema)
const parsed = $derived(parseDbm(params.dbm))
let input: DbInput | undefined = $derived.by(() => {
if (!parsed) return undefined
if (parsed.type === 'ducklake') {
return {
type: 'ducklake' as const,
ducklake: parsed.path,
specificSchema: parsed.schema,
specificTable: parsed.table
}
}
// datatable or database
const resType = parsed.type === 'datatable' ? 'postgresql' : parsed.resType
if (!isDbType(resType ?? undefined)) return undefined
return {
type: 'database' as const,
resourceType: resType as DbType,
resourcePath: parsed.type === 'datatable' ? `datatable://${parsed.path}` : parsed.path,
specificSchema: parsed.schema,
specificTable: parsed.table
}
})
const isDatatableInput = $derived(parsed?.type === 'datatable')
function updateField(updates: Partial<ParsedDbm>) {
const p = parseDbm(params.dbm)
if (!p) return
Object.assign(p, updates)
params.dbm = buildDbm(p)
}
// Not URL-persisted: the drawer defaults back to the nav workspace on reload,
// which is the correct fallback outside the session that opened it.
let workspace = $state<string | undefined>(undefined)
function openDrawer(nInput: DbInput, ws?: string) {
workspace = ws
if (nInput.type === 'database') {
const isDatatable = nInput.resourcePath.startsWith('datatable://')
params.dbm = buildDbm({
type: isDatatable ? 'datatable' : 'database',
path: isDatatable ? nInput.resourcePath.slice('datatable://'.length) : nInput.resourcePath,
resType: isDatatable ? undefined : nInput.resourceType,
schema: nInput.specificSchema,
table: nInput.specificTable
})
} else {
params.dbm = buildDbm({
type: 'ducklake',
path: nInput.ducklake,
schema: nInput.specificSchema,
table: nInput.specificTable
})
}
}
function closeDrawer() {
params.dbm = null
}
return {
get input() {
return input
},
get effectiveInput() {
return input
},
get isDatatableInput() {
return isDatatableInput
},
get selectedDatatable() {
return parsed?.type === 'datatable' ? parsed.path : undefined
},
set selectedDatatable(v: string | undefined) {
if (v) updateField({ path: v })
},
get selectedSchema() {
return parsed?.schema
},
set selectedSchema(v: string | undefined) {
updateField({ schema: v })
},
get selectedTable() {
return parsed?.table
},
set selectedTable(v: string | undefined) {
updateField({ table: v })
},
get open() {
return !!input
},
get workspace() {
return workspace
},
set workspace(v: string | undefined) {
workspace = v
},
openDrawer,
closeDrawer
}
}