const DEFAULT_API_PORT = process.env.SFERA_API_PORT ?? process.env.NEXT_PUBLIC_SFERA_API_PORT ?? "8000"; const FALLBACK_API_URL = "http://192.168.200.60:8000"; export type AdminSummary = { indexed_projects: number; stored_snapshots: number; knowledge_records: number; knowledge_packs: number; users: number; tasks: number; comments: number; ownership: number; privacy_markers: number; ai_usage_records: number; jobs: number; metrics: number; marketplace_packages: number; }; export type Neo4jStatus = { status: string; uri: string; nodes: number; edges: number; }; export type StoredSnapshot = { project_id: string; snapshot_id: string; snapshot_hash: string | null; path: string; }; export type ProjectSummary = { project_id: string; name: string; status: string; has_snapshot: boolean; }; export type AiUsageSummary = { request_count: number; prompt_tokens: number; completion_tokens: number; total_tokens: number; cost: number; currency: string; }; export type AiPolicy = { policy: { privacy_mode: string; allow_code_context: boolean; allow_external_calls: boolean; token_limit_per_day: number | null; }; used_tokens: number; remaining_tokens: number | null; }; export type Health = { status: string; }; export type SnapshotSummary = { snapshot_id: string; project_id: string; snapshot_hash: string | null; node_count: number; edge_count: number; diagnostics_count: number; unresolved_references_count: number; }; export type ReviewFinding = { finding_id?: string; title: string; severity: "INFO" | "WARNING" | "ERROR" | string; message: string; source_path?: string | null; line_start?: number | null; }; export type WorkspaceModuleRoutine = { name: string; kind?: string; line_start?: number | null; line_end?: number | null; export?: boolean; }; export type WorkspaceModuleSource = { name: string; qualified_name: string; module_role: string; source_path: string; source_text: string; routines_count: number; routines: WorkspaceModuleRoutine[]; }; export type BslCompletionItem = { label: string; kind: string; detail?: string | null; insert_text?: string | null; }; export type ProjectReport = { project_id: string; snapshot_id: string; node_count: number; edge_count: number; procedure_count: number; query_count: number; write_count: number; role_count: number; access_grant_count: number; object_attribute_count: number; tabular_section_count: number; unsecured_object_count: number; unsecured_objects: string[]; integration_count: number; outbound_integration_count: number; diagnostic_count: number; ownership_count: number; unowned_object_count: number; unowned_objects: string[]; privacy_marker_count: number; sensitive_candidate_count: number; unclassified_sensitive_count: number; ai_usage_request_count: number; ai_usage_total_tokens: number; ai_usage_cost: number; }; export type NamedNode = { lineage_id: string; kind: string; name: string; qualified_name: string; }; export type SourceLocation = { source_path: string | null; line_start: number | null; line_end: number | null; column_start: number | null; column_end: number | null; }; export type SymbolResult = { node: NamedNode; source: SourceLocation; }; export type SymbolReference = { edge_id: string; kind: string; direction: "incoming" | "outgoing" | string; source: NamedNode | null; target: NamedNode | null; location: SourceLocation | null; attributes: Record; }; export type SymbolReferences = { symbol: SymbolResult; references: SymbolReference[]; }; export type KnowledgeSchemaCoverage = { covered_count: number; uncovered_count: number; uncovered: NamedNode[]; }; export type FormSemantics = { form: NamedNode; commands: NamedNode[]; elements: NamedNode[]; command_handlers: Record; }; export type ObjectUi = { object: NamedNode; forms: FormSemantics[]; }; export type TabularSectionColumns = { tabular_section: NamedNode; columns: NamedNode[]; }; export type ObjectSchema = { object: NamedNode; attributes: NamedNode[]; tabular_sections: TabularSectionColumns[]; }; export type ObjectImpact = { object_name: string; object: NamedNode; modules: NamedNode[]; routines: NamedNode[]; forms: NamedNode[]; commands: NamedNode[]; attributes: NamedNode[]; tabular_sections: NamedNode[]; tabular_section_columns: Record; roles: NamedNode[]; role_access: Array<{ role: NamedNode; permissions: Record }>; jobs: NamedNode[]; callees: NamedNode[]; query_tables: NamedNode[]; writes: NamedNode[]; }; export type IntegrationEndpoint = { endpoint_id: string; name: string; kind: string; direction: string; owner: string | null; }; export type ScheduledJob = { job_id: string; name: string; routine_name: string; schedule: string | null; }; export type MetadataTypeSpec = { code: string; russian_name: string; tree_branch: string; icon: string; description?: string; documentation_url?: string; child_groups: string[]; module_kinds: string[]; properties?: string[]; context_actions?: string[]; }; export type MetadataChildObjectSpec = { code: string; russian_name: string; parent_groups: string[]; description: string; documentation_url: string; }; export type MetadataCatalog = { platform_family: string; source: string; common_branch_children: string[]; types: MetadataTypeSpec[]; child_object_types?: MetadataChildObjectSpec[]; }; export type MetadataTreeNode = { id: string; label: string; kind: string; icon: string; qualified_name: string | null; count: number; loaded_count: number; has_more: boolean; children: MetadataTreeNode[]; }; export type ProjectMetadataTree = { project_id: string; root: MetadataTreeNode; }; export type MetadataTreeChildren = { project_id: string; parent_id: string; offset: number; limit: number; total: number; has_more: boolean; children: MetadataTreeNode[]; }; export type MetadataTreeSearch = { project_id: string; q: string; total: number; results: MetadataTreeNode[]; }; export type MetadataTreePathStep = { parent_id: string; child_id: string; offset: number; }; export type MetadataTreePath = { project_id: string; node_id: string; path: string[]; steps: MetadataTreePathStep[]; }; export type SirNode = { lineage_id: string; semantic_id?: string; kind: string; name: string; qualified_name: string; source_path?: string | null; line_start?: number | null; line_end?: number | null; attributes?: Record; }; export type SirExport = { project_id: string; snapshot_id: string; snapshot_hash: string | null; nodes: SirNode[]; edges: unknown[]; diagnostics: unknown[]; }; export type FlowchartNode = { id: string; label: string; kind: string; qualified_name?: string | null; count: number; level: number; expandable: boolean; }; export type FlowchartEdge = { id: string; source: string; target: string; kind: string; label: string; count: number; }; export type ProjectFlowchart = { project_id: string; mode: "overview" | "focus" | string; focus?: string | null; total_nodes: number; total_edges: number; nodes: FlowchartNode[]; edges: FlowchartEdge[]; }; export type AuthoringContext = { project_id: string; object: NamedNode | null; routine: NamedNode | null; local_variables: string[]; parameters: string[]; object_attributes: NamedNode[]; tabular_sections: NamedNode[]; form_elements: NamedNode[]; commands: NamedNode[]; query_tables: NamedNode[]; writes: NamedNode[]; available_methods: string[]; review_findings: ReviewFinding[]; }; export type AuthoringCompletionPreview = { allowed: boolean; insert_text: string; semantic_diff: Array<{ kind: string; text: string }>; checks: Array<{ name: string; status: string; message: string }>; context: AuthoringContext; }; export type AuthoringSemanticDiffPreviewRequest = { object_name: string; routine_name?: string | null; original_text: string; proposed_text: string; source_path?: string | null; task_id?: string | null; session_id?: string | null; user_id?: string | null; }; export type AuthoringSemanticDiffPreview = { project_id: string; target: NamedNode | null; changed: boolean; added_lines: number; removed_lines: number; semantic_diff: Array<{ kind: string; text: string }>; affected_nodes: NamedNode[]; checks: Array<{ name: string; status: string; message: string }>; version_preview: { lineage_id: string; semantic_id: string; current_version_id: string | null; next_version_id: string; object_hash: string; task_id: string | null; session_id: string | null; apply_available: boolean; } | null; }; export type AuthoringApplyChangeSetRequest = { target_lineage_id?: string | null; object_name?: string | null; routine_name?: string | null; source_path?: string | null; original_text: string; proposed_text: string; task_id?: string | null; session_id?: string | null; user_id?: string | null; estimated_tokens?: number; expected_next_version_id: string; approved_by: string; approval_note?: string | null; apply_to_production: false; }; export type SemanticObjectVersion = { version_id: string; lineage_id: string; semantic_id: string; object_hash: string; task_id: string | null; session_id: string | null; parent_version_id: string | null; payload: Record; created_at: string; }; export type ObjectVersionSummary = { version_id: string; lineage_id: string; semantic_id: string; object_hash: string; parent_version_id: string | null; task_id: string | null; session_id: string | null; }; export type SemanticObjectDiffEntry = { path: string; kind: string; before?: unknown | null; after?: unknown | null; }; export type SemanticObjectDiff = { from_version_id: string; to_version_id: string; lineage_id: string; changed: boolean; entries: SemanticObjectDiffEntry[]; }; export type AuthoringApplyChangeSetResponse = { project_id: string; status: string; change_id: string; version: SemanticObjectVersion; preview: AuthoringSemanticDiffPreview; persisted_path: string; production_applied: boolean; }; export type AuthoringRollbackPreview = { project_id: string; change_id: string; original_version_id: string; rollback_version_id: string; target: NamedNode | null; semantic_diff: Array<{ kind: string; text: string }>; checks: Array<{ name: string; status: string; message: string }>; apply_available: boolean; }; export type AuthoringApplyRollbackRequest = { expected_rollback_version_id: string; approved_by: string; approval_note?: string | null; task_id?: string | null; session_id?: string | null; apply_to_production: false; }; export type AuthoringApplyRollbackResponse = { project_id: string; status: string; change_id: string; rollback_change_id: string; version: SemanticObjectVersion; preview: AuthoringRollbackPreview; persisted_path: string; production_applied: boolean; }; export type AuthoringMetadataAttributeDraft = { name: string; type: string; synonym?: string | null; required?: boolean; }; export type AuthoringMetadataTabularSectionDraft = { name: string; synonym?: string | null; attributes: AuthoringMetadataAttributeDraft[]; }; export type AuthoringMetadataCommandDraft = { name: string; handler?: string | null; }; export type AuthoringMetadataObjectDraft = { object_kind: string; name: string; synonym?: string | null; attributes: AuthoringMetadataAttributeDraft[]; tabular_sections: AuthoringMetadataTabularSectionDraft[]; forms?: string[]; commands?: AuthoringMetadataCommandDraft[]; task_id?: string | null; session_id?: string | null; user_id?: string | null; }; export type AuthoringMetadataObjectPreview = { project_id: string; target: NamedNode; changed: boolean; added_lines: number; removed_lines: number; semantic_diff: Array<{ kind: string; text: string }>; checks: Array<{ name: string; status: string; message: string }>; version_preview: NonNullable; }; export type AuthoringApplyMetadataObjectRequest = AuthoringMetadataObjectDraft & { expected_next_version_id: string; approved_by: string; approval_note?: string | null; apply_to_production: false; }; export type AuthoringApplyMetadataObjectResponse = { project_id: string; status: string; change_id: string; version: SemanticObjectVersion; preview: AuthoringMetadataObjectPreview; persisted_path: string; production_applied: boolean; }; export type AuthoringChangeSummary = { change_id: string; project_id: string; status: string; target: NamedNode | null; version_id: string; approved_by: string; approval_note: string | null; task_id: string | null; session_id: string | null; added_lines: number; removed_lines: number; production_applied: boolean; }; export type AuthoringSessionContext = { user_id: string; task_id: string; session_id: string; }; export function resolveApiUrl(hostHeader?: string | null) { const configuredUrl = process.env.SFERA_API_URL ?? process.env.NEXT_PUBLIC_SFERA_API_URL; if (configuredUrl) { return configuredUrl; } const host = hostHeader?.split(",")[0]?.trim(); if (!host) { return FALLBACK_API_URL; } const protocol = host.startsWith("localhost") || host.startsWith("127.0.0.1") ? "http" : "http"; const hostname = host.startsWith("[") ? host.slice(0, host.indexOf("]") + 1) : host.replace(/:\d+$/, ""); const apiHostname = hostname === "127.0.0.1" || hostname === "[::1]" ? "localhost" : hostname; return `${protocol}://${apiHostname}:${DEFAULT_API_PORT}`; } async function getJson(apiUrl: string, path: string): Promise { const response = await fetch(`${apiUrl}${path}`, { cache: "no-store", headers: { Accept: "application/json" } }); if (!response.ok) { throw new Error(await formatResponseError(response)); } return response.json() as Promise; } async function postJson(apiUrl: string, path: string, body: unknown): Promise { const response = await fetch(`${apiUrl}${path}`, { method: "POST", cache: "no-store", headers: { Accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify(body) }); if (!response.ok) { throw new Error(await formatResponseError(response)); } return response.json() as Promise; } async function formatResponseError(response: Response) { const status = `${response.status} ${response.statusText}`; const detail = await readResponseErrorDetail(response); return detail ? `${status}: ${detail}` : status; } async function readResponseErrorDetail(response: Response) { const contentType = response.headers.get("content-type") ?? ""; if (contentType.includes("application/json")) { try { const payload = (await response.clone().json()) as unknown; return stringifyErrorPayload(payload); } catch { return ""; } } try { return (await response.text()).trim(); } catch { return ""; } } function stringifyErrorPayload(payload: unknown): string { if (typeof payload === "string") { return payload; } if (payload && typeof payload === "object" && "detail" in payload) { const detail = (payload as { detail: unknown }).detail; if (typeof detail === "string") { return detail; } if (detail !== null && detail !== undefined) { return JSON.stringify(detail); } } if (payload !== null && payload !== undefined) { return JSON.stringify(payload); } return ""; } async function getOptionalJson(apiUrl: string, path: string, fallback: T): Promise { try { return await getJson(apiUrl, path); } catch { return fallback; } } async function postOptionalJson(apiUrl: string, path: string, body: unknown, fallback: T): Promise { try { return await postJson(apiUrl, path, body); } catch { return fallback; } } function ideAuthoringSessionContext(projectId: string): AuthoringSessionContext { const safeProjectId = projectId.replace(/[^A-Za-z0-9_.-]/g, "-"); return { user_id: "ide.developer", task_id: `task.ide.${safeProjectId}`, session_id: `session.ide.${safeProjectId}` }; } export async function ensureAuthoringSession(projectId: string, apiUrl = resolveApiUrl()): Promise { const context = ideAuthoringSessionContext(projectId); await postJson(apiUrl, "/collaboration/users", { user_id: context.user_id, display_name: "SFERA IDE" }); await postJson(apiUrl, `/security/users/${encodeURIComponent(context.user_id)}/roles/developer`, {}); await postJson(apiUrl, "/collaboration/tasks", { task_id: context.task_id, project_id: projectId, title: "SFERA IDE authoring", status: "IN_PROGRESS", assignee_user_id: context.user_id }); await postJson(apiUrl, "/collaboration/sessions", { session: { session_id: context.session_id, task_id: context.task_id, user_id: context.user_id } }); return context; } export async function getDashboardData(apiUrl = resolveApiUrl()) { const [health, summary, snapshots, neo4j, aiUsage, aiPolicy] = await Promise.all([ getJson(apiUrl, "/health"), getJson(apiUrl, "/admin/summary"), getJson(apiUrl, "/storage/snapshots"), getJson(apiUrl, "/graph/neo4j/status"), getJson(apiUrl, "/ai/usage/summary"), getJson(apiUrl, "/ai/policy") ]); return { health, summary, snapshots, neo4j, aiUsage, aiPolicy, apiUrl }; } export async function getApiHealth(apiUrl = resolveApiUrl()) { return getJson(apiUrl, "/health"); } export async function getStoredSnapshots(apiUrl = resolveApiUrl()) { return getJson(apiUrl, "/storage/snapshots"); } export async function getProjects(apiUrl = resolveApiUrl()) { return getJson(apiUrl, "/projects"); } export async function searchProjectSymbols( projectId: string, query: string, options: { kind?: string; limit?: number; apiUrl?: string } = {} ) { const apiUrl = options.apiUrl ?? resolveApiUrl(); const params = new URLSearchParams({ q: query, limit: String(options.limit ?? 50) }); if (options.kind) { params.set("kind", options.kind); } return getJson(apiUrl, `/projects/${projectId}/symbols?${params.toString()}`); } export async function getProjectSymbolDefinition( projectId: string, lineageId: string, apiUrl = resolveApiUrl() ) { const params = new URLSearchParams({ lineage_id: lineageId }); return getJson(apiUrl, `/projects/${projectId}/symbols/definition?${params.toString()}`); } export async function getProjectSymbolReferences( projectId: string, lineageId: string, options: { direction?: "incoming" | "outgoing" | "both"; apiUrl?: string } = {} ) { const apiUrl = options.apiUrl ?? resolveApiUrl(); const params = new URLSearchParams({ lineage_id: lineageId, direction: options.direction ?? "incoming" }); return getJson(apiUrl, `/projects/${projectId}/symbols/references?${params.toString()}`); } export async function getBslCompletions( projectId: string, options: { apiUrl?: string; receiver?: string | null; q?: string | null; qualifiedName?: string | null; limit?: number } = {} ) { const apiUrl = options.apiUrl ?? resolveApiUrl(); const params = new URLSearchParams({ q: options.q ?? "", limit: String(options.limit ?? 80) }); if (options.receiver) { params.set("receiver", options.receiver); } if (options.qualifiedName) { params.set("qualified_name", options.qualifiedName); } return getJson(apiUrl, `/projects/${projectId}/bsl/completions?${params.toString()}`); } export async function getProjectWorkspaceData(projectId: string, apiUrl = resolveApiUrl(), selectedRoutine?: string | null, activeMode?: string | null) { const selectedRoutineName = selectedRoutine?.trim() ?? null; const workspaceMode = activeMode?.trim() || "overview"; const isModuleMode = workspaceMode === "module"; const needsObjectPanels = !isModuleMode; const needsFlowchart = workspaceMode === "flowchart"; const fallbackSourceText = [ "// Код модуля не загружен.", "// Выберите реальный модуль в дереве проекта или выполните индексирование с исходниками BSL." ].join("\n"); const snapshot = await getOptionalJson(apiUrl, `/projects/${projectId}/snapshot`, null); const isLargeProject = (snapshot?.node_count ?? 0) > 20000; const [metadataCatalog, metadataTree, exportSnapshot] = await Promise.all([ isLargeProject ? Promise.resolve(null) : getOptionalJson(apiUrl, "/metadata/catalog", null), getOptionalJson(apiUrl, `/projects/${projectId}/metadata/tree?object_limit_per_branch=${isLargeProject ? 0 : 200}`, null), isLargeProject ? Promise.resolve(null) : getOptionalJson(apiUrl, `/projects/${projectId}/snapshot/export`, null) ]); const flowchart = needsFlowchart ? await getOptionalJson(apiUrl, `/projects/${projectId}/flowchart?limit=80`, null) : null; const selectedMetadataSearch = selectedRoutineName ? await getOptionalJson( apiUrl, `/projects/${projectId}/metadata/tree/search?q=${encodeURIComponent(selectedRoutineName)}&limit=1`, null ) : null; let selectedTreeNode = selectedMetadataSearch?.results[0] ?? findFirstModuleOwnerNode(metadataTree?.root) ?? null; if (!selectedRoutineName && !selectedTreeNode) { const firstCommonModulePage = await getOptionalJson( apiUrl, `/projects/${projectId}/metadata/tree/children?${new URLSearchParams({ node_id: "common.Общие модули", offset: "0", limit: "1" })}`, null ); selectedTreeNode = firstCommonModulePage?.children[0] ?? null; } const selectedObjectName = selectedRoutineName ?? selectedTreeNode?.qualified_name ?? null; const selectedObjectModules = selectedObjectName ? getOptionalJson( apiUrl, `/projects/${projectId}/normalized/object/modules?${new URLSearchParams({ qualified_name: selectedObjectName })}`, [] ) : Promise.resolve([]); const [selectedObjectSchema, selectedObjectUi, selectedObjectImpact, report, review, coverage, forms, integrations, jobs, authoringChanges, projectVersions, objectModules] = await Promise.all([ needsObjectPanels && selectedObjectName ? getOptionalJson( apiUrl, `/projects/${projectId}/objects/schema/${encodeURIComponent(selectedObjectName)}`, null ) : Promise.resolve(null), needsObjectPanels && selectedObjectName ? getOptionalJson( apiUrl, `/projects/${projectId}/objects/ui/${encodeURIComponent(selectedObjectName)}`, null ) : Promise.resolve(null), needsObjectPanels && selectedObjectName ? getOptionalJson( apiUrl, `/projects/${projectId}/objects/impact/${encodeURIComponent(selectedObjectName)}`, null ) : Promise.resolve(null), isLargeProject ? Promise.resolve(null) : getOptionalJson(apiUrl, `/projects/${projectId}/report`, null), isLargeProject ? Promise.resolve([]) : getOptionalJson(apiUrl, `/projects/${projectId}/review`, []), isLargeProject ? Promise.resolve(null) : getOptionalJson(apiUrl, `/projects/${projectId}/knowledge/schema-coverage`, null), isLargeProject ? Promise.resolve([]) : getOptionalJson(apiUrl, `/projects/${projectId}/ui/forms`, []), isLargeProject ? Promise.resolve([]) : getOptionalJson(apiUrl, `/projects/${projectId}/integrations`, []), isLargeProject ? Promise.resolve([]) : getOptionalJson(apiUrl, `/projects/${projectId}/jobs/scheduled`, []), getOptionalJson(apiUrl, `/projects/${projectId}/authoring/changes`, []), isLargeProject ? Promise.resolve([]) : getOptionalJson(apiUrl, `/projects/${projectId}/versions`, []) , selectedObjectModules ]); const selectedObjectModule = selectedObjectName && objectModules.length > 0 ? objectModules.find((module) => selectedRoutineName ? module.qualified_name === selectedRoutineName || module.source_path === selectedRoutineName || module.routines.some((routine) => routine.name === selectedRoutineName || `${module.qualified_name}.${routine.name}` === selectedRoutineName) : false ) ?? objectModules[0] : null; const selectedModuleRoutine = selectedRoutineName ? selectedObjectModule?.routines.find((routine) => routine.name === selectedRoutineName || `${selectedObjectModule.qualified_name}.${routine.name}` === selectedRoutineName )?.name ?? selectedObjectModule?.routines[0]?.name ?? null : selectedObjectModule?.routines[0]?.name ?? null; const snapshotModule = !selectedObjectModule ? firstSnapshotModule(exportSnapshot) : null; const authoringSourceText = selectedObjectModule?.source_text?.trim() ? selectedObjectModule.source_text : typeof snapshotModule?.attributes?.source_text === "string" ? snapshotModule.attributes.source_text : ""; const editorSelectedObject = selectedObjectName ?? snapshotModule?.qualified_name ?? null; const editorSelectedRoutine = selectedModuleRoutine ?? selectedRoutineName ?? null; const authoringSession = selectedObjectName && authoringSourceText ? await ensureAuthoringSession(projectId, apiUrl).catch(() => null) : null; const authoringPreview = needsObjectPanels && selectedObjectName && authoringSourceText ? await postOptionalJson( apiUrl, `/projects/${projectId}/authoring/completion-preview`, { object_name: selectedObjectName, routine_name: selectedModuleRoutine, source_path: selectedObjectModule?.source_path ?? null, source_text: authoringSourceText, user_id: authoringSession?.user_id ?? null }, null ) : null; const authoringProposedText = authoringPreview?.insert_text ? `${authoringSourceText}\n\n${authoringPreview.insert_text}` : null; const semanticDiffPreview = selectedObjectName && authoringSourceText && authoringProposedText ? await postOptionalJson( apiUrl, `/projects/${projectId}/authoring/semantic-diff-preview`, { object_name: selectedObjectName, routine_name: selectedModuleRoutine, original_text: authoringSourceText, proposed_text: authoringProposedText, source_path: selectedObjectModule?.source_path ?? null, task_id: authoringSession?.task_id ?? null, session_id: authoringSession?.session_id ?? null, user_id: authoringSession?.user_id ?? null }, null ) : null; return { projectId, apiUrl, metadataCatalog, metadataTree, selectedMetadataNode: selectedMetadataSearch?.results[0] ?? null, selectedObjectSchema, selectedObjectUi, selectedObjectImpact, snapshot, exportSnapshot, report, review, coverage, forms, integrations, jobs, flowchart, authoringPreview, semanticDiffPreview, authoringChanges, projectVersions, editorSourceText: authoringSourceText || fallbackSourceText, editorProposedText: authoringProposedText, editorSelectedObject, editorSelectedRoutine, editorModules: objectModules, editorModuleName: selectedObjectModule?.name ?? snapshotModule?.name ?? null, editorSourcePath: selectedObjectModule?.source_path ?? snapshotModule?.source_path ?? null }; } const moduleOwnerKinds = new Set([ "CATALOG", "DOCUMENT", "REGISTER", "COMMON_MODULE", "CONSTANT", "DOCUMENT_JOURNAL", "ENUM", "REPORT", "DATA_PROCESSOR", "CHART_OF_CHARACTERISTIC_TYPES", "CHART_OF_ACCOUNTS", "CHART_OF_CALCULATION_TYPES", "EXCHANGE_PLAN", "EXTERNAL_DATA_SOURCE", "SCHEDULED_JOB", "BUSINESS_PROCESS", "TASK", "HTTP_SERVICE", "WEB_SERVICE", "XDTO_PACKAGE", "EVENT_SUBSCRIPTION" ]); function findFirstModuleOwnerNode(root?: MetadataTreeNode): MetadataTreeNode | null { if (!root) { return null; } const stack = [root]; while (stack.length > 0) { const node = stack.shift()!; if (node.qualified_name && moduleOwnerKinds.has(node.kind)) { return node; } stack.push(...node.children); } return null; } function firstSnapshotModule(snapshot: SirExport | null): SirNode | null { return snapshot?.nodes.find((node) => node.kind === "MODULE" && typeof node.attributes?.source_text === "string") ?? null; } export async function getMetadataTreeChildren( projectId: string, nodeId: string, options: { offset?: number; limit?: number; apiUrl?: string } = {} ) { const apiUrl = options.apiUrl ?? resolveApiUrl(); const params = new URLSearchParams({ node_id: nodeId, offset: String(options.offset ?? 0), limit: String(options.limit ?? 80) }); return getJson(apiUrl, `/projects/${projectId}/metadata/tree/children?${params.toString()}`); } export async function searchMetadataTree( projectId: string, query: string, options: { limit?: number; apiUrl?: string } = {} ) { const apiUrl = options.apiUrl ?? resolveApiUrl(); const params = new URLSearchParams({ q: query, limit: String(options.limit ?? 80) }); return getJson(apiUrl, `/projects/${projectId}/metadata/tree/search?${params.toString()}`); } export async function getMetadataTreePath( projectId: string, nodeId: string, options: { apiUrl?: string } = {} ) { const apiUrl = options.apiUrl ?? resolveApiUrl(); const params = new URLSearchParams({ node_id: nodeId }); return getJson(apiUrl, `/projects/${projectId}/metadata/tree/path?${params.toString()}`); } export async function getProjectFlowchart( projectId: string, options: { apiUrl?: string; focus?: string | null; depth?: number; limit?: number } = {} ) { const apiUrl = options.apiUrl ?? resolveApiUrl(); const params = new URLSearchParams(); if (options.focus) { params.set("focus", options.focus); } if (options.depth) { params.set("depth", String(options.depth)); } if (options.limit) { params.set("limit", String(options.limit)); } const query = params.toString(); return getJson(apiUrl, `/projects/${projectId}/flowchart${query ? `?${query}` : ""}`); } export async function getProjectVersions(projectId: string, apiUrl = resolveApiUrl()) { return getJson(apiUrl, `/projects/${projectId}/versions`); } export async function getLineageVersions(lineageId: string, apiUrl = resolveApiUrl()) { return getJson(apiUrl, `/versions/${encodeURIComponent(lineageId)}`); } export async function getVersionDiff( lineageId: string, fromVersionId: string, toVersionId: string, apiUrl = resolveApiUrl() ) { const params = new URLSearchParams({ from_version_id: fromVersionId, to_version_id: toVersionId }); return getJson(apiUrl, `/versions/${encodeURIComponent(lineageId)}/diff?${params.toString()}`); } export async function applyAuthoringChangeSet( projectId: string, request: AuthoringApplyChangeSetRequest, apiUrl = resolveApiUrl() ) { return postJson( apiUrl, `/projects/${projectId}/authoring/apply-change-set`, request ); } export async function getAuthoringSemanticDiffPreview( projectId: string, request: AuthoringSemanticDiffPreviewRequest, apiUrl = resolveApiUrl() ) { return postOptionalJson( apiUrl, `/projects/${projectId}/authoring/semantic-diff-preview`, request, null ); } export async function getAuthoringRollbackPreview( projectId: string, changeId: string, apiUrl = resolveApiUrl() ) { return getJson( apiUrl, `/projects/${projectId}/authoring/changes/${encodeURIComponent(changeId)}/rollback-preview` ); } export async function applyAuthoringRollback( projectId: string, changeId: string, request: AuthoringApplyRollbackRequest, apiUrl = resolveApiUrl() ) { return postJson( apiUrl, `/projects/${projectId}/authoring/changes/${encodeURIComponent(changeId)}/apply-rollback`, request ); } export async function getAuthoringMetadataObjectPreview( projectId: string, request: AuthoringMetadataObjectDraft, apiUrl = resolveApiUrl() ) { return postJson( apiUrl, `/projects/${projectId}/authoring/metadata-object-preview`, request ); } export async function applyAuthoringMetadataObject( projectId: string, request: AuthoringApplyMetadataObjectRequest, apiUrl = resolveApiUrl() ) { return postJson( apiUrl, `/projects/${projectId}/authoring/apply-metadata-object`, request ); }