mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 08:02:40 +00:00
refactor: remove global ai draft fallback store
This commit is contained in:
@@ -94,13 +94,19 @@ vi.mock('$lib/gen', async () => {
|
||||
existsApp: vi.fn(async () => false)
|
||||
}),
|
||||
VariableService: wrapService(actual.VariableService, {
|
||||
existsVariable: vi.fn(async () => false)
|
||||
existsVariable: vi.fn(async () => false),
|
||||
getVariable: vi.fn(async ({ path }: { path: string }) => ({
|
||||
path,
|
||||
value: 'super-secret-token',
|
||||
is_secret: true,
|
||||
description: 'API key'
|
||||
})),
|
||||
listVariable: vi.fn(async () => [])
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
import { globalTools, prepareGlobalUserMessage } from './core'
|
||||
import { globalDraftStore } from './draftStore.svelte'
|
||||
import { UserDraft, __resetUserDraftForTesting } from '$lib/userDraft.svelte'
|
||||
import type { Tool, ToolCallbacks } from '../shared'
|
||||
|
||||
@@ -135,12 +141,18 @@ async function callGlobalTool(
|
||||
|
||||
describe('global AI tools', () => {
|
||||
beforeEach(() => {
|
||||
globalDraftStore.clearDrafts(WORKSPACE)
|
||||
__resetUserDraftForTesting()
|
||||
localStorage.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('does not expose resource or variable draft writers until their UserDraft contracts are complete', () => {
|
||||
const toolNames = globalTools.map((tool) => tool.def.function.name)
|
||||
|
||||
expect(toolNames).not.toContain('write_resource')
|
||||
expect(toolNames).not.toContain('write_variable')
|
||||
})
|
||||
|
||||
it('writes script drafts into the shared UserDraft store', async () => {
|
||||
const content = 'export async function main() {\n\treturn "hello"\n}'
|
||||
|
||||
@@ -273,14 +285,7 @@ describe('global AI tools', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('redacts variable draft values when reading workspace items', async () => {
|
||||
await callGlobalTool('write_variable', {
|
||||
path: 'f/secrets/api_key',
|
||||
value: 'super-secret-token',
|
||||
is_secret: true,
|
||||
description: 'API key'
|
||||
})
|
||||
|
||||
it('redacts existing variable values when reading workspace items', async () => {
|
||||
const raw = await callGlobalTool('read_workspace_item', {
|
||||
type: 'variable',
|
||||
path: 'f/secrets/api_key'
|
||||
@@ -292,7 +297,7 @@ describe('global AI tools', () => {
|
||||
type: 'variable',
|
||||
path: 'f/secrets/api_key',
|
||||
summary: 'API key',
|
||||
isDraft: true
|
||||
isDraft: false
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -61,14 +61,11 @@ import {
|
||||
} from '../shared'
|
||||
import type { ContextElement } from '../context'
|
||||
import {
|
||||
resourceRequestSchema,
|
||||
scheduleRequestSchema,
|
||||
triggerRequestSchemas,
|
||||
variableRequestSchema
|
||||
triggerRequestSchemas
|
||||
} from '../workspaceToolsZod.gen'
|
||||
import {
|
||||
getWorkspaceItemKey,
|
||||
globalDraftStore,
|
||||
TRIGGER_KINDS,
|
||||
type AppDraftValue,
|
||||
type FlowDraftValue,
|
||||
@@ -76,7 +73,7 @@ import {
|
||||
type TriggerKind,
|
||||
type WorkspaceItem,
|
||||
type WorkspaceItemType
|
||||
} from './draftStore.svelte'
|
||||
} from './workspaceItems'
|
||||
import { buildFlowDeployRequestBody, buildScriptDeployRequestBody } from './deployRequests'
|
||||
import {
|
||||
deleteGlobalDraft,
|
||||
@@ -279,10 +276,6 @@ const writeTriggerSchema = z.object({
|
||||
)
|
||||
})
|
||||
|
||||
const writeResourceSchema = resourceRequestSchema
|
||||
|
||||
const writeVariableSchema = variableRequestSchema
|
||||
|
||||
const searchResourceTypesSchema = z.object({
|
||||
query: z.string().describe('Substring to match against resource type names.'),
|
||||
limit: z
|
||||
@@ -474,10 +467,10 @@ const initAppSchema = z.object({
|
||||
|
||||
const GLOBAL_SYSTEM_PROMPT = `You are Windmill's global workspace assistant.
|
||||
|
||||
You can inspect workspace scripts, flows, schedules, triggers, resources, variables, and apps, then create draft changes in the frontend AI draft store.
|
||||
You can inspect workspace scripts, flows, schedules, triggers, resources, variables, and apps, then create draft changes in the frontend AI draft store for supported item types.
|
||||
|
||||
Important rules:
|
||||
- write_{script,flow,schedule,trigger,resource,variable} create or overwrite drafts. They do not save, deploy, or mutate workspace items.
|
||||
- write_{script,flow,schedule,trigger} create or overwrite drafts. They do not save, deploy, or mutate workspace items.
|
||||
- edit_script and patch_flow_json apply small exact-text edits and save the result as a draft. Prefer them for localized changes; use write_* for large rewrites.
|
||||
- For flows specifically: read_workspace_item and patch_flow_json work on a COMPACT view where rawscript module bodies are replaced with the placeholder "inline_script.<moduleId>". Use read_flow_module_code / set_flow_module_code to inspect or overwrite an inline script body; use patch_flow_json for structural edits.
|
||||
- deploy_workspace_item persists a draft to the workspace via the real backend create/update API and removes the draft. Requires user confirmation. Only call after the user has reviewed the draft and explicitly asked to deploy.
|
||||
@@ -943,31 +936,6 @@ const triggerServices: Record<TriggerKind, TriggerService> = {
|
||||
}
|
||||
}
|
||||
|
||||
async function workspaceItemExists(
|
||||
type: WorkspaceItemType,
|
||||
path: string,
|
||||
workspace: string,
|
||||
triggerKind?: TriggerKind
|
||||
): Promise<boolean> {
|
||||
switch (type) {
|
||||
case 'script':
|
||||
return ScriptService.existsScriptByPath({ workspace, path })
|
||||
case 'flow':
|
||||
return FlowService.existsFlowByPath({ workspace, path })
|
||||
case 'schedule':
|
||||
return ScheduleService.existsSchedule({ workspace, path })
|
||||
case 'trigger':
|
||||
if (!triggerKind) return false
|
||||
return triggerServices[triggerKind].exists({ workspace, path })
|
||||
case 'resource':
|
||||
return ResourceService.existsResource({ workspace, path })
|
||||
case 'variable':
|
||||
return VariableService.existsVariable({ workspace, path })
|
||||
case 'app':
|
||||
return AppService.existsApp({ workspace, path })
|
||||
}
|
||||
}
|
||||
|
||||
async function readWorkspaceItem(
|
||||
type: WorkspaceItemType,
|
||||
path: string,
|
||||
@@ -1168,14 +1136,11 @@ ${getRawAppPrompt()}`
|
||||
}
|
||||
|
||||
function getResourceInstructions(): string {
|
||||
return `# Global draft resource & variable instructions
|
||||
return `# Resource & variable reference
|
||||
|
||||
- Global mode writes complete draft payloads only; it does not save, deploy, run, scaffold local files, or generate metadata.
|
||||
- A resource draft is a workspace item: \`{ type: 'resource', path, summary?, value, isDraft }\`. \`value\` is a CreateResource body: \`{ path, value, description?, resource_type, labels? }\` where the inner \`value\` is the resource type's data shape.
|
||||
- A variable draft is a workspace item: \`{ type: 'variable', path, summary?, value, isDraft }\`. \`value\` is a CreateVariable body: \`{ path, value, is_secret, description, account?, is_oauth?, expires_at?, labels? }\`.
|
||||
- For secret fields in a resource value, do NOT inline the raw secret. Create a Variable first with \`is_secret: true\`, then in the resource value reference it as \`"$var:path/to/variable"\`.
|
||||
- Global mode can inspect resources and variables, but cannot create resource or variable drafts until their editor-native UserDraft contracts are complete.
|
||||
- For secret fields in a resource value, do NOT inline the raw secret. Reference variables as \`"$var:path/to/variable"\`.
|
||||
- Reference formats inside resource values: \`$var:g/all/name\` (global), \`$var:u/user/name\` (user), \`$var:f/folder/name\` (folder). Reference another resource with \`$res:path/to/resource\`.
|
||||
- When deploying drafts that depend on each other (e.g., a resource and the variables it references), deploy the variables first.
|
||||
- Use \`search_resource_types\` to discover valid \`resource_type\` names and their JSON Schemas. Match the resource value to that schema.
|
||||
- For OAuth resources, the \`is_oauth: true\` flag is managed by Windmill's OAuth flow; global mode generally creates manual resources, not OAuth ones.
|
||||
|
||||
@@ -1476,59 +1441,13 @@ export const globalTools: Tool<{}>[] = [
|
||||
return deleteWorkspaceItem(parsed, ctx)
|
||||
}
|
||||
},
|
||||
{
|
||||
def: createToolDef(
|
||||
writeResourceSchema,
|
||||
'write_resource',
|
||||
'Create or overwrite an AI draft resource. Does not save or deploy. Reference secret values via $var:path/to/variable; create the variable separately with write_variable.',
|
||||
{ strict: false }
|
||||
),
|
||||
showDetails: true,
|
||||
streamArguments: true,
|
||||
showFade: true,
|
||||
fn: async (ctx) => {
|
||||
const parsed = writeResourceSchema.parse(ctx.args)
|
||||
return writeFallbackDraft(
|
||||
{
|
||||
type: 'resource',
|
||||
path: parsed.path,
|
||||
summary: parsed.description,
|
||||
value: parsed,
|
||||
isDraft: true
|
||||
},
|
||||
ctx
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
def: createToolDef(
|
||||
writeVariableSchema,
|
||||
'write_variable',
|
||||
'Create or overwrite an AI draft variable. Does not save or deploy. Use is_secret: true for secret values. After deploy, reference from a resource as $var:path/to/variable.',
|
||||
{ strict: false }
|
||||
),
|
||||
showDetails: true,
|
||||
streamArguments: true,
|
||||
showFade: true,
|
||||
fn: async (ctx) => {
|
||||
const parsed = writeVariableSchema.parse(ctx.args)
|
||||
return writeFallbackDraft(
|
||||
{
|
||||
type: 'variable',
|
||||
path: parsed.path,
|
||||
summary: parsed.description,
|
||||
value: parsed,
|
||||
isDraft: true
|
||||
},
|
||||
ctx
|
||||
)
|
||||
}
|
||||
},
|
||||
// TODO: Re-enable write_resource/write_variable tools after resource and
|
||||
// variable use self-contained editor-native UserDraft values.
|
||||
{
|
||||
def: createToolDef(
|
||||
searchResourceTypesSchema,
|
||||
'search_resource_types',
|
||||
'Search for resource types in the workspace by substring. Returns names, descriptions, and JSON Schemas — use this before write_resource to know what shape value should have.'
|
||||
'Search for resource types in the workspace by substring. Returns names, descriptions, and JSON Schemas.'
|
||||
),
|
||||
fn: async ({ args, workspace, toolId, toolCallbacks }) => {
|
||||
const parsed = searchResourceTypesSchema.parse(args)
|
||||
@@ -1705,7 +1624,7 @@ function finishDraftWrite(item: WorkspaceItem, exists: boolean, ctx: WriteDraftC
|
||||
{
|
||||
success: true,
|
||||
message: `${verb} AI draft ${item.type} "${item.path}". The workspace was not saved or deployed.`,
|
||||
item: item.type === 'variable' ? serializeWorkspaceItemForRead(item) : item
|
||||
item
|
||||
},
|
||||
null,
|
||||
2
|
||||
@@ -1871,20 +1790,6 @@ async function writeTriggerDraft(
|
||||
)
|
||||
}
|
||||
|
||||
async function writeFallbackDraft(item: WorkspaceItem, ctx: WriteDraftCtx): Promise<string> {
|
||||
const { workspace } = ctx
|
||||
startDraftWrite(ctx, item.type, item.path)
|
||||
|
||||
const existingDraft =
|
||||
getGlobalDraft(workspace, item.type, item.path, item.triggerKind) !== undefined
|
||||
const backendExists = existingDraft
|
||||
? false
|
||||
: await workspaceItemExists(item.type, item.path, workspace, item.triggerKind)
|
||||
const stored = globalDraftStore.setDraft(workspace, item)
|
||||
|
||||
return finishDraftWrite(stored, existingDraft || backendExists, ctx)
|
||||
}
|
||||
|
||||
function saveAppDraft(workspace: string, path: string, value: AppDraftValue): WorkspaceItem {
|
||||
UserDraft.save('raw_app', path, normalizeAppDraftValue(value), { workspace })
|
||||
return getRequiredGlobalDraft(workspace, 'app', path)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { Flow, NewScript, Script } from '$lib/gen/types.gen'
|
||||
import { buildFlowDeployRequestBody, buildScriptDeployRequestBody } from './deployRequests'
|
||||
import type { WorkspaceItem } from './draftStore.svelte'
|
||||
import type { WorkspaceItem } from './workspaceItems'
|
||||
|
||||
describe('global AI deploy request builders', () => {
|
||||
it('preserves existing script metadata while replacing draft-controlled fields', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Flow, NewScript, OpenFlowWPath, Script } from '$lib/gen/types.gen'
|
||||
import type { FlowDraftValue, WorkspaceItem } from './draftStore.svelte'
|
||||
import type { FlowDraftValue, WorkspaceItem } from './workspaceItems'
|
||||
|
||||
type ScriptWithDeployMetadata = Script & Partial<Pick<NewScript, 'assets' | 'cache_ignore_s3_path'>>
|
||||
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
import type {
|
||||
AzureTriggerData,
|
||||
CreateResource,
|
||||
CreateVariable,
|
||||
FlowValue,
|
||||
GcpTriggerData,
|
||||
NewHttpTrigger,
|
||||
NewKafkaTrigger,
|
||||
NewMqttTrigger,
|
||||
NewNatsTrigger,
|
||||
NewPostgresTrigger,
|
||||
NewSchedule,
|
||||
NewSqsTrigger,
|
||||
NewWebsocketTrigger,
|
||||
Policy,
|
||||
ScriptLang
|
||||
} from '$lib/gen/types.gen'
|
||||
|
||||
/**
|
||||
* Flow draft value. Mirrors what the backend's create/update flow API expects
|
||||
* — the OpenFlow value, plus the inputs schema and (optional) groups.
|
||||
*
|
||||
* Schema and groups are split out from FlowValue intentionally so that
|
||||
* deploy_workspace_item can preserve them through the draft → workspace
|
||||
* round-trip; an earlier version dropped them on every deploy.
|
||||
*/
|
||||
export type FlowDraftValue = {
|
||||
value: FlowValue
|
||||
schema?: Record<string, any> | null
|
||||
groups?: NonNullable<FlowValue['groups']> | null
|
||||
}
|
||||
|
||||
export const TRIGGER_KINDS = [
|
||||
'http',
|
||||
'websocket',
|
||||
'kafka',
|
||||
'nats',
|
||||
'postgres',
|
||||
'mqtt',
|
||||
'sqs',
|
||||
'gcp',
|
||||
'azure'
|
||||
] as const
|
||||
|
||||
export type TriggerKind = (typeof TRIGGER_KINDS)[number]
|
||||
|
||||
export type TriggerRequestBody =
|
||||
| NewHttpTrigger
|
||||
| NewWebsocketTrigger
|
||||
| NewKafkaTrigger
|
||||
| NewNatsTrigger
|
||||
| NewPostgresTrigger
|
||||
| NewMqttTrigger
|
||||
| NewSqsTrigger
|
||||
| GcpTriggerData
|
||||
| AzureTriggerData
|
||||
|
||||
export type WorkspaceItemType =
|
||||
| 'script'
|
||||
| 'flow'
|
||||
| 'schedule'
|
||||
| 'trigger'
|
||||
| 'resource'
|
||||
| 'variable'
|
||||
| 'app'
|
||||
|
||||
export type AppDraftValue = {
|
||||
summary?: string
|
||||
files: Record<string, string>
|
||||
runnables: Record<string, any>
|
||||
data?: any
|
||||
policy?: Policy
|
||||
custom_path?: string
|
||||
}
|
||||
|
||||
export type WorkspaceItem = {
|
||||
type: WorkspaceItemType
|
||||
path: string
|
||||
summary?: string
|
||||
language?: ScriptLang
|
||||
triggerKind?: TriggerKind
|
||||
value?:
|
||||
| string
|
||||
| FlowDraftValue
|
||||
| NewSchedule
|
||||
| TriggerRequestBody
|
||||
| CreateResource
|
||||
| CreateVariable
|
||||
| AppDraftValue
|
||||
isDraft: boolean
|
||||
}
|
||||
|
||||
export function getWorkspaceItemKey(
|
||||
type: WorkspaceItemType,
|
||||
path: string,
|
||||
triggerKind?: TriggerKind
|
||||
): string {
|
||||
if (type === 'trigger') {
|
||||
return `trigger:${triggerKind ?? ''}:${path}`
|
||||
}
|
||||
return `${type}:${path}`
|
||||
}
|
||||
|
||||
function clone<T>(value: T): T {
|
||||
return structuredClone($state.snapshot(value)) as T
|
||||
}
|
||||
|
||||
class GlobalDraftStore {
|
||||
private drafts = $state<Record<string, Record<string, WorkspaceItem>>>({})
|
||||
|
||||
private getWorkspaceDrafts(workspace: string): Record<string, WorkspaceItem> {
|
||||
return this.drafts[workspace] ?? {}
|
||||
}
|
||||
|
||||
private ensureWorkspaceDrafts(workspace: string): Record<string, WorkspaceItem> {
|
||||
if (!this.drafts[workspace]) {
|
||||
this.drafts[workspace] = {}
|
||||
}
|
||||
return this.drafts[workspace]
|
||||
}
|
||||
|
||||
listDrafts(workspace: string): WorkspaceItem[] {
|
||||
return Object.values(this.getWorkspaceDrafts(workspace)).map(clone)
|
||||
}
|
||||
|
||||
getDraft(
|
||||
workspace: string,
|
||||
type: WorkspaceItemType,
|
||||
path: string,
|
||||
triggerKind?: TriggerKind
|
||||
): WorkspaceItem | undefined {
|
||||
const draft = this.getWorkspaceDrafts(workspace)[getWorkspaceItemKey(type, path, triggerKind)]
|
||||
return draft ? clone(draft) : undefined
|
||||
}
|
||||
|
||||
setDraft(workspace: string, item: WorkspaceItem): WorkspaceItem {
|
||||
const stored: WorkspaceItem = { ...clone(item), isDraft: true }
|
||||
this.ensureWorkspaceDrafts(workspace)[
|
||||
getWorkspaceItemKey(item.type, item.path, item.triggerKind)
|
||||
] = stored
|
||||
return clone(stored)
|
||||
}
|
||||
|
||||
deleteDraft(
|
||||
workspace: string,
|
||||
type: WorkspaceItemType,
|
||||
path: string,
|
||||
triggerKind?: TriggerKind
|
||||
): void {
|
||||
const drafts = this.drafts[workspace]
|
||||
if (!drafts) return
|
||||
|
||||
delete drafts[getWorkspaceItemKey(type, path, triggerKind)]
|
||||
if (Object.keys(drafts).length === 0) {
|
||||
delete this.drafts[workspace]
|
||||
}
|
||||
}
|
||||
|
||||
clearDrafts(workspace: string): void {
|
||||
delete this.drafts[workspace]
|
||||
}
|
||||
|
||||
getScriptDraft(workspace: string, path: string): WorkspaceItem | undefined {
|
||||
return this.getDraft(workspace, 'script', path)
|
||||
}
|
||||
|
||||
getFlowDraft(workspace: string, path: string): WorkspaceItem | undefined {
|
||||
return this.getDraft(workspace, 'flow', path)
|
||||
}
|
||||
|
||||
getScheduleDraft(workspace: string, path: string): WorkspaceItem | undefined {
|
||||
return this.getDraft(workspace, 'schedule', path)
|
||||
}
|
||||
|
||||
getTriggerDraft(workspace: string, kind: TriggerKind, path: string): WorkspaceItem | undefined {
|
||||
return this.getDraft(workspace, 'trigger', path, kind)
|
||||
}
|
||||
|
||||
getResourceDraft(workspace: string, path: string): WorkspaceItem | undefined {
|
||||
return this.getDraft(workspace, 'resource', path)
|
||||
}
|
||||
|
||||
getVariableDraft(workspace: string, path: string): WorkspaceItem | undefined {
|
||||
return this.getDraft(workspace, 'variable', path)
|
||||
}
|
||||
|
||||
getAppDraft(workspace: string, path: string): WorkspaceItem | undefined {
|
||||
return this.getDraft(workspace, 'app', path)
|
||||
}
|
||||
}
|
||||
|
||||
export const globalDraftStore = new GlobalDraftStore()
|
||||
@@ -1,56 +0,0 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { globalDraftStore } from './draftStore.svelte'
|
||||
|
||||
const WORKSPACE_A = 'draft-store-test-a'
|
||||
const WORKSPACE_B = 'draft-store-test-b'
|
||||
|
||||
function clearTestDrafts() {
|
||||
globalDraftStore.clearDrafts(WORKSPACE_A)
|
||||
globalDraftStore.clearDrafts(WORKSPACE_B)
|
||||
}
|
||||
|
||||
describe('globalDraftStore', () => {
|
||||
beforeEach(clearTestDrafts)
|
||||
|
||||
it('lists and reads drafts only from the requested workspace', () => {
|
||||
globalDraftStore.setDraft(WORKSPACE_A, {
|
||||
type: 'script',
|
||||
path: 'f/shared/path',
|
||||
language: 'bun',
|
||||
value: 'export async function main() {}',
|
||||
isDraft: true
|
||||
})
|
||||
|
||||
expect(globalDraftStore.getDraft(WORKSPACE_A, 'script', 'f/shared/path')?.value).toBe(
|
||||
'export async function main() {}'
|
||||
)
|
||||
expect(globalDraftStore.getDraft(WORKSPACE_B, 'script', 'f/shared/path')).toBeUndefined()
|
||||
expect(globalDraftStore.listDrafts(WORKSPACE_A)).toHaveLength(1)
|
||||
expect(globalDraftStore.listDrafts(WORKSPACE_B)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('deletes and clears drafts only from the requested workspace', () => {
|
||||
globalDraftStore.setDraft(WORKSPACE_A, {
|
||||
type: 'flow',
|
||||
path: 'f/shared/path',
|
||||
value: { value: { modules: [] }, schema: null, groups: null },
|
||||
isDraft: true
|
||||
})
|
||||
globalDraftStore.setDraft(WORKSPACE_B, {
|
||||
type: 'flow',
|
||||
path: 'f/shared/path',
|
||||
value: { value: { modules: [] }, schema: { workspace: WORKSPACE_B }, groups: null },
|
||||
isDraft: true
|
||||
})
|
||||
|
||||
globalDraftStore.deleteDraft(WORKSPACE_A, 'flow', 'f/shared/path')
|
||||
|
||||
expect(globalDraftStore.getDraft(WORKSPACE_A, 'flow', 'f/shared/path')).toBeUndefined()
|
||||
expect(globalDraftStore.getDraft(WORKSPACE_B, 'flow', 'f/shared/path')).toBeDefined()
|
||||
|
||||
globalDraftStore.clearDrafts(WORKSPACE_B)
|
||||
|
||||
expect(globalDraftStore.listDrafts(WORKSPACE_A)).toHaveLength(0)
|
||||
expect(globalDraftStore.listDrafts(WORKSPACE_B)).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
@@ -2,13 +2,12 @@ import type { Flow, NewSchedule, NewScript } from '$lib/gen/types.gen'
|
||||
import { UserDraft, type UserDraftItemKind, type UserDraftListEntry } from '$lib/userDraft.svelte'
|
||||
import {
|
||||
getWorkspaceItemKey,
|
||||
globalDraftStore,
|
||||
type AppDraftValue,
|
||||
type TriggerRequestBody,
|
||||
type TriggerKind,
|
||||
type WorkspaceItem,
|
||||
type WorkspaceItemType
|
||||
} from './draftStore.svelte'
|
||||
} from './workspaceItems'
|
||||
|
||||
type SharedWorkspaceItemType = 'script' | 'flow' | 'app' | 'schedule' | 'trigger'
|
||||
|
||||
@@ -53,6 +52,11 @@ const SHARED_DRAFT_KINDS = [
|
||||
] as const satisfies UserDraftItemKind[]
|
||||
const DEFAULT_APP_DATA = { tables: [], datatable: undefined, schema: undefined }
|
||||
|
||||
// TODO: Add resource and variable here only after their editor-native
|
||||
// UserDraft contracts are self-contained. Resource drafts currently need
|
||||
// resource_type outside the draft value, and new variable drafts do not have
|
||||
// a complete persisted/live empty-path contract.
|
||||
|
||||
function clone<T>(value: T): T {
|
||||
return structuredClone(value) as T
|
||||
}
|
||||
@@ -221,19 +225,14 @@ export function getGlobalDraft(
|
||||
triggerKind?: TriggerKind
|
||||
): WorkspaceItem | undefined {
|
||||
if (isSharedWorkspaceItemType(type)) {
|
||||
const shared = getSharedDraft(workspace, type, path, triggerKind)
|
||||
if (shared) return shared
|
||||
return getSharedDraft(workspace, type, path, triggerKind)
|
||||
}
|
||||
return globalDraftStore.getDraft(workspace, type, path, triggerKind)
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function listGlobalDrafts(workspace: string): WorkspaceItem[] {
|
||||
const drafts = new Map<string, WorkspaceItem>()
|
||||
|
||||
for (const draft of globalDraftStore.listDrafts(workspace)) {
|
||||
drafts.set(getWorkspaceItemKey(draft.type, draft.path, draft.triggerKind), draft)
|
||||
}
|
||||
|
||||
for (const entry of UserDraft.list({ workspace, itemKinds: [...SHARED_DRAFT_KINDS] })) {
|
||||
const draft = sharedDraftEntryToWorkspaceItem(entry)
|
||||
if (!draft) continue
|
||||
@@ -252,14 +251,12 @@ export function deleteGlobalDraft(
|
||||
if (isSharedWorkspaceItemType(type)) {
|
||||
deleteSharedDraft(workspace, type, path, triggerKind)
|
||||
}
|
||||
globalDraftStore.deleteDraft(workspace, type, path, triggerKind)
|
||||
}
|
||||
|
||||
export function clearGlobalDrafts(workspace: string): void {
|
||||
for (const draft of UserDraft.list({ workspace, itemKinds: [...SHARED_DRAFT_KINDS] })) {
|
||||
UserDraft.remove(draft.itemKind, draft.path, { workspace })
|
||||
}
|
||||
globalDraftStore.clearDrafts(workspace)
|
||||
}
|
||||
|
||||
export function triggerKindToUserDraftKind(kind: TriggerKind): UserDraftItemKind {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import type {
|
||||
AzureTriggerData,
|
||||
CreateResource,
|
||||
CreateVariable,
|
||||
FlowValue,
|
||||
GcpTriggerData,
|
||||
NewHttpTrigger,
|
||||
NewKafkaTrigger,
|
||||
NewMqttTrigger,
|
||||
NewNatsTrigger,
|
||||
NewPostgresTrigger,
|
||||
NewSchedule,
|
||||
NewSqsTrigger,
|
||||
NewWebsocketTrigger,
|
||||
Policy,
|
||||
ScriptLang
|
||||
} from '$lib/gen/types.gen'
|
||||
|
||||
/**
|
||||
* Flow draft value. Mirrors what the backend's create/update flow API expects
|
||||
* -- the OpenFlow value, plus the inputs schema and (optional) groups.
|
||||
*
|
||||
* Schema and groups are split out from FlowValue intentionally so that
|
||||
* deploy_workspace_item can preserve them through the draft -> workspace
|
||||
* round-trip; an earlier version dropped them on every deploy.
|
||||
*/
|
||||
export type FlowDraftValue = {
|
||||
value: FlowValue
|
||||
schema?: Record<string, any> | null
|
||||
groups?: NonNullable<FlowValue['groups']> | null
|
||||
}
|
||||
|
||||
export const TRIGGER_KINDS = [
|
||||
'http',
|
||||
'websocket',
|
||||
'kafka',
|
||||
'nats',
|
||||
'postgres',
|
||||
'mqtt',
|
||||
'sqs',
|
||||
'gcp',
|
||||
'azure'
|
||||
] as const
|
||||
|
||||
export type TriggerKind = (typeof TRIGGER_KINDS)[number]
|
||||
|
||||
export type TriggerRequestBody =
|
||||
| NewHttpTrigger
|
||||
| NewWebsocketTrigger
|
||||
| NewKafkaTrigger
|
||||
| NewNatsTrigger
|
||||
| NewPostgresTrigger
|
||||
| NewMqttTrigger
|
||||
| NewSqsTrigger
|
||||
| GcpTriggerData
|
||||
| AzureTriggerData
|
||||
|
||||
export type WorkspaceItemType =
|
||||
| 'script'
|
||||
| 'flow'
|
||||
| 'schedule'
|
||||
| 'trigger'
|
||||
| 'resource'
|
||||
| 'variable'
|
||||
| 'app'
|
||||
|
||||
export type AppDraftValue = {
|
||||
summary?: string
|
||||
files: Record<string, string>
|
||||
runnables: Record<string, any>
|
||||
data?: any
|
||||
policy?: Policy
|
||||
custom_path?: string
|
||||
}
|
||||
|
||||
export type WorkspaceItem = {
|
||||
type: WorkspaceItemType
|
||||
path: string
|
||||
summary?: string
|
||||
language?: ScriptLang
|
||||
triggerKind?: TriggerKind
|
||||
value?:
|
||||
| string
|
||||
| FlowDraftValue
|
||||
| NewSchedule
|
||||
| TriggerRequestBody
|
||||
| CreateResource
|
||||
| CreateVariable
|
||||
| AppDraftValue
|
||||
isDraft: boolean
|
||||
}
|
||||
|
||||
export function getWorkspaceItemKey(
|
||||
type: WorkspaceItemType,
|
||||
path: string,
|
||||
triggerKind?: TriggerKind
|
||||
): string {
|
||||
if (type === 'trigger') {
|
||||
return `trigger:${triggerKind ?? ''}:${path}`
|
||||
}
|
||||
return `${type}:${path}`
|
||||
}
|
||||
Reference in New Issue
Block a user