refactor(editor): extract typescript extra-libs registration

Headless linting must register the same rt.d.ts resource-type namespace and
custom windmill-client declarations as a mounted editor, since diagnostics
depend on them. Move the apply bodies to lint/typescriptExtraLibs.ts; the
editor keeps its reactive resource() wrapper around the fetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-07-20 12:35:06 +02:00
parent b89e082e87
commit 35f3196c4a
2 changed files with 73 additions and 56 deletions
+9 -56
View File
@@ -43,13 +43,7 @@
import { editorFontSize } from '$lib/editorFontSize.svelte'
import { workspaceStore } from '$lib/stores'
import DdlMigrationGuard from './DdlMigrationGuard.svelte'
import {
type Preview,
ResourceService,
type ScriptLang,
UserService,
WorkspaceService
} from '$lib/gen'
import { type Preview, type ScriptLang, UserService } from '$lib/gen'
import type { Text } from 'yjs'
import {
initializeVscode,
@@ -104,12 +98,16 @@
import GlobalReviewButtons from './copilot/chat/GlobalReviewButtons.svelte'
import AIChatInlineWidget from './copilot/chat/AIChatInlineWidget.svelte'
import { writable } from 'svelte/store'
import { formatResourceTypes } from './copilot/chat/script/core'
import type { ScriptLintResult } from './copilot/chat/shared'
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'
import { editorPositionMap } from '$lib/utils'
import { extToLang } from '$lib/editorLangUtils'
import { computeModelPath, computeModelUri } from './lint/monacoUri'
import {
applyCustomWmillTypes,
ensureResourceTypeNamespace,
fetchCustomWmillTypesData
} from './lint/typescriptExtraLibs'
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
import type { Selection } from 'monaco-editor'
import { canHavePreprocessor, getPreprocessorModuleCode } from '$lib/script_helpers'
@@ -1733,61 +1731,16 @@
let customTsTypesData = resource([() => lang], async () => {
if (lang !== 'typescript') return undefined
let datatables = (
await WorkspaceService.listDataTables({ workspace: $workspaceStore ?? '' })
).map((d) => d.name)
let ducklakes = await WorkspaceService.listDucklakes({ workspace: $workspaceStore ?? '' })
return { datatables, ducklakes }
return await fetchCustomWmillTypesData($workspaceStore ?? '')
})
function setTypescriptCustomTypes() {
if (!customTsTypesData.current) return
if (lang !== 'typescript') return
const ducklakeNames = customTsTypesData.current.ducklakes
const datatableNames = customTsTypesData.current.datatables
const ducklakeNameType = ducklakeNames.length
? ducklakeNames.map((name) => JSON.stringify(name)).join(' | ')
: 'string'
const datatableNameType = datatableNames.length
? datatableNames.map((name) => JSON.stringify(name)).join(' | ')
: 'string'
const isDucklakeOptional = ducklakeNames.includes('main')
const isDataTableOptional = datatableNames.includes('main')
let disposeTs = typescriptDefaults.addExtraLib(
`export {};
declare module 'windmill-client' {
import { type DatatableSqlTemplateFunction, type SqlTemplateFunction } from 'windmill-client';
export function ducklake(name${isDucklakeOptional ? '?' : ''}: ${ducklakeNameType}): SqlTemplateFunction;
export function datatable(name${isDataTableOptional ? '?' : ''}: ${datatableNameType}): DatatableSqlTemplateFunction;
}`,
'file:///custom_wmill_types.d.ts'
)
return () => {
disposeTs.dispose()
}
return applyCustomWmillTypes(customTsTypesData.current)
}
async function setTypescriptRTNamespace() {
if (
scriptLang &&
(scriptLang === 'bun' ||
scriptLang === 'deno' ||
scriptLang === 'bunnative' ||
scriptLang === 'nativets')
) {
const resourceTypes = await ResourceService.listResourceType({
workspace: $workspaceStore ?? ''
})
const namespace = formatResourceTypes(
resourceTypes,
scriptLang === 'bunnative' ? 'bun' : scriptLang
)
typescriptDefaults.addExtraLib(namespace, 'rt.d.ts')
}
await ensureResourceTypeNamespace($workspaceStore ?? '', scriptLang)
}
async function setTypescriptExtraLibs() {
@@ -0,0 +1,64 @@
import { typescriptDefaults } from '@codingame/monaco-vscode-standalone-typescript-language-features'
import { ResourceService, WorkspaceService } from '$lib/gen'
import { formatResourceTypes } from '../copilot/chat/script/core'
// Extra libs that shape TypeScript diagnostics for Windmill code. They are registered
// under fixed URIs in the global typescriptDefaults, so a headless linter and a mounted
// editor necessarily see the same declarations — as long as both go through here.
const RESOURCE_TYPE_LANGS = ['bun', 'deno', 'bunnative', 'nativets'] as const
type ResourceTypeLang = (typeof RESOURCE_TYPE_LANGS)[number]
export async function ensureResourceTypeNamespace(
workspace: string,
scriptLang: string | undefined
): Promise<void> {
if (!RESOURCE_TYPE_LANGS.includes(scriptLang as ResourceTypeLang)) {
return
}
const lang = scriptLang as ResourceTypeLang
const resourceTypes = await ResourceService.listResourceType({ workspace })
const namespace = formatResourceTypes(resourceTypes, lang === 'bunnative' ? 'bun' : lang)
typescriptDefaults.addExtraLib(namespace, 'rt.d.ts')
}
export interface CustomWmillTypesData {
datatables: string[]
ducklakes: string[]
}
export async function fetchCustomWmillTypesData(workspace: string): Promise<CustomWmillTypesData> {
const datatables = (await WorkspaceService.listDataTables({ workspace })).map((d) => d.name)
const ducklakes = await WorkspaceService.listDucklakes({ workspace })
return { datatables, ducklakes }
}
export function applyCustomWmillTypes(data: CustomWmillTypesData): () => void {
const { datatables: datatableNames, ducklakes: ducklakeNames } = data
const ducklakeNameType = ducklakeNames.length
? ducklakeNames.map((name) => JSON.stringify(name)).join(' | ')
: 'string'
const datatableNameType = datatableNames.length
? datatableNames.map((name) => JSON.stringify(name)).join(' | ')
: 'string'
const isDucklakeOptional = ducklakeNames.includes('main')
const isDataTableOptional = datatableNames.includes('main')
const disposeTs = typescriptDefaults.addExtraLib(
`export {};
declare module 'windmill-client' {
import { type DatatableSqlTemplateFunction, type SqlTemplateFunction } from 'windmill-client';
export function ducklake(name${isDucklakeOptional ? '?' : ''}: ${ducklakeNameType}): SqlTemplateFunction;
export function datatable(name${isDataTableOptional ? '?' : ''}: ${datatableNameType}): DatatableSqlTemplateFunction;
}`,
'file:///custom_wmill_types.d.ts'
)
return () => {
disposeTs.dispose()
}
}
export async function ensureCustomWmillTypes(workspace: string): Promise<() => void> {
return applyCustomWmillTypes(await fetchCustomWmillTypesData(workspace))
}