Files
model-platform/frontend/app/services/api.ts
T
2026-07-31 17:22:25 +08:00

1062 lines
26 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.
// API client for the platform backend.
//
// All endpoints that take a workspace context require the caller to
// pass `workspaceId` explicitly. Components read the active workspace
// from `useAuth().currentWorkspace` and thread it through; the cookie
// set by `/api/v1/auth/login` is sent automatically thanks to
// `credentials: "same-origin"`, and the backend reads it via the
// shared `request_context` dependency.
//
// 401 from any endpoint means the session has expired or was never
// established; the global `apiRequest` helper bounces the user to
// `/login` so the platform never tries to render with a stale identity.
export type ScriptType = "python" | "notebook";
export type Visibility = "private" | "workspace" | "public";
export type Employee = {
user_id: string;
username: string;
display_name: string;
email: string | null;
status: "active" | "disabled" | "locked";
role_code: "admin" | "developer";
role_name: string;
created_at: string;
};
export type ScriptItem = {
script_id: string;
workspace_id: string;
current_object_id: string;
owner_user_id: string;
script_name: string;
script_type: ScriptType;
visibility: Visibility;
status: string;
relative_path: string;
content_hash: string;
size_bytes: number;
created_at: string;
updated_at: string;
};
export type WorkspaceDirectory = {
path: string;
name: string;
parent_path: string;
};
type ApiEnvelope<T> = {
request_id: string;
data: T;
meta: Record<string, unknown>;
};
type ApiErrorEnvelope = {
detail?: string | {
code?: string;
message?: string;
};
error?: {
code?: string;
message?: string;
details?: {
editor_name?: string;
lease_expires_at?: string;
};
};
};
export class ApiRequestError extends Error {
readonly status: number;
readonly code?: string;
constructor(message: string, status: number, code?: string) {
super(message);
this.name = "ApiRequestError";
this.status = status;
this.code = code;
}
}
function appendWorkspaceId(path: string, workspaceId: string): string {
// `path` may already contain a query string. Use URLSearchParams to
// merge cleanly either way.
const separator = path.includes("?") ? "&" : "?";
return `${path}${separator}workspace_id=${encodeURIComponent(workspaceId)}`;
}
async function apiRequest<T>(
path: string,
init: RequestInit = {},
workspaceId?: string,
): Promise<T> {
const finalPath = workspaceId ? appendWorkspaceId(path, workspaceId) : path;
const response = await fetch(finalPath, {
...init,
credentials: "same-origin",
headers: {
"X-Request-ID": crypto.randomUUID().replaceAll("-", ""),
...(init.body ? { "Content-Type": "application/json" } : {}),
...init.headers,
},
});
// Session expired / never authenticated — bounce to login. The
// /login route itself is the only path that must remain reachable
// while anonymous, so the redirect there is safe.
if (response.status === 401 && typeof window !== "undefined") {
const here = window.location.pathname;
if (here !== "/login") {
window.location.assign("/login");
}
throw new ApiRequestError("未登录或登录已过期", 401);
}
const payload = (await response.json().catch(() => ({}))) as
| ApiEnvelope<T>
| ApiErrorEnvelope;
if (!response.ok) {
const error = payload as ApiErrorEnvelope;
const detailMessage = typeof error.detail === "string"
? error.detail
: error.detail?.message;
const editor = error.error?.details?.editor_name;
throw new ApiRequestError(
(editor ? `${error.error?.message ?? "文件正在编辑"}${editor}` : undefined)
?? error.error?.message
?? detailMessage
?? `请求失败(HTTP ${response.status}`,
response.status,
typeof error.detail === "object"
? error.detail?.code
: error.error?.code,
);
}
return (payload as ApiEnvelope<T>).data;
}
export async function listScripts(workspaceId: string): Promise<ScriptItem[]> {
return apiRequest<ScriptItem[]>("/api/v1/scripts", {}, workspaceId);
}
function initialContent(scriptType: ScriptType): string {
if (scriptType === "python") {
return [
'"""模型实验开发平台构建脚本。"""',
"",
"",
"def main() -> None:",
' print("Hello, Model Platform!")',
"",
"",
'if __name__ == "__main__":',
" main()",
"",
].join("\n");
}
return JSON.stringify(
{
cells: [
{
cell_type: "markdown",
metadata: {},
source: ["# 新建模型实验\n", "在这里开始数据探索与模型构建。"],
},
{
cell_type: "code",
execution_count: null,
metadata: {},
outputs: [],
source: ["print('Hello, Model Platform!')\n"],
},
],
metadata: {
kernelspec: {
display_name: "Python 3",
language: "python",
name: "python3",
},
language_info: {
name: "python",
version: "3.12",
},
},
nbformat: 4,
nbformat_minor: 5,
},
null,
2,
);
}
export async function createScript(
workspaceId: string,
input: {
name: string;
scriptType: ScriptType;
visibility: Visibility;
parentPath?: string | null;
},
): Promise<ScriptItem> {
return apiRequest<ScriptItem>(
"/api/v1/scripts",
{
method: "POST",
body: JSON.stringify({
script_name: input.name.trim(),
script_type: input.scriptType,
visibility: input.visibility,
content: initialContent(input.scriptType),
parent_path: input.parentPath,
}),
},
workspaceId,
);
}
export async function uploadScript(
workspaceId: string,
file: File,
parentPath = "",
visibility: Visibility = "workspace",
): Promise<ScriptItem> {
const parameters = new URLSearchParams({
file_name: file.name,
parent_path: parentPath,
visibility,
});
return apiRequest<ScriptItem>(
`/api/v1/scripts/upload?${parameters.toString()}`,
{
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: file,
},
workspaceId,
);
}
export async function updateScript(
workspaceId: string,
scriptId: string,
input: { content: string },
): Promise<ScriptItem> {
return apiRequest<ScriptItem>(
`/api/v1/scripts/${scriptId}`,
{ method: "PUT", body: JSON.stringify(input) },
workspaceId,
);
}
export async function deleteScript(
workspaceId: string,
scriptId: string,
): Promise<{ script_id: string; status: string; versions_preserved: boolean }> {
return apiRequest(
`/api/v1/scripts/${scriptId}`,
{ method: "DELETE" },
workspaceId,
);
}
export async function listWorkspaceDirectories(
workspaceId: string,
): Promise<WorkspaceDirectory[]> {
const data = await apiRequest<{ directories: WorkspaceDirectory[] }>(
"/api/v1/workspace-tree",
{},
workspaceId,
);
return data.directories;
}
export async function createWorkspaceDirectory(
workspaceId: string,
directoryName: string,
parentPath = "",
): Promise<WorkspaceDirectory> {
return apiRequest<WorkspaceDirectory>(
"/api/v1/workspace-directories",
{
method: "POST",
body: JSON.stringify({
directory_name: directoryName,
parent_path: parentPath,
}),
},
workspaceId,
);
}
export async function deleteWorkspaceDirectory(
workspaceId: string,
path: string,
): Promise<{
path: string;
status: string;
deleted_scripts: number;
versions_preserved: boolean;
}> {
const parameters = new URLSearchParams({ path });
return apiRequest(
`/api/v1/workspace-directories?${parameters.toString()}`,
{ method: "DELETE" },
workspaceId,
);
}
export type FileLockSession = {
edit_session_id: string;
workspace_id: string;
storage_object_id: string;
user_id: string;
session_status: "active" | "closed" | "expired";
lease_seconds: number;
heartbeat_interval_seconds: number;
expires_at: string;
runtime_id: string;
jupyter_session_id: string;
jupyter_url?: string;
relative_path?: string;
lock_token?: string;
};
export type ActiveEditSession = FileLockSession & {
script_id: string;
script_name: string;
lock_token: string;
ticket_expires_at?: string;
};
export type JupyterAccessTicket = {
edit_session_id: string;
jupyter_url: string;
expires_at: string;
};
export type StableVersion = {
versions_id: string;
workspace_id: string;
script_id: string;
source_object_id: string;
artifact_object_id: string;
version_no: number;
version_label: string;
source_path: string;
artifact_path: string;
content_hash: string;
file_size_bytes: number;
visibility: Visibility;
release_note: string | null;
created_by: string;
created_at: string;
};
// Note: the file-lock and jupyter-ticket endpoints are not yet
// implemented in the backend (see the cookie+JWT auth refactor plan).
// They are retained here so the editor UI keeps its existing call
// sites, but they will return 404 until the backend ships the
// corresponding routes.
export async function acquireFileLock(
workspaceId: string,
script: ScriptItem,
): Promise<ActiveEditSession> {
const session = await apiRequest<FileLockSession>(
`/api/v1/files/${script.current_object_id}/lock`,
{ method: "POST" },
workspaceId,
);
if (!session.lock_token) {
throw new Error("加锁成功响应缺少 lock_token");
}
return {
...session,
script_id: script.script_id,
script_name: script.script_name,
lock_token: session.lock_token,
};
}
export async function heartbeatFileLock(
workspaceId: string,
session: ActiveEditSession,
): Promise<FileLockSession> {
return apiRequest<FileLockSession>(
`/api/v1/file-locks/${session.edit_session_id}/heartbeat`,
{
method: "POST",
body: JSON.stringify({ lock_token: session.lock_token }),
},
workspaceId,
);
}
export async function releaseFileLock(
workspaceId: string,
session: ActiveEditSession,
): Promise<FileLockSession> {
return apiRequest<FileLockSession>(
`/api/v1/file-locks/${session.edit_session_id}`,
{
method: "DELETE",
body: JSON.stringify({ lock_token: session.lock_token }),
},
workspaceId,
);
}
export function releaseFileLockOnUnload(
workspaceId: string,
session: ActiveEditSession,
): void {
void fetch(
`/api/v1/file-locks/${session.edit_session_id}?workspace_id=${
encodeURIComponent(workspaceId)
}`,
{
method: "DELETE",
credentials: "same-origin",
keepalive: true,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ lock_token: session.lock_token }),
},
);
}
export async function createJupyterAccessTicket(
workspaceId: string,
session: ActiveEditSession,
): Promise<JupyterAccessTicket> {
return apiRequest<JupyterAccessTicket>(
"/api/v1/jupyter/access-tickets",
{
method: "POST",
body: JSON.stringify({
edit_session_id: session.edit_session_id,
lock_token: session.lock_token,
}),
},
workspaceId,
);
}
export async function listScriptVersions(
workspaceId: string,
scriptId: string,
): Promise<StableVersion[]> {
return apiRequest<StableVersion[]>(
`/api/v1/scripts/${scriptId}/versions`,
{},
workspaceId,
);
}
export async function publishScriptVersion(
workspaceId: string,
input: {
script: ScriptItem;
releaseNote: string;
visibility: Visibility;
},
): Promise<StableVersion> {
return apiRequest<StableVersion>(
`/api/v1/scripts/${input.script.script_id}/versions`,
{
method: "POST",
body: JSON.stringify({
source_object_id: input.script.current_object_id,
release_note: input.releaseNote.trim() || null,
visibility: input.visibility,
}),
},
workspaceId,
);
}
export type ScheduleArtifact = {
versions_id: string;
version_label: string;
script_id: string;
script_name: string;
script_type: ScriptType;
content_hash: string;
file_size_bytes: number;
visibility: Visibility;
created_by: string;
created_at: string;
};
export type ScheduleNode = {
node_id: string;
schedule_id: string;
node_key: string;
node_name: string;
versions_id: string;
timeout_seconds: number;
retry_count: number;
retry_interval_sec: number;
position_x: number;
position_y: number;
arguments_json: Record<string, unknown>;
env_refs_json: Record<string, string>;
created_at: string;
updated_at: string;
version: {
versions_id: string;
version_label: string;
script_id: string;
script_name: string;
script_type: ScriptType;
content_hash: string;
created_at: string;
};
};
export type ScheduleEdge = {
edge_id: string;
schedule_id: string;
source_node_id: string;
target_node_id: string;
condition_expr: string | null;
created_at: string;
};
export type DagValidation = {
valid: boolean;
node_count: number;
edge_count: number;
root_node_ids: string[];
leaf_node_ids: string[];
topological_order: string[];
errors: Array<{
code: string;
message: string;
edge_id?: string;
node_ids?: string[];
}>;
};
export type Schedule = {
schedule_id: string;
workspace_id: string;
schedule_name: string;
description: string | null;
trigger_type: "manual" | "cron" | "api";
cron_expression: string | null;
timezone: string;
enabled: boolean;
workflow_version: number;
max_concurrency: number;
failure_policy: "stop" | "continue";
last_run_at: string | null;
next_run_at: string | null;
created_by: string;
updated_by: string;
created_at: string;
updated_at: string;
node_count: number;
edge_count: number;
nodes: ScheduleNode[];
edges: ScheduleEdge[];
dag_validation: DagValidation;
};
export type CronPreview = {
cron_expression: string;
timezone: string;
base_time: string;
occurrences: Array<{
local_time: string;
utc_time: string;
}>;
};
export type ScheduleRunStatus =
| "queued"
| "running"
| "succeeded"
| "failed"
| "cancelled"
| "timed_out";
export type ScheduleNodeRunStatus =
| ScheduleRunStatus
| "skipped";
export type ScheduleRunSummary = {
run_id: string;
schedule_id: string;
workspace_id: string;
workflow_version: number;
trigger_type: "manual" | "cron" | "api" | "retry";
run_status: ScheduleRunStatus;
state_version: number;
queued_at: string;
started_at: string | null;
finished_at: string | null;
duration_ms: number | null;
error_code: string | null;
error_message: string | null;
logs_object_id: string | null;
result_object_id: string | null;
};
export type ScheduleNodeRun = {
node_run_id: string;
run_id: string;
node_id: string;
versions_id: string;
attempt_no: number;
node_status: ScheduleNodeRunStatus;
state_version: number;
started_at: string | null;
finished_at: string | null;
duration_ms: number | null;
exit_code: number | null;
message: string | null;
logs_object_id: string | null;
result_object_id: string | null;
};
export type ScheduleRunDetail = ScheduleRunSummary & {
node_runs: ScheduleNodeRun[];
};
export async function listSchedules(workspaceId: string): Promise<Schedule[]> {
return apiRequest<Schedule[]>("/api/v1/schedules", {}, workspaceId);
}
export async function getSchedule(
workspaceId: string,
scheduleId: string,
): Promise<Schedule> {
return apiRequest<Schedule>(
`/api/v1/schedules/${scheduleId}`,
{},
workspaceId,
);
}
export async function createSchedule(
workspaceId: string,
input: {
schedule_name: string;
description?: string | null;
trigger_type?: "manual" | "cron" | "api";
cron_expression?: string | null;
timezone?: string;
enabled?: boolean;
max_concurrency?: number;
failure_policy?: "stop" | "continue";
},
): Promise<Schedule> {
return apiRequest<Schedule>(
"/api/v1/schedules",
{ method: "POST", body: JSON.stringify(input) },
workspaceId,
);
}
export async function updateSchedule(
workspaceId: string,
scheduleId: string,
input: {
workflow_version: number;
schedule_name?: string;
description?: string | null;
trigger_type?: "manual" | "cron" | "api";
cron_expression?: string | null;
timezone?: string;
enabled?: boolean;
max_concurrency?: number;
failure_policy?: "stop" | "continue";
},
): Promise<Schedule> {
return apiRequest<Schedule>(
`/api/v1/schedules/${scheduleId}`,
{ method: "PATCH", body: JSON.stringify(input) },
workspaceId,
);
}
export async function deleteSchedule(
workspaceId: string,
scheduleId: string,
workflowVersion: number,
): Promise<{ schedule_id: string; deleted: boolean; workflow_version: number }> {
return apiRequest(
`/api/v1/schedules/${scheduleId}`,
{ method: "DELETE", body: JSON.stringify({ workflow_version: workflowVersion }) },
workspaceId,
);
}
export async function listScheduleArtifacts(
workspaceId: string,
): Promise<ScheduleArtifact[]> {
return apiRequest<ScheduleArtifact[]>(
"/api/v1/schedule-artifacts",
{},
workspaceId,
);
}
export async function hideScheduleArtifact(
workspaceId: string,
versionsId: string,
): Promise<{
versions_id: string;
deleted: boolean;
artifact_preserved: boolean;
}> {
return apiRequest(
`/api/v1/versions/${versionsId}`,
{ method: "DELETE" },
workspaceId,
);
}
export async function listEmployees(workspaceId: string): Promise<Employee[]> {
return apiRequest<Employee[]>("/api/v1/admin/employees", {}, workspaceId);
}
export async function createEmployee(
workspaceId: string,
input: {
username: string;
display_name: string;
email?: string | null;
role_code: "admin" | "developer";
},
): Promise<Employee> {
return apiRequest<Employee>(
"/api/v1/admin/employees",
{ method: "POST", body: JSON.stringify(input) },
workspaceId,
);
}
export async function updateEmployee(
workspaceId: string,
userId: string,
input: {
display_name?: string;
email?: string | null;
role_code?: "admin" | "developer";
status?: "active" | "disabled" | "locked";
},
): Promise<Employee> {
return apiRequest<Employee>(
`/api/v1/admin/employees/${userId}`,
{ method: "PATCH", body: JSON.stringify(input) },
workspaceId,
);
}
export async function deleteEmployee(
workspaceId: string,
userId: string,
): Promise<{ user_id: string; deleted: boolean }> {
return apiRequest(
`/api/v1/admin/employees/${userId}`,
{ method: "DELETE" },
workspaceId,
);
}
export async function createScheduleNode(
workspaceId: string,
scheduleId: string,
input: {
workflow_version: number;
node_key: string;
node_name: string;
versions_id: string;
timeout_seconds?: number;
retry_count?: number;
retry_interval_sec?: number;
position_x?: number;
position_y?: number;
arguments_json?: Record<string, unknown>;
env_refs_json?: Record<string, string>;
},
): Promise<Schedule> {
return apiRequest<Schedule>(
`/api/v1/schedules/${scheduleId}/nodes`,
{ method: "POST", body: JSON.stringify(input) },
workspaceId,
);
}
export async function updateScheduleNode(
workspaceId: string,
scheduleId: string,
nodeId: string,
input: {
workflow_version: number;
node_name?: string;
versions_id?: string;
timeout_seconds?: number;
retry_count?: number;
retry_interval_sec?: number;
position_x?: number;
position_y?: number;
arguments_json?: Record<string, unknown>;
env_refs_json?: Record<string, string>;
},
): Promise<Schedule> {
return apiRequest<Schedule>(
`/api/v1/schedules/${scheduleId}/nodes/${nodeId}`,
{ method: "PUT", body: JSON.stringify(input) },
workspaceId,
);
}
export async function deleteScheduleNode(
workspaceId: string,
scheduleId: string,
nodeId: string,
workflowVersion: number,
): Promise<Schedule> {
return apiRequest<Schedule>(
`/api/v1/schedules/${scheduleId}/nodes/${nodeId}`,
{ method: "DELETE", body: JSON.stringify({ workflow_version: workflowVersion }) },
workspaceId,
);
}
export async function createScheduleEdge(
workspaceId: string,
scheduleId: string,
input: {
workflow_version: number;
source_node_id: string;
target_node_id: string;
condition_expr?: string | null;
},
): Promise<Schedule> {
return apiRequest<Schedule>(
`/api/v1/schedules/${scheduleId}/edges`,
{ method: "POST", body: JSON.stringify(input) },
workspaceId,
);
}
export async function deleteScheduleEdge(
workspaceId: string,
scheduleId: string,
edgeId: string,
workflowVersion: number,
): Promise<Schedule> {
return apiRequest<Schedule>(
`/api/v1/schedules/${scheduleId}/edges/${edgeId}`,
{ method: "DELETE", body: JSON.stringify({ workflow_version: workflowVersion }) },
workspaceId,
);
}
export async function validateSchedule(
workspaceId: string,
scheduleId: string,
): Promise<DagValidation & {
schedule_id: string;
workflow_version: number;
}> {
return apiRequest(
`/api/v1/schedules/${scheduleId}/validate`,
{ method: "POST" },
workspaceId,
);
}
export async function previewCron(
workspaceId: string,
input: {
cron_expression: string;
timezone: string;
count?: number;
base_time?: string;
},
): Promise<CronPreview> {
return apiRequest<CronPreview>(
"/api/v1/cron/preview",
{ method: "POST", body: JSON.stringify(input) },
workspaceId,
);
}
export async function runScheduleNow(
workspaceId: string,
scheduleId: string,
): Promise<ScheduleRunDetail> {
return apiRequest<ScheduleRunDetail>(
`/api/v1/schedules/${scheduleId}/run`,
{
method: "POST",
headers: {
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ reason: "manual_run" }),
},
workspaceId,
);
}
export async function listScheduleRuns(
workspaceId: string,
input: {
scheduleId?: string;
status?: ScheduleRunStatus;
limit?: number;
} = {},
): Promise<ScheduleRunSummary[]> {
const query = new URLSearchParams();
if (input.scheduleId) query.set("schedule_id", input.scheduleId);
if (input.status) query.set("status", input.status);
query.set("limit", String(input.limit ?? 20));
return apiRequest<ScheduleRunSummary[]>(
`/api/v1/schedule-runs?${query.toString()}`,
{},
workspaceId,
);
}
export async function getScheduleRun(
workspaceId: string,
runId: string,
): Promise<ScheduleRunDetail> {
return apiRequest<ScheduleRunDetail>(
`/api/v1/schedule-runs/${runId}`,
{},
workspaceId,
);
}
// ----------------------------------------------------------------------------
// Workspace-bound API surface.
//
// `useApi()` in ~/context/AuthContext returns an object where every
// function has had its first `workspaceId` argument pre-filled. The
// type below lets consumers import the bound type without depending
// on the raw functions. Keep this last in the file so the type
// references all the exports above.
// ----------------------------------------------------------------------------
export type WorkspaceBoundApi = {
listScripts: () => Promise<ScriptItem[]>;
createScript: (
input: Parameters<typeof createScript>[1],
) => Promise<ScriptItem>;
uploadScript: (
file: File,
parentPath?: string,
visibility?: Visibility,
) => Promise<ScriptItem>;
updateScript: (
scriptId: string,
input: Parameters<typeof updateScript>[1],
) => Promise<ScriptItem>;
deleteScript: (
scriptId: string,
) => Promise<{ script_id: string; status: string; versions_preserved: boolean }>;
listWorkspaceDirectories: () => Promise<WorkspaceDirectory[]>;
createWorkspaceDirectory: (
directoryName: string,
parentPath?: string,
) => Promise<WorkspaceDirectory>;
deleteWorkspaceDirectory: (
path: string,
) => Promise<{
path: string;
status: string;
deleted_scripts: number;
versions_preserved: boolean;
}>;
acquireFileLock: (
script: ScriptItem,
) => Promise<ActiveEditSession>;
heartbeatFileLock: (
session: ActiveEditSession,
) => Promise<FileLockSession>;
releaseFileLock: (
session: ActiveEditSession,
) => Promise<FileLockSession>;
releaseFileLockOnUnload: (session: ActiveEditSession) => void;
createJupyterAccessTicket: (
session: ActiveEditSession,
) => Promise<JupyterAccessTicket>;
listScriptVersions: (scriptId: string) => Promise<StableVersion[]>;
publishScriptVersion: (
input: Parameters<typeof publishScriptVersion>[1],
) => Promise<StableVersion>;
listSchedules: () => Promise<Schedule[]>;
getSchedule: (scheduleId: string) => Promise<Schedule>;
createSchedule: (
input: Parameters<typeof createSchedule>[1],
) => Promise<Schedule>;
updateSchedule: (
scheduleId: string,
input: Parameters<typeof updateSchedule>[2],
) => Promise<Schedule>;
deleteSchedule: (
scheduleId: string,
workflowVersion: number,
) => Promise<{ schedule_id: string; deleted: boolean; workflow_version: number }>;
listScheduleArtifacts: () => Promise<ScheduleArtifact[]>;
hideScheduleArtifact: (
versionsId: string,
) => Promise<{
versions_id: string;
deleted: boolean;
artifact_preserved: boolean;
}>;
listEmployees: () => Promise<Employee[]>;
createEmployee: (
input: Parameters<typeof createEmployee>[1],
) => Promise<Employee>;
updateEmployee: (
userId: string,
input: Parameters<typeof updateEmployee>[2],
) => Promise<Employee>;
deleteEmployee: (
userId: string,
) => Promise<{ user_id: string; deleted: boolean }>;
createScheduleNode: (
scheduleId: string,
input: Parameters<typeof createScheduleNode>[2],
) => Promise<Schedule>;
updateScheduleNode: (
scheduleId: string,
nodeId: string,
input: Parameters<typeof updateScheduleNode>[3],
) => Promise<Schedule>;
deleteScheduleNode: (
scheduleId: string,
nodeId: string,
workflowVersion: number,
) => Promise<Schedule>;
createScheduleEdge: (
scheduleId: string,
input: Parameters<typeof createScheduleEdge>[2],
) => Promise<Schedule>;
deleteScheduleEdge: (
scheduleId: string,
edgeId: string,
workflowVersion: number,
) => Promise<Schedule>;
validateSchedule: (
scheduleId: string,
) => Promise<DagValidation & { schedule_id: string; workflow_version: number }>;
previewCron: (
input: Parameters<typeof previewCron>[1],
) => Promise<CronPreview>;
runScheduleNow: (scheduleId: string) => Promise<ScheduleRunDetail>;
listScheduleRuns: (
input?: Parameters<typeof listScheduleRuns>[1],
) => Promise<ScheduleRunSummary[]>;
getScheduleRun: (runId: string) => Promise<ScheduleRunDetail>;
};