feat(ts-migration): Phase 2.3 — all API satellite modules JS→TS with full contracts
Converted 12 files: - api/assistant.ts [C:3] — chat, conversations, history, confirm/cancel - api/reports.ts [C:3] — list/detail fetch with query builder - api/datasetReview.ts [C:3] — optimistic-lock, conflict detection, DTO normalization - api/maintenance.ts [C:3] — start/end events, settings, banners - api/translate.ts [C:2] — barrel re-export - api/translate/jobs.ts [C:2] — CRUD + duplicate - api/translate/runs.ts [C:2] — trigger, status, history, retry, cancel, metrics - api/translate/dictionaries.ts [C:3] — dictionary + entry CRUD, import - api/translate/datasources.ts [C:2] — columns, preview, approve/edit/reject rows - api/translate/corrections.ts [C:2] — inline edit, bulk replace, CSV download - api/translate/schedules.ts [C:2] — cron CRUD, enable/disable, next executions - api/translate/target-schema.ts [C:2] — schema validation with graceful error fallback Each function: - Typed parameters + generic <T = unknown> return - @POST / @SIDE_EFFECT / @RELATION DEPENDS_ON chains - Normalized TranslateApiError pattern (message, code, retryable) - checkTargetTableSchema has @RATIONALE for graceful error handling Build: npm run build passes cleanly
This commit is contained in:
@@ -1,106 +0,0 @@
|
||||
// #region AssistantApi [C:3] [TYPE Module] [SEMANTICS assistant, api, chat, session, history]
|
||||
// @BRIEF API client wrapper for assistant chat, session-scoped prompts, confirmation actions, conversation management, and history retrieval.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
|
||||
// #region AssistantApi:Module [TYPE Function]
|
||||
// @SEMANTICS: assistant, api, client, chat, confirmation
|
||||
// @PURPOSE: API client wrapper for assistant chat, session-scoped prompts, confirmation actions, and history retrieval.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [EXT:frontend:api_module]
|
||||
// @INVARIANT: All assistant requests must use requestApi wrapper (no native fetch).
|
||||
|
||||
import { requestApi } from '$lib/api.js';
|
||||
|
||||
// #region sendAssistantMessage:Function [TYPE Function]
|
||||
// @PURPOSE: Send a user message to assistant orchestrator endpoint.
|
||||
// @PRE: payload.message is a non-empty string.
|
||||
// @POST: Returns assistant response object with deterministic state.
|
||||
export function sendAssistantMessage(payload) {
|
||||
return requestApi('/assistant/messages', 'POST', payload);
|
||||
}
|
||||
// #endregion sendAssistantMessage:Function
|
||||
|
||||
// #region buildAssistantSeedMessage:Function [TYPE Function]
|
||||
// @PURPOSE: Compose visible assistant seed text from context label and prompt body.
|
||||
// @PRE: prompt contains the user-visible request.
|
||||
// @POST: Returns trimmed seed message string for prefilled input.
|
||||
export function buildAssistantSeedMessage({ label = '', prompt = '' } = {}) {
|
||||
const normalizedLabel = String(label || '').trim();
|
||||
const normalizedPrompt = String(prompt || '').trim();
|
||||
if (normalizedLabel && normalizedPrompt) {
|
||||
return `${normalizedLabel}: ${normalizedPrompt}`;
|
||||
}
|
||||
return normalizedPrompt || normalizedLabel;
|
||||
}
|
||||
// #endregion buildAssistantSeedMessage:Function
|
||||
|
||||
// #region confirmAssistantOperation:Function [TYPE Function]
|
||||
// @PURPOSE: Confirm a pending risky assistant operation.
|
||||
// @PRE: confirmationId references an existing pending token.
|
||||
// @POST: Returns execution response (started/success/failed).
|
||||
export function confirmAssistantOperation(confirmationId) {
|
||||
return requestApi(`/assistant/confirmations/${confirmationId}/confirm`, 'POST');
|
||||
}
|
||||
// #endregion confirmAssistantOperation:Function
|
||||
|
||||
// #region cancelAssistantOperation:Function [TYPE Function]
|
||||
// @PURPOSE: Cancel a pending risky assistant operation.
|
||||
// @PRE: confirmationId references an existing pending token.
|
||||
// @POST: Operation is cancelled and cannot be executed by this token.
|
||||
export function cancelAssistantOperation(confirmationId) {
|
||||
return requestApi(`/assistant/confirmations/${confirmationId}/cancel`, 'POST');
|
||||
}
|
||||
// #endregion cancelAssistantOperation:Function
|
||||
|
||||
// #region getAssistantHistory:Function [TYPE Function]
|
||||
// @PURPOSE: Retrieve paginated assistant conversation history.
|
||||
// @PRE: page/pageSize are positive integers.
|
||||
// @POST: Returns a paginated payload with history items.
|
||||
export function getAssistantHistory(page = 1, pageSize = 20, conversationId = null, fromLatest = false) {
|
||||
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
|
||||
if (conversationId) {
|
||||
params.append('conversation_id', conversationId);
|
||||
}
|
||||
if (fromLatest) {
|
||||
params.append('from_latest', 'true');
|
||||
}
|
||||
return requestApi(`/assistant/history?${params.toString()}`, 'GET');
|
||||
}
|
||||
// #endregion getAssistantHistory:Function
|
||||
|
||||
// #region getAssistantConversations:Function [TYPE Function]
|
||||
// @PURPOSE: Retrieve paginated conversation list for assistant sidebar/history switcher.
|
||||
// @PRE: page/pageSize are positive integers.
|
||||
// @POST: Returns paginated conversation summaries.
|
||||
export function getAssistantConversations(
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
includeArchived = false,
|
||||
search = '',
|
||||
archivedOnly = false,
|
||||
) {
|
||||
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
|
||||
if (includeArchived) {
|
||||
params.append('include_archived', 'true');
|
||||
}
|
||||
if (archivedOnly) {
|
||||
params.append('archived_only', 'true');
|
||||
}
|
||||
if (search?.trim()) {
|
||||
params.append('search', search.trim());
|
||||
}
|
||||
return requestApi(`/assistant/conversations?${params.toString()}`, 'GET');
|
||||
}
|
||||
// #endregion getAssistantConversations:Function
|
||||
|
||||
// #region deleteAssistantConversation:Function [TYPE Function]
|
||||
// @PURPOSE: Soft-delete or hard-delete a conversation.
|
||||
// @PRE: conversationId string is provided.
|
||||
// @POST: Returns success status.
|
||||
export function deleteAssistantConversation(conversationId) {
|
||||
return requestApi(`/assistant/conversations/${conversationId}`, 'DELETE');
|
||||
}
|
||||
// #endregion deleteAssistantConversation:Function
|
||||
|
||||
// #endregion AssistantApi:Module
|
||||
// #endregion AssistantApi
|
||||
106
frontend/src/lib/api/assistant.ts
Normal file
106
frontend/src/lib/api/assistant.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
// #region AssistantApi [C:3] [TYPE Module] [SEMANTICS assistant, api, chat, session, history]
|
||||
// @BRIEF API client for assistant chat — message sending, conversation management, confirmation operations, and history retrieval.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @PRE All requests go through requestApi wrapper (never native fetch).
|
||||
// @POST Returns typed API responses or throws normalized errors.
|
||||
|
||||
import { requestApi } from '$lib/api';
|
||||
|
||||
// #region sendAssistantMessage [C:2] [TYPE Function] [SEMANTICS assistant, chat, message]
|
||||
// @BRIEF Send a user message to the assistant orchestrator endpoint.
|
||||
// @PRE payload.message is a non-empty string.
|
||||
// @POST Returns assistant response object with deterministic state.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export function sendAssistantMessage<T = unknown>(payload: { message: string; [k: string]: unknown }): Promise<T> {
|
||||
return requestApi<T>('/assistant/messages', 'POST', payload);
|
||||
}
|
||||
// #endregion sendAssistantMessage
|
||||
|
||||
// #region buildAssistantSeedMessage [C:1] [TYPE Function] [SEMANTICS assistant, seed, prompt]
|
||||
// @BRIEF Compose visible assistant seed text from context label and prompt body.
|
||||
// @PRE prompt contains the user-visible request.
|
||||
// @POST Returns trimmed seed message string for prefilled input.
|
||||
export function buildAssistantSeedMessage({ label = '', prompt = '' }: { label?: string; prompt?: string } = {}): string {
|
||||
const normalizedLabel = String(label || '').trim();
|
||||
const normalizedPrompt = String(prompt || '').trim();
|
||||
if (normalizedLabel && normalizedPrompt) {
|
||||
return `${normalizedLabel}: ${normalizedPrompt}`;
|
||||
}
|
||||
return normalizedPrompt || normalizedLabel;
|
||||
}
|
||||
// #endregion buildAssistantSeedMessage
|
||||
|
||||
// #region confirmAssistantOperation [C:2] [TYPE Function] [SEMANTICS assistant, confirm, operation]
|
||||
// @BRIEF Confirm a pending risky assistant operation.
|
||||
// @PRE confirmationId references an existing pending token.
|
||||
// @POST Returns execution response (started/success/failed).
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export function confirmAssistantOperation<T = unknown>(confirmationId: string): Promise<T> {
|
||||
return requestApi<T>(`/assistant/confirmations/${confirmationId}/confirm`, 'POST');
|
||||
}
|
||||
// #endregion confirmAssistantOperation
|
||||
|
||||
// #region cancelAssistantOperation [C:2] [TYPE Function] [SEMANTICS assistant, cancel, operation]
|
||||
// @BRIEF Cancel a pending risky assistant operation.
|
||||
// @PRE confirmationId references an existing pending token.
|
||||
// @POST Operation is cancelled and cannot be executed by this token.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export function cancelAssistantOperation<T = unknown>(confirmationId: string): Promise<T> {
|
||||
return requestApi<T>(`/assistant/confirmations/${confirmationId}/cancel`, 'POST');
|
||||
}
|
||||
// #endregion cancelAssistantOperation
|
||||
|
||||
// #region getAssistantHistory [C:2] [TYPE Function] [SEMANTICS assistant, history, pagination]
|
||||
// @BRIEF Retrieve paginated assistant conversation history.
|
||||
// @PRE page/pageSize are positive integers.
|
||||
// @POST Returns a paginated payload with history items.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export function getAssistantHistory<T = unknown>(page: number = 1, pageSize: number = 20, conversationId: string | null = null, fromLatest: boolean = false): Promise<T> {
|
||||
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
|
||||
if (conversationId) {
|
||||
params.append('conversation_id', conversationId);
|
||||
}
|
||||
if (fromLatest) {
|
||||
params.append('from_latest', 'true');
|
||||
}
|
||||
return requestApi<T>(`/assistant/history?${params.toString()}`, 'GET');
|
||||
}
|
||||
// #endregion getAssistantHistory
|
||||
|
||||
// #region getAssistantConversations [C:2] [TYPE Function] [SEMANTICS assistant, conversations, sidebar]
|
||||
// @BRIEF Retrieve paginated conversation list for assistant sidebar/history switcher.
|
||||
// @PRE page/pageSize are positive integers.
|
||||
// @POST Returns paginated conversation summaries.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export function getAssistantConversations<T = unknown>(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
includeArchived: boolean = false,
|
||||
search: string = '',
|
||||
archivedOnly: boolean = false,
|
||||
): Promise<T> {
|
||||
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
|
||||
if (includeArchived) {
|
||||
params.append('include_archived', 'true');
|
||||
}
|
||||
if (archivedOnly) {
|
||||
params.append('archived_only', 'true');
|
||||
}
|
||||
if (search?.trim()) {
|
||||
params.append('search', search.trim());
|
||||
}
|
||||
return requestApi<T>(`/assistant/conversations?${params.toString()}`, 'GET');
|
||||
}
|
||||
// #endregion getAssistantConversations
|
||||
|
||||
// #region deleteAssistantConversation [C:2] [TYPE Function] [SEMANTICS assistant, conversation, delete]
|
||||
// @BRIEF Soft-delete or hard-delete a conversation.
|
||||
// @PRE conversationId string is provided.
|
||||
// @POST Returns success status.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export function deleteAssistantConversation<T = unknown>(conversationId: string): Promise<T> {
|
||||
return requestApi<T>(`/assistant/conversations/${conversationId}`, 'DELETE');
|
||||
}
|
||||
// #endregion deleteAssistantConversation
|
||||
// #endregion AssistantApi
|
||||
@@ -1,136 +0,0 @@
|
||||
// #region DatasetReviewApi [C:3] [TYPE Module] [SEMANTICS dataset, review, api, session, conflict]
|
||||
// @BRIEF Shared frontend helpers for dataset review session-scoped API requests, optimistic-lock propagation, and session DTO normalization.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
|
||||
// #region DatasetReviewApi:Module [TYPE Function]
|
||||
// @SEMANTICS: dataset-review, api, session-version, headers, conflict-handling
|
||||
// @PURPOSE: Provide shared frontend helpers for dataset review session-scoped API requests, optimistic-lock propagation, and refreshed session DTO normalization.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [EXT:frontend:api_module]
|
||||
|
||||
import { requestApi } from '$lib/api.js';
|
||||
|
||||
// #region buildDatasetReviewRequestOptions:Function [TYPE Function]
|
||||
// @PURPOSE: Attach optimistic-lock session version header when the current version is known.
|
||||
// @PRE: sessionVersion may be null when no loaded session version exists yet.
|
||||
// @POST: Returns requestApi-compatible options object.
|
||||
export function buildDatasetReviewRequestOptions(sessionVersion) {
|
||||
if (sessionVersion === null || sessionVersion === undefined || sessionVersion === '') {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
headers: {
|
||||
'X-Session-Version': String(sessionVersion),
|
||||
},
|
||||
};
|
||||
}
|
||||
// #endregion buildDatasetReviewRequestOptions:Function
|
||||
|
||||
// #region requestDatasetReviewApi:Function [TYPE Function]
|
||||
// @PURPOSE: Proxy dataset review mutations through requestApi with optional optimistic-lock headers.
|
||||
// @PRE: endpoint and method are valid requestApi inputs.
|
||||
// @POST: Executes requestApi with X-Session-Version only when a session version is known.
|
||||
export function requestDatasetReviewApi(endpoint, method = 'GET', body = null, sessionVersion = null) {
|
||||
const requestOptions = buildDatasetReviewRequestOptions(sessionVersion);
|
||||
if (requestOptions.headers) {
|
||||
return requestApi(endpoint, method, body, requestOptions);
|
||||
}
|
||||
return requestApi(endpoint, method, body);
|
||||
}
|
||||
// #endregion requestDatasetReviewApi:Function
|
||||
|
||||
// #region isDatasetReviewConflictError:Function [TYPE Function]
|
||||
// @PURPOSE: Detect optimistic-lock conflicts from dataset review mutations.
|
||||
// @PRE: error may be null or a normalized API error.
|
||||
// @POST: Returns true when the mutation failed with 409 conflict semantics.
|
||||
export function isDatasetReviewConflictError(error) {
|
||||
return Number(error?.status) === 409;
|
||||
}
|
||||
// #endregion isDatasetReviewConflictError:Function
|
||||
|
||||
// #region getDatasetReviewConflictMessage:Function [TYPE Function]
|
||||
// @PURPOSE: Return explicit 409-style guidance for stale dataset review mutations.
|
||||
// @PRE: error may be null or a normalized API error.
|
||||
// @POST: Returns a user-facing conflict message string.
|
||||
export function getDatasetReviewConflictMessage(error) {
|
||||
return (
|
||||
error?.message ||
|
||||
'This review session changed in another action. Reload the latest session state and retry the update.'
|
||||
);
|
||||
}
|
||||
// #endregion getDatasetReviewConflictMessage:Function
|
||||
|
||||
// #region extractDatasetReviewVersion:Function [TYPE Function]
|
||||
// @PURPOSE: Resolve the latest session version from refreshed DTO fragments.
|
||||
// @PRE: payload may be a session summary, a detail payload, or a mutation fragment.
|
||||
// @POST: Returns the newest known session version or null.
|
||||
export function extractDatasetReviewVersion(payload) {
|
||||
if (!payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
for (const item of payload) {
|
||||
const version = extractDatasetReviewVersion(item);
|
||||
if (version !== null) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (payload.session_version !== undefined && payload.session_version !== null) {
|
||||
return payload.session_version;
|
||||
}
|
||||
if (payload.version !== undefined && payload.version !== null) {
|
||||
return payload.version;
|
||||
}
|
||||
if (payload.session) {
|
||||
return extractDatasetReviewVersion(payload.session);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
// #endregion extractDatasetReviewVersion:Function
|
||||
|
||||
// #region normalizeDatasetReviewDetail:Function [TYPE Function]
|
||||
// @PURPOSE: Normalize refreshed session-detail DTOs into the legacy route-friendly workspace shape.
|
||||
// @PRE: detail may already be legacy-flat or refreshed with nested session/session_version fields.
|
||||
// @POST: Returns a shape with flattened summary fields plus refreshed collections and version metadata.
|
||||
export function normalizeDatasetReviewDetail(detail) {
|
||||
if (!detail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!detail.session && detail.session_id) {
|
||||
return detail;
|
||||
}
|
||||
|
||||
const summary = detail.session || {};
|
||||
const preview = detail.preview || null;
|
||||
|
||||
return {
|
||||
...summary,
|
||||
session_version:
|
||||
detail.session_version ?? summary.session_version ?? summary.version ?? null,
|
||||
version: detail.session_version ?? summary.session_version ?? summary.version ?? null,
|
||||
profile: detail.profile || null,
|
||||
findings: detail.findings || [],
|
||||
semantic_sources: detail.semantic_sources || [],
|
||||
semantic_fields: detail.semantic_fields || [],
|
||||
imported_filters: detail.filters || detail.imported_filters || [],
|
||||
template_variables: detail.template_variables || [],
|
||||
execution_mappings: detail.mappings || detail.execution_mappings || [],
|
||||
clarification: detail.clarification || null,
|
||||
clarification_sessions: detail.clarification
|
||||
? [detail.clarification.clarification_session].filter(Boolean)
|
||||
: detail.clarification_sessions || [],
|
||||
previews: preview ? [preview] : detail.previews || [],
|
||||
preview,
|
||||
latest_run_context: detail.latest_run_context || null,
|
||||
};
|
||||
}
|
||||
// #endregion normalizeDatasetReviewDetail:Function
|
||||
|
||||
// #endregion DatasetReviewApi:Module
|
||||
// #endregion DatasetReviewApi
|
||||
142
frontend/src/lib/api/datasetReview.ts
Normal file
142
frontend/src/lib/api/datasetReview.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
// #region DatasetReviewApi [C:3] [TYPE Module] [SEMANTICS dataset, review, api, session, conflict]
|
||||
// @BRIEF Dataset review session-scoped API helpers — optimistic-lock propagation, conflict detection, DTO normalization.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @PRE All mutations go through requestApi with X-Session-Version header when version is known.
|
||||
// @POST Returns typed API responses, conflict detection booleans, or normalized session shapes.
|
||||
|
||||
import { requestApi } from '$lib/api';
|
||||
import type { FetchOptions } from '$lib/types/api';
|
||||
|
||||
// #region buildDatasetReviewRequestOptions [C:2] [TYPE Function] [SEMANTICS dataset, review, headers, version]
|
||||
// @BRIEF Attach optimistic-lock session version header when the current version is known.
|
||||
// @PRE sessionVersion may be null when no loaded session version exists yet.
|
||||
// @POST Returns requestApi-compatible options object with optional X-Session-Version header.
|
||||
export function buildDatasetReviewRequestOptions(sessionVersion: string | number | null | undefined): FetchOptions {
|
||||
if (sessionVersion === null || sessionVersion === undefined || sessionVersion === '') {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
headers: {
|
||||
'X-Session-Version': String(sessionVersion),
|
||||
},
|
||||
};
|
||||
}
|
||||
// #endregion buildDatasetReviewRequestOptions
|
||||
|
||||
// #region requestDatasetReviewApi [C:2] [TYPE Function] [SEMANTICS dataset, review, mutation, proxy]
|
||||
// @BRIEF Proxy dataset review mutations through requestApi with optional optimistic-lock headers.
|
||||
// @PRE endpoint and method are valid requestApi inputs.
|
||||
// @POST Executes requestApi with X-Session-Version only when a session version is known.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export function requestDatasetReviewApi<T = unknown>(
|
||||
endpoint: string,
|
||||
method: string = 'GET',
|
||||
body: unknown = null,
|
||||
sessionVersion: string | number | null = null,
|
||||
): Promise<T> {
|
||||
const requestOptions = buildDatasetReviewRequestOptions(sessionVersion);
|
||||
if (requestOptions.headers) {
|
||||
return requestApi<T>(endpoint, method, body, requestOptions);
|
||||
}
|
||||
return requestApi<T>(endpoint, method, body);
|
||||
}
|
||||
// #endregion requestDatasetReviewApi
|
||||
|
||||
// #region isDatasetReviewConflictError [C:1] [TYPE Function] [SEMANTICS dataset, review, conflict]
|
||||
// @BRIEF Detect optimistic-lock conflicts from dataset review mutations.
|
||||
// @PRE error may be null or a normalized API error.
|
||||
// @POST Returns true when the mutation failed with 409 conflict semantics.
|
||||
export function isDatasetReviewConflictError(error: { status?: number } | null | undefined): boolean {
|
||||
return Number(error?.status) === 409;
|
||||
}
|
||||
// #endregion isDatasetReviewConflictError
|
||||
|
||||
// #region getDatasetReviewConflictMessage [C:1] [TYPE Function] [SEMANTICS dataset, review, conflict, message]
|
||||
// @BRIEF Return user-facing 409-style guidance for stale dataset review mutations.
|
||||
// @PRE error may be null or a normalized API error.
|
||||
// @POST Returns a user-facing conflict message string.
|
||||
export function getDatasetReviewConflictMessage(error: { message?: string } | null | undefined): string {
|
||||
return (
|
||||
error?.message ||
|
||||
'This review session changed in another action. Reload the latest session state and retry the update.'
|
||||
);
|
||||
}
|
||||
// #endregion getDatasetReviewConflictMessage
|
||||
|
||||
// #region extractDatasetReviewVersion [C:2] [TYPE Function] [SEMANTICS dataset, review, version, extraction]
|
||||
// @BRIEF Resolve the latest session version from refreshed DTO fragments.
|
||||
// @PRE payload may be a session summary, a detail payload, or a mutation fragment.
|
||||
// @POST Returns the newest known session version or null.
|
||||
// @RATIONALE Recursively walks nested session/version fields because the API response shape
|
||||
// varies between list (summary), detail (nested), and mutation (flat) endpoints.
|
||||
export function extractDatasetReviewVersion(payload: Record<string, unknown> | unknown[] | null | undefined): string | number | null {
|
||||
if (!payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
for (const item of payload) {
|
||||
const version = extractDatasetReviewVersion(item as Record<string, unknown>);
|
||||
if (version !== null) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const obj = payload as Record<string, unknown>;
|
||||
if (obj.session_version !== undefined && obj.session_version !== null) {
|
||||
return obj.session_version as string | number;
|
||||
}
|
||||
if (obj.version !== undefined && obj.version !== null) {
|
||||
return obj.version as string | number;
|
||||
}
|
||||
if (obj.session) {
|
||||
return extractDatasetReviewVersion(obj.session as Record<string, unknown>);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
// #endregion extractDatasetReviewVersion
|
||||
|
||||
// #region normalizeDatasetReviewDetail [C:2] [TYPE Function] [SEMANTICS dataset, review, normalize, dto]
|
||||
// @BRIEF Normalize refreshed session-detail DTOs into the legacy route-friendly workspace shape.
|
||||
// @PRE detail may already be legacy-flat or refreshed with nested session/session_version fields.
|
||||
// @POST Returns a shape with flattened summary fields plus refreshed collections and version metadata.
|
||||
export function normalizeDatasetReviewDetail<T extends Record<string, unknown> | null | undefined>(detail: T): Record<string, unknown> | null {
|
||||
if (!detail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const obj = detail as Record<string, unknown>;
|
||||
if (!obj.session && obj.session_id) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const summary = (obj.session || {}) as Record<string, unknown>;
|
||||
const preview = obj.preview || null;
|
||||
|
||||
return {
|
||||
...summary,
|
||||
session_version:
|
||||
obj.session_version ?? summary.session_version ?? summary.version ?? null,
|
||||
version: obj.session_version ?? summary.session_version ?? summary.version ?? null,
|
||||
profile: obj.profile || null,
|
||||
findings: obj.findings || [],
|
||||
semantic_sources: obj.semantic_sources || [],
|
||||
semantic_fields: obj.semantic_fields || [],
|
||||
imported_filters: obj.filters || obj.imported_filters || [],
|
||||
template_variables: obj.template_variables || [],
|
||||
execution_mappings: obj.mappings || obj.execution_mappings || [],
|
||||
clarification: obj.clarification || null,
|
||||
clarification_sessions: obj.clarification
|
||||
? [(obj.clarification as Record<string, unknown>).clarification_session].filter(Boolean)
|
||||
: obj.clarification_sessions || [],
|
||||
previews: preview ? [preview] : obj.previews || [],
|
||||
preview,
|
||||
latest_run_context: obj.latest_run_context || null,
|
||||
};
|
||||
}
|
||||
// #endregion normalizeDatasetReviewDetail
|
||||
// #endregion DatasetReviewApi
|
||||
@@ -1,69 +0,0 @@
|
||||
// #region MaintenanceApi [C:2] [TYPE Module] [SEMANTICS api, maintenance, rest, client]
|
||||
// @BRIEF API client for Maintenance Banner endpoints. Uses requestApi for all calls.
|
||||
// @LAYER Frontend
|
||||
// @RELATION DEPENDS_ON -> [EXT:frontend:requestApi]
|
||||
// @ADR-0007: Uses $effect(() => subscribe(...)) pattern, NOT fromStore + $derived.
|
||||
|
||||
import { requestApi } from "$lib/api.js";
|
||||
|
||||
const BASE = "/maintenance";
|
||||
|
||||
/**
|
||||
* Start a maintenance event.
|
||||
* @param {object} params - { tables, start_time, end_time?, message? }
|
||||
* @returns {Promise<object>} { task_id, maintenance_id, status }
|
||||
*/
|
||||
export async function startMaintenance(params) {
|
||||
return requestApi(`${BASE}/start`, "POST", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* End a specific maintenance event.
|
||||
* @param {string} maintenanceId
|
||||
* @returns {Promise<object>} { task_id, status }
|
||||
*/
|
||||
export async function endMaintenance(maintenanceId) {
|
||||
return requestApi(`${BASE}/${maintenanceId}/end`, "POST");
|
||||
}
|
||||
|
||||
/**
|
||||
* End all active maintenance events.
|
||||
* @returns {Promise<object>} { task_id, status }
|
||||
*/
|
||||
export async function endAllMaintenance() {
|
||||
return requestApi(`${BASE}/end-all`, "POST");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current settings.
|
||||
* @returns {Promise<object>} MaintenanceSettingsResponse
|
||||
*/
|
||||
export async function getSettings() {
|
||||
return requestApi(`${BASE}/settings`, "GET");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings.
|
||||
* @param {object} settings
|
||||
* @returns {Promise<object>} Updated MaintenanceSettingsResponse
|
||||
*/
|
||||
export async function updateSettings(settings) {
|
||||
return requestApi(`${BASE}/settings`, "PUT", settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* List events.
|
||||
* @returns {Promise<object>} { active: [...], completed: [...] }
|
||||
*/
|
||||
export async function listEvents() {
|
||||
return requestApi(`${BASE}/events`, "GET");
|
||||
}
|
||||
|
||||
/**
|
||||
* List dashboard banner states.
|
||||
* @returns {Promise<object>} Array of { dashboard_id, active, events, start_time, end_time }
|
||||
*/
|
||||
export async function listDashboardBanners() {
|
||||
return requestApi(`${BASE}/dashboard-banners`, "GET");
|
||||
}
|
||||
// #endregion MaintenanceApi
|
||||
82
frontend/src/lib/api/maintenance.ts
Normal file
82
frontend/src/lib/api/maintenance.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
// #region MaintenanceApi [C:3] [TYPE Module] [SEMANTICS api, maintenance, banners, settings, events]
|
||||
// @BRIEF API client for Maintenance Banner endpoints — start/end events, settings, dashboard banners.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @PRE All calls go through requestApi wrapper.
|
||||
// @POST Returns typed API responses for maintenance lifecycle operations.
|
||||
// @ADR-0007 Uses $effect(() => subscribe(...)) pattern, NOT fromStore + $derived.
|
||||
|
||||
import { requestApi } from '$lib/api';
|
||||
|
||||
const BASE = '/maintenance';
|
||||
|
||||
// #region startMaintenance [C:2] [TYPE Function] [SEMANTICS maintenance, start, event]
|
||||
// @BRIEF Start a maintenance event.
|
||||
// @PRE params contains at minimum { tables, start_time }.
|
||||
// @POST Returns { task_id, maintenance_id, status }.
|
||||
// @SIDE_EFFECT Creates a maintenance event on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function startMaintenance<T = unknown>(params: Record<string, unknown>): Promise<T> {
|
||||
return requestApi<T>(`${BASE}/start`, 'POST', params);
|
||||
}
|
||||
// #endregion startMaintenance
|
||||
|
||||
// #region endMaintenance [C:2] [TYPE Function] [SEMANTICS maintenance, end, event]
|
||||
// @BRIEF End a specific maintenance event.
|
||||
// @PRE maintenanceId is a non-empty string.
|
||||
// @POST Returns { task_id, status }.
|
||||
// @SIDE_EFFECT Closes the specified maintenance event on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function endMaintenance<T = unknown>(maintenanceId: string): Promise<T> {
|
||||
return requestApi<T>(`${BASE}/${maintenanceId}/end`, 'POST');
|
||||
}
|
||||
// #endregion endMaintenance
|
||||
|
||||
// #region endAllMaintenance [C:2] [TYPE Function] [SEMANTICS maintenance, end-all, events]
|
||||
// @BRIEF End all active maintenance events.
|
||||
// @POST Returns { task_id, status }.
|
||||
// @SIDE_EFFECT Closes all active maintenance events on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function endAllMaintenance<T = unknown>(): Promise<T> {
|
||||
return requestApi<T>(`${BASE}/end-all`, 'POST');
|
||||
}
|
||||
// #endregion endAllMaintenance
|
||||
|
||||
// #region getMaintenanceSettings [C:2] [TYPE Function] [SEMANTICS maintenance, settings, get]
|
||||
// @BRIEF Get current maintenance settings.
|
||||
// @POST Returns MaintenanceSettingsResponse.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function getSettings<T = unknown>(): Promise<T> {
|
||||
return requestApi<T>(`${BASE}/settings`, 'GET');
|
||||
}
|
||||
// #endregion getMaintenanceSettings
|
||||
|
||||
// #region updateSettings [C:2] [TYPE Function] [SEMANTICS maintenance, settings, update]
|
||||
// @BRIEF Update maintenance settings.
|
||||
// @PRE settings is a valid MaintenanceSettings payload.
|
||||
// @POST Returns updated MaintenanceSettingsResponse.
|
||||
// @SIDE_EFFECT Persists settings changes on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function updateSettings<T = unknown>(settings: Record<string, unknown>): Promise<T> {
|
||||
return requestApi<T>(`${BASE}/settings`, 'PUT', settings);
|
||||
}
|
||||
// #endregion updateSettings
|
||||
|
||||
// #region listEvents [C:2] [TYPE Function] [SEMANTICS maintenance, events, list]
|
||||
// @BRIEF List active and completed maintenance events.
|
||||
// @POST Returns { active: [...], completed: [...] }.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function listEvents<T = unknown>(): Promise<T> {
|
||||
return requestApi<T>(`${BASE}/events`, 'GET');
|
||||
}
|
||||
// #endregion listEvents
|
||||
|
||||
// #region listDashboardBanners [C:2] [TYPE Function] [SEMANTICS maintenance, dashboard, banners]
|
||||
// @BRIEF List dashboard banner states for all dashboards.
|
||||
// @POST Returns array of { dashboard_id, active, events, start_time, end_time }.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function listDashboardBanners<T = unknown>(): Promise<T> {
|
||||
return requestApi<T>(`${BASE}/dashboard-banners`, 'GET');
|
||||
}
|
||||
// #endregion listDashboardBanners
|
||||
// #endregion MaintenanceApi
|
||||
@@ -1,116 +0,0 @@
|
||||
// #region ReportsApi [C:4] [TYPE Module] [SEMANTICS report, api, search, filter, query]
|
||||
// @BRIEF Wrapper-based reports API client for list/detail retrieval through the shared API wrapper.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @PRE Shared API wrapper is configured for the current frontend auth/session context.
|
||||
// @POST Report list and detail helpers return backend payloads or normalized UI-safe errors.
|
||||
// @SIDE_EFFECT Performs authenticated report HTTP requests through the shared API wrapper.
|
||||
|
||||
// #region ReportsApi:Module [TYPE Function]
|
||||
// @SEMANTICS: frontend, api_client, reports, wrapper
|
||||
// @PURPOSE: Wrapper-based reports API client for list/detail retrieval without direct native fetch usage.
|
||||
// @LAYER Infra
|
||||
// @RELATION DEPENDS_ON -> [EXT:frontend:api_module]
|
||||
// @PRE: Shared API wrapper is configured for the current frontend auth/session context.
|
||||
// @POST: Report list and detail helpers return backend payloads or normalized UI-safe errors.
|
||||
// @SIDE_EFFECT: Performs authenticated report HTTP requests through the shared API wrapper.
|
||||
// @INVARIANT: Uses existing api wrapper methods and returns structured errors for UI-state mapping.
|
||||
import { api } from '../api.js';
|
||||
|
||||
// #region buildReportQueryString:Function [TYPE Function]
|
||||
// @PURPOSE: Build query string for reports list endpoint from filter options.
|
||||
// @PRE: options is an object with optional report query fields.
|
||||
// @POST: Returns URL query string without leading '?'.
|
||||
export function buildReportQueryString(options = {}) {
|
||||
console.log("[reports][EXT:frontend:api][buildReportQueryString:START]");
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
|
||||
if (Array.isArray(options.task_types) && options.task_types.length > 0) {
|
||||
params.append('task_types', options.task_types.join(','));
|
||||
}
|
||||
if (Array.isArray(options.statuses) && options.statuses.length > 0) {
|
||||
params.append('statuses', options.statuses.join(','));
|
||||
}
|
||||
|
||||
if (options.time_from) params.append('time_from', options.time_from);
|
||||
if (options.time_to) params.append('time_to', options.time_to);
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.sort_by) params.append('sort_by', options.sort_by);
|
||||
if (options.sort_order) params.append('sort_order', options.sort_order);
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
// #endregion buildReportQueryString:Function
|
||||
|
||||
// #region normalizeApiError:Function [TYPE Function]
|
||||
// @PURPOSE: Convert unknown API exceptions into deterministic UI-consumable error objects.
|
||||
// @PRE: error may be Error/string/object.
|
||||
// @POST: Returns structured error object.
|
||||
export function normalizeApiError(error) {
|
||||
console.log("[reports][EXT:frontend:api][normalizeApiError:START]");
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
'Failed to load reports';
|
||||
|
||||
return {
|
||||
message,
|
||||
code: 'REPORTS_API_ERROR',
|
||||
retryable: true
|
||||
};
|
||||
}
|
||||
// #endregion normalizeApiError:Function
|
||||
|
||||
// #region getReports:Function [TYPE Function]
|
||||
// @PURPOSE: Fetch unified report list using existing request wrapper.
|
||||
// @PRE: valid auth context for protected endpoint.
|
||||
// @POST: Returns parsed payload or structured error for UI-state mapping.
|
||||
//
|
||||
// @TEST_CONTRACT: GetReportsApi ->
|
||||
// {
|
||||
// required_fields: {},
|
||||
// optional_fields: {options: Object},
|
||||
// invariants: [
|
||||
// "Fetches from /reports with built query string",
|
||||
// "Returns response payload on success",
|
||||
// "Catches and normalizes errors using normalizeApiError"
|
||||
// ]
|
||||
// }
|
||||
// @TEST_FIXTURE: valid_get_reports -> {"options": {"page": 1}}
|
||||
// @TEST_EDGE: api_fetch_failure -> api.fetchApi throws error
|
||||
// @TEST_INVARIANT: error_normalization -> verifies: [api_fetch_failure]
|
||||
export async function getReports(options = {}) {
|
||||
try {
|
||||
console.log("[reports][EXT:frontend:api][getReports:STARTED]", options);
|
||||
const query = buildReportQueryString(options);
|
||||
const res = await api.fetchApi(`/reports${query ? `?${query}` : ''}`);
|
||||
console.log("[reports][EXT:frontend:api][getReports:SUCCESS]", res);
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error("[reports][EXT:frontend:api][getReports:FAILED]", error);
|
||||
throw normalizeApiError(error);
|
||||
}
|
||||
}
|
||||
// #endregion getReports:Function
|
||||
|
||||
// #region getReportDetail:Function [TYPE Function]
|
||||
// @PURPOSE: Fetch one report detail by report_id.
|
||||
// @PRE: reportId is non-empty string; valid auth context.
|
||||
// @POST: Returns parsed detail payload or structured error object.
|
||||
export async function getReportDetail(reportId) {
|
||||
try {
|
||||
console.log(`[reports][EXT:frontend:api][getReportDetail:STARTED] id=${reportId}`);
|
||||
const res = await api.fetchApi(`/reports/${reportId}`);
|
||||
console.log(`[reports][EXT:frontend:api][getReportDetail:SUCCESS] id=${reportId}`);
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error(`[reports][EXT:frontend:api][getReportDetail:FAILED] id=${reportId}`, error);
|
||||
throw normalizeApiError(error);
|
||||
}
|
||||
}
|
||||
// #endregion getReportDetail:Function
|
||||
|
||||
// #endregion ReportsApi:Module
|
||||
// #endregion ReportsApi
|
||||
105
frontend/src/lib/api/reports.ts
Normal file
105
frontend/src/lib/api/reports.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// #region ReportsApi [C:3] [TYPE Module] [SEMANTICS report, api, search, filter, query]
|
||||
// @BRIEF Reports API client — list/detail retrieval, query-string building, error normalization.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @PRE Shared API wrapper is configured for the current frontend auth/session context.
|
||||
// @POST Report list and detail helpers return backend payloads or normalized UI-safe errors.
|
||||
|
||||
import { api } from '../api';
|
||||
|
||||
// #region reportApiTypes [C:1] [TYPE Block] [SEMANTICS types, reports]
|
||||
interface ReportQueryOptions {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
task_types?: string[];
|
||||
statuses?: string[];
|
||||
time_from?: string;
|
||||
time_to?: string;
|
||||
search?: string;
|
||||
sort_by?: string;
|
||||
sort_order?: string;
|
||||
}
|
||||
|
||||
interface NormalizedError {
|
||||
message: string;
|
||||
code: string;
|
||||
retryable: boolean;
|
||||
}
|
||||
// #endregion reportApiTypes
|
||||
|
||||
// #region buildReportQueryString [C:2] [TYPE Function] [SEMANTICS report, query, params]
|
||||
// @BRIEF Build query string for reports list endpoint from filter options.
|
||||
// @PRE options is an object with optional report query fields.
|
||||
// @POST Returns URL query string without leading '?'.
|
||||
export function buildReportQueryString(options: ReportQueryOptions = {}): string {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
|
||||
if (Array.isArray(options.task_types) && options.task_types.length > 0) {
|
||||
params.append('task_types', options.task_types.join(','));
|
||||
}
|
||||
if (Array.isArray(options.statuses) && options.statuses.length > 0) {
|
||||
params.append('statuses', options.statuses.join(','));
|
||||
}
|
||||
|
||||
if (options.time_from) params.append('time_from', options.time_from);
|
||||
if (options.time_to) params.append('time_to', options.time_to);
|
||||
if (options.search) params.append('search', options.search);
|
||||
if (options.sort_by) params.append('sort_by', options.sort_by);
|
||||
if (options.sort_order) params.append('sort_order', options.sort_order);
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
// #endregion buildReportQueryString
|
||||
|
||||
// #region normalizeApiError [C:2] [TYPE Function] [SEMANTICS report, error, normalization]
|
||||
// @BRIEF Convert unknown API exceptions into deterministic UI-consumable error objects.
|
||||
// @PRE error may be Error/string/object.
|
||||
// @POST Returns structured error object with message, code, retryable flag.
|
||||
export function normalizeApiError(error: unknown): NormalizedError {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
'Failed to load reports';
|
||||
|
||||
return {
|
||||
message,
|
||||
code: 'REPORTS_API_ERROR',
|
||||
retryable: true,
|
||||
};
|
||||
}
|
||||
// #endregion normalizeApiError
|
||||
|
||||
// #region getReports [C:3] [TYPE Function] [SEMANTICS report, list, fetch]
|
||||
// @BRIEF Fetch unified report list using the shared API wrapper.
|
||||
// @PRE Valid auth context for protected endpoint.
|
||||
// @POST Returns parsed payload or throws normalized error.
|
||||
// @SIDE_EFFECT Performs authenticated GET request through api.fetchApi.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function getReports<T = unknown>(options: ReportQueryOptions = {}): Promise<T> {
|
||||
try {
|
||||
const query = buildReportQueryString(options);
|
||||
return await api.fetchApi<T>(`/reports${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeApiError(error);
|
||||
}
|
||||
}
|
||||
// #endregion getReports
|
||||
|
||||
// #region getReportDetail [C:3] [TYPE Function] [SEMANTICS report, detail, fetch]
|
||||
// @BRIEF Fetch one report detail by report_id.
|
||||
// @PRE reportId is non-empty string; valid auth context.
|
||||
// @POST Returns parsed detail payload or throws normalized error.
|
||||
// @SIDE_EFFECT Performs authenticated GET request through api.fetchApi.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function getReportDetail<T = unknown>(reportId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/reports/${reportId}`);
|
||||
} catch (error) {
|
||||
throw normalizeApiError(error);
|
||||
}
|
||||
}
|
||||
// #endregion getReportDetail
|
||||
// #endregion ReportsApi
|
||||
@@ -1,31 +1,34 @@
|
||||
// #region TranslateApi [C:2] [TYPE Module] [SEMANTICS translate, api, barrel]
|
||||
// @BRIEF Barrel module re-exporting all translate API functions from domain-split modules.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [TranslateJobsApi]
|
||||
// @RELATION DEPENDS_ON -> [TranslateRunsApi]
|
||||
// @RELATION DEPENDS_ON -> [TranslateDictionariesApi]
|
||||
// @RELATION DEPENDS_ON -> [TranslateSchedulesApi]
|
||||
// @RELATION DEPENDS_ON -> [TranslateCorrectionsApi]
|
||||
// @RATIONALE Decomposed from 664->~30 lines by splitting into domain modules per INV_7.
|
||||
// @RELATION DEPENDS_ON -> [TranslateDatasourcesApi]
|
||||
// @RELATION DEPENDS_ON -> [TranslateTargetSchemaApi]
|
||||
// @RATIONALE Decomposed from 664→~30 lines by splitting into domain modules per INV_7.
|
||||
// @REJECTED Keeping all APIs in one file was rejected because it exceeded the 400-line module limit.
|
||||
|
||||
// Jobs
|
||||
export { fetchJobs, createJob, updateJob, deleteJob, duplicateJob } from './translate/jobs.js';
|
||||
export { fetchJobs, createJob, updateJob, deleteJob, duplicateJob } from './translate/jobs';
|
||||
|
||||
// Datasources & preview
|
||||
export { fetchDatasourceColumns, fetchDatasources, fetchPreview, approveRow, editRow, rejectRow, acceptPreview, fetchPreviewRecords } from './translate/datasources.js';
|
||||
export { fetchDatasourceColumns, fetchDatasources, fetchPreview, approveRow, editRow, rejectRow, acceptPreview, fetchPreviewRecords } from './translate/datasources';
|
||||
|
||||
// Runs
|
||||
export { triggerRun, fetchRunStatus, fetchRunHistory, fetchRunRecords, retryFailedBatches, retryInsert, cancelRun, fetchRunBatches, fetchAllRuns, fetchRunDetail, fetchJobMetrics, fetchAllMetrics } from './translate/runs.js';
|
||||
export { triggerRun, fetchRunStatus, fetchRunHistory, fetchRunRecords, retryFailedBatches, retryInsert, cancelRun, fetchRunBatches, fetchAllRuns, fetchRunDetail, fetchJobMetrics, fetchAllMetrics } from './translate/runs';
|
||||
|
||||
// Dictionaries
|
||||
export { dictionaryApi } from './translate/dictionaries.js';
|
||||
export { dictionaryApi } from './translate/dictionaries';
|
||||
|
||||
// Schedules
|
||||
export { fetchSchedule, setSchedule, deleteSchedule, enableSchedule, disableSchedule, fetchNextExecutions } from './translate/schedules.js';
|
||||
export { fetchSchedule, setSchedule, deleteSchedule, enableSchedule, disableSchedule, fetchNextExecutions } from './translate/schedules';
|
||||
|
||||
// Corrections & bulk replace
|
||||
export { submitCorrection, submitBulkCorrections, inlineEditCorrection, submitCorrectionToDict, bulkFindReplace, bulkReplacePreview, downloadSkippedCsv } from './translate/corrections.js';
|
||||
export { submitCorrection, submitBulkCorrections, inlineEditCorrection, submitCorrectionToDict, bulkFindReplace, bulkReplacePreview, downloadSkippedCsv } from './translate/corrections';
|
||||
|
||||
// Target schema validation
|
||||
export { checkTargetTableSchema } from './translate/target-schema.js';
|
||||
export { checkTargetTableSchema } from './translate/target-schema';
|
||||
// #endregion TranslateApi
|
||||
@@ -1,85 +0,0 @@
|
||||
// #region TranslateCorrectionsApi [C:2] [TYPE Module] [SEMANTICS translate, corrections, bulk-replace, api]
|
||||
// @BRIEF API client for translation corrections — inline edits, dictionary submission, bulk find-and-replace, CSV download.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
function normalizeTranslateError(error, defaultMessage = 'Translation API error') {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR', retryable: true };
|
||||
}
|
||||
|
||||
export async function submitCorrection(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/corrections', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit correction');
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitBulkCorrections(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/corrections/bulk', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit bulk corrections');
|
||||
}
|
||||
}
|
||||
|
||||
export async function inlineEditCorrection(runId, recordId, languageCode, data) {
|
||||
try {
|
||||
return await api.requestApi(
|
||||
`/translate/runs/${runId}/records/${recordId}/languages/${languageCode}`,
|
||||
'PUT',
|
||||
data
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to apply inline correction');
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitCorrectionToDict(runId, recordId, languageCode, dictId) {
|
||||
try {
|
||||
return await api.requestApi(
|
||||
`/translate/runs/${runId}/records/${recordId}/languages/${languageCode}/submit-to-dict`,
|
||||
'POST',
|
||||
{ dictionary_id: dictId }
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit correction to dictionary');
|
||||
}
|
||||
}
|
||||
|
||||
export async function bulkFindReplace(runId, data) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/bulk-replace`, data);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to perform bulk find-and-replace');
|
||||
}
|
||||
}
|
||||
|
||||
export async function bulkReplacePreview(runId, data) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/bulk-replace`, { ...data, preview: true });
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to preview bulk find-and-replace');
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadSkippedCsv(runId) {
|
||||
try {
|
||||
const blob = await api.fetchApiBlob(`/translate/runs/${runId}/skipped.csv`);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `skipped-${runId.substring(0, 8)}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to download skipped CSV');
|
||||
}
|
||||
}
|
||||
// #endregion TranslateCorrectionsApi
|
||||
137
frontend/src/lib/api/translate/corrections.ts
Normal file
137
frontend/src/lib/api/translate/corrections.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
// #region TranslateCorrectionsApi [C:2] [TYPE Module] [SEMANTICS translate, corrections, bulk-replace, api]
|
||||
// @BRIEF API client for translation corrections — inline edits, dictionary submission, bulk find-and-replace, CSV download.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @POST All functions return typed responses or throw normalized TranslateApiError.
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import type { TranslateApiError } from './jobs';
|
||||
|
||||
function normalizeTranslateError(error: unknown, defaultMessage: string = 'Translation API error'): TranslateApiError {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR' as const, retryable: true };
|
||||
}
|
||||
|
||||
// #region submitCorrection [C:2] [TYPE Function] [SEMANTICS translate, corrections, submit]
|
||||
// @BRIEF Submit a single correction for a translation record.
|
||||
// @PRE payload contains record_id, language_code, and corrected_text.
|
||||
// @POST Returns correction confirmation.
|
||||
// @SIDE_EFFECT Updates the translation on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function submitCorrection<T = unknown>(payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>('/translate/corrections', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit correction');
|
||||
}
|
||||
}
|
||||
// #endregion submitCorrection
|
||||
|
||||
// #region submitBulkCorrections [C:2] [TYPE Function] [SEMANTICS translate, corrections, bulk, submit]
|
||||
// @BRIEF Submit multiple corrections in a single request.
|
||||
// @PRE payload contains array of correction objects.
|
||||
// @POST Returns bulk confirmation with per-item results.
|
||||
// @SIDE_EFFECT Updates multiple translations on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function submitBulkCorrections<T = unknown>(payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>('/translate/corrections/bulk', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit bulk corrections');
|
||||
}
|
||||
}
|
||||
// #endregion submitBulkCorrections
|
||||
|
||||
// #region inlineEditCorrection [C:2] [TYPE Function] [SEMANTICS translate, corrections, inline, edit]
|
||||
// @BRIEF Apply an inline correction to a specific record+language combination in a run.
|
||||
// @PRE runId, recordId, languageCode are non-empty strings. data contains corrected values.
|
||||
// @POST Returns updated record data.
|
||||
// @SIDE_EFFECT Modifies translation for the specified record+language.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function inlineEditCorrection<T = unknown>(runId: string, recordId: string, languageCode: string, data: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.requestApi<T>(
|
||||
`/translate/runs/${runId}/records/${recordId}/languages/${languageCode}`,
|
||||
'PUT',
|
||||
data
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to apply inline correction');
|
||||
}
|
||||
}
|
||||
// #endregion inlineEditCorrection
|
||||
|
||||
// #region submitCorrectionToDict [C:2] [TYPE Function] [SEMANTICS translate, corrections, dictionary, submit]
|
||||
// @BRIEF Submit a correction to be added as a dictionary entry.
|
||||
// @PRE runId, recordId, languageCode, dictId are non-empty strings.
|
||||
// @POST Creates a dictionary entry from the correction.
|
||||
// @SIDE_EFFECT Adds entry to the specified dictionary on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function submitCorrectionToDict<T = unknown>(runId: string, recordId: string, languageCode: string, dictId: string): Promise<T> {
|
||||
try {
|
||||
return await api.requestApi<T>(
|
||||
`/translate/runs/${runId}/records/${recordId}/languages/${languageCode}/submit-to-dict`,
|
||||
'POST',
|
||||
{ dictionary_id: dictId }
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit correction to dictionary');
|
||||
}
|
||||
}
|
||||
// #endregion submitCorrectionToDict
|
||||
|
||||
// #region bulkFindReplace [C:2] [TYPE Function] [SEMANTICS translate, corrections, bulk, find-replace]
|
||||
// @BRIEF Perform bulk find-and-replace across a translation run.
|
||||
// @PRE runId is a non-empty string. data contains find/replace patterns.
|
||||
// @POST Returns bulk operation results.
|
||||
// @SIDE_EFFECT Applies find-and-replace to multiple records.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function bulkFindReplace<T = unknown>(runId: string, data: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/runs/${runId}/bulk-replace`, data);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to perform bulk find-and-replace');
|
||||
}
|
||||
}
|
||||
// #endregion bulkFindReplace
|
||||
|
||||
// #region bulkReplacePreview [C:2] [TYPE Function] [SEMANTICS translate, corrections, bulk, preview]
|
||||
// @BRIEF Preview bulk find-and-replace operation (dry run, no changes applied).
|
||||
// @PRE runId is a non-empty string. data contains find/replace patterns.
|
||||
// @POST Returns preview with affected record count. No changes are persisted.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function bulkReplacePreview<T = unknown>(runId: string, data: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/runs/${runId}/bulk-replace`, { ...data, preview: true });
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to preview bulk find-and-replace');
|
||||
}
|
||||
}
|
||||
// #endregion bulkReplacePreview
|
||||
|
||||
// #region downloadSkippedCsv [C:2] [TYPE Function] [SEMANTICS translate, corrections, csv, download]
|
||||
// @BRIEF Download a CSV of skipped records for a translation run.
|
||||
// @PRE runId is a non-empty string.
|
||||
// @POST Triggers browser download of a CSV file. Returns nothing on success.
|
||||
// @SIDE_EFFECT Creates a Blob URL and programmatically clicks a download anchor.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApiBlob]
|
||||
export async function downloadSkippedCsv(runId: string): Promise<void> {
|
||||
try {
|
||||
const blob = await api.fetchApiBlob(`/translate/runs/${runId}/skipped.csv`);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `skipped-${runId.substring(0, 8)}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to download skipped CSV');
|
||||
}
|
||||
}
|
||||
// #endregion downloadSkippedCsv
|
||||
// #endregion TranslateCorrectionsApi
|
||||
@@ -1,89 +0,0 @@
|
||||
// #region TranslateDatasourcesApi [C:2] [TYPE Module] [SEMANTICS translate, datasources, columns, preview, api]
|
||||
// @BRIEF API client for translation datasources — column fetching, preview, row-level review actions.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
function normalizeTranslateError(error, defaultMessage = 'Translation API error') {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR', retryable: true };
|
||||
}
|
||||
|
||||
export async function fetchDatasources(envId, search = '') {
|
||||
try {
|
||||
return await api.fetchApi(
|
||||
`/translate/datasources?env_id=${encodeURIComponent(envId)}&search=${encodeURIComponent(search)}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch datasources');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDatasourceColumns(datasourceId, envId) {
|
||||
try {
|
||||
return await api.fetchApi(
|
||||
`/translate/datasources/${datasourceId}/columns?env_id=${encodeURIComponent(envId)}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch datasource columns');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPreview(jobId, sampleSize = 10, envId = '') {
|
||||
try {
|
||||
const body = { sample_size: sampleSize };
|
||||
if (envId) body.env_id = envId;
|
||||
return await api.postApi(`/translate/jobs/${jobId}/preview`, body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to run preview');
|
||||
}
|
||||
}
|
||||
|
||||
export async function approveRow(jobId, rowKey, languageCode = null) {
|
||||
try {
|
||||
const body = { action: 'approve' };
|
||||
if (languageCode) body.language_code = languageCode;
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to approve row');
|
||||
}
|
||||
}
|
||||
|
||||
export async function editRow(jobId, rowKey, translation, languageCode = null) {
|
||||
try {
|
||||
const body = { action: 'edit', translation };
|
||||
if (languageCode) body.language_code = languageCode;
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to edit row');
|
||||
}
|
||||
}
|
||||
|
||||
export async function rejectRow(jobId, rowKey, languageCode = null) {
|
||||
try {
|
||||
const body = { action: 'reject' };
|
||||
if (languageCode) body.language_code = languageCode;
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to reject row');
|
||||
}
|
||||
}
|
||||
|
||||
export async function acceptPreview(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/preview/accept`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to accept preview');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPreviewRecords(sessionId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/preview/${sessionId}/records`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch preview records');
|
||||
}
|
||||
}
|
||||
// #endregion TranslateDatasourcesApi
|
||||
145
frontend/src/lib/api/translate/datasources.ts
Normal file
145
frontend/src/lib/api/translate/datasources.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
// #region TranslateDatasourcesApi [C:2] [TYPE Module] [SEMANTICS translate, datasources, columns, preview, api]
|
||||
// @BRIEF API client for translation datasources — column fetching, preview, row-level review actions.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @POST All functions return typed responses or throw normalized TranslateApiError.
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import type { TranslateApiError } from './jobs';
|
||||
|
||||
function normalizeTranslateError(error: unknown, defaultMessage: string = 'Translation API error'): TranslateApiError {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR' as const, retryable: true };
|
||||
}
|
||||
|
||||
// #region fetchDatasources [C:2] [TYPE Function] [SEMANTICS translate, datasources, list]
|
||||
// @BRIEF Fetch available datasources for a given environment.
|
||||
// @PRE envId is a non-empty string.
|
||||
// @POST Returns list of datasources matching the search.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchDatasources<T = unknown>(envId: string, search: string = ''): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(
|
||||
`/translate/datasources?env_id=${encodeURIComponent(envId)}&search=${encodeURIComponent(search)}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch datasources');
|
||||
}
|
||||
}
|
||||
// #endregion fetchDatasources
|
||||
|
||||
// #region fetchDatasourceColumns [C:2] [TYPE Function] [SEMANTICS translate, datasources, columns]
|
||||
// @BRIEF Fetch columns for a specific datasource.
|
||||
// @PRE datasourceId and envId are non-empty strings.
|
||||
// @POST Returns list of column definitions.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchDatasourceColumns<T = unknown>(datasourceId: string, envId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(
|
||||
`/translate/datasources/${datasourceId}/columns?env_id=${encodeURIComponent(envId)}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch datasource columns');
|
||||
}
|
||||
}
|
||||
// #endregion fetchDatasourceColumns
|
||||
|
||||
// #region fetchPreview [C:2] [TYPE Function] [SEMANTICS translate, preview, rows]
|
||||
// @BRIEF Fetch a sample preview of rows for a translation job.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns preview data with sample rows.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function fetchPreview<T = unknown>(jobId: string, sampleSize: number = 10, envId: string = ''): Promise<T> {
|
||||
try {
|
||||
const body: Record<string, unknown> = { sample_size: sampleSize };
|
||||
if (envId) body.env_id = envId;
|
||||
return await api.postApi<T>(`/translate/jobs/${jobId}/preview`, body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to run preview');
|
||||
}
|
||||
}
|
||||
// #endregion fetchPreview
|
||||
|
||||
// #region approveRow [C:2] [TYPE Function] [SEMANTICS translate, preview, approve, row]
|
||||
// @BRIEF Approve a specific preview row for translation.
|
||||
// @PRE jobId and rowKey are non-empty strings.
|
||||
// @POST Row is approved for translation.
|
||||
// @SIDE_EFFECT Updates the row status on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function approveRow<T = unknown>(jobId: string, rowKey: string, languageCode: string | null = null): Promise<T> {
|
||||
try {
|
||||
const body: Record<string, unknown> = { action: 'approve' };
|
||||
if (languageCode) body.language_code = languageCode;
|
||||
return await api.requestApi<T>(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to approve row');
|
||||
}
|
||||
}
|
||||
// #endregion approveRow
|
||||
|
||||
// #region editRow [C:2] [TYPE Function] [SEMANTICS translate, preview, edit, row]
|
||||
// @BRIEF Edit translation for a specific preview row.
|
||||
// @PRE jobId and rowKey are non-empty strings. translation is a non-empty string.
|
||||
// @POST Returns updated row data.
|
||||
// @SIDE_EFFECT Modifies the translation on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function editRow<T = unknown>(jobId: string, rowKey: string, translation: string, languageCode: string | null = null): Promise<T> {
|
||||
try {
|
||||
const body: Record<string, unknown> = { action: 'edit', translation };
|
||||
if (languageCode) body.language_code = languageCode;
|
||||
return await api.requestApi<T>(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to edit row');
|
||||
}
|
||||
}
|
||||
// #endregion editRow
|
||||
|
||||
// #region rejectRow [C:2] [TYPE Function] [SEMANTICS translate, preview, reject, row]
|
||||
// @BRIEF Reject a specific preview row (exclude from translation).
|
||||
// @PRE jobId and rowKey are non-empty strings.
|
||||
// @POST Row is marked as rejected and excluded from translation.
|
||||
// @SIDE_EFFECT Updates the row status on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function rejectRow<T = unknown>(jobId: string, rowKey: string, languageCode: string | null = null): Promise<T> {
|
||||
try {
|
||||
const body: Record<string, unknown> = { action: 'reject' };
|
||||
if (languageCode) body.language_code = languageCode;
|
||||
return await api.requestApi<T>(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', body);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to reject row');
|
||||
}
|
||||
}
|
||||
// #endregion rejectRow
|
||||
|
||||
// #region acceptPreview [C:2] [TYPE Function] [SEMANTICS translate, preview, accept, finalize]
|
||||
// @BRIEF Accept all approved rows in a preview session and start translation.
|
||||
// @PRE jobId is a non-empty string with a finalized preview session.
|
||||
// @POST Translation run is triggered for the accepted rows.
|
||||
// @SIDE_EFFECT Creates translation run from accepted preview rows.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function acceptPreview<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/jobs/${jobId}/preview/accept`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to accept preview');
|
||||
}
|
||||
}
|
||||
// #endregion acceptPreview
|
||||
|
||||
// #region fetchPreviewRecords [C:2] [TYPE Function] [SEMANTICS translate, preview, records]
|
||||
// @BRIEF Fetch records for a completed preview session.
|
||||
// @PRE sessionId is a non-empty string.
|
||||
// @POST Returns preview records with translated content.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchPreviewRecords<T = unknown>(sessionId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/preview/${sessionId}/records`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch preview records');
|
||||
}
|
||||
}
|
||||
// #endregion fetchPreviewRecords
|
||||
// #endregion TranslateDatasourcesApi
|
||||
@@ -1,109 +0,0 @@
|
||||
// #region TranslateDictionariesApi [C:2] [TYPE Module] [SEMANTICS translate, dictionaries, api, crud, entries]
|
||||
// @BRIEF API client for translation dictionary CRUD — list, create, update, delete, entries, import.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
function normalizeTranslateError(error, defaultMessage = 'Translation API error') {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR', retryable: true };
|
||||
}
|
||||
|
||||
export const dictionaryApi = {
|
||||
async fetchDictionaries(options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/dictionaries${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionaries');
|
||||
}
|
||||
},
|
||||
|
||||
async createDictionary(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/dictionaries', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async getDictionary(dictionaryId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/dictionaries/${dictionaryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async updateDictionary(dictionaryId, payload) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/dictionaries/${dictionaryId}`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async deleteDictionary(dictionaryId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/dictionaries/${dictionaryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async fetchEntries(dictionaryId, options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(
|
||||
`/translate/dictionaries/${dictionaryId}/entries${query ? `?${query}` : ''}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionary entries');
|
||||
}
|
||||
},
|
||||
|
||||
async createEntry(dictionaryId, payload) {
|
||||
try {
|
||||
return await api.postApi(`/translate/dictionaries/${dictionaryId}/entries`, payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create dictionary entry');
|
||||
}
|
||||
},
|
||||
|
||||
async updateEntry(dictionaryId, entryId, payload) {
|
||||
try {
|
||||
return await api.requestApi(
|
||||
`/translate/dictionaries/${dictionaryId}/entries/${entryId}`,
|
||||
'PUT',
|
||||
payload
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update dictionary entry');
|
||||
}
|
||||
},
|
||||
|
||||
async deleteEntry(dictionaryId, entryId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/dictionaries/${dictionaryId}/entries/${entryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete dictionary entry');
|
||||
}
|
||||
},
|
||||
|
||||
async importEntries(dictionaryId, payload) {
|
||||
try {
|
||||
return await api.postApi(`/translate/dictionaries/${dictionaryId}/import`, payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to import dictionary entries');
|
||||
}
|
||||
},
|
||||
};
|
||||
// #endregion TranslateDictionariesApi
|
||||
145
frontend/src/lib/api/translate/dictionaries.ts
Normal file
145
frontend/src/lib/api/translate/dictionaries.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
// #region TranslateDictionariesApi [C:3] [TYPE Module] [SEMANTICS translate, dictionaries, api, crud, entries]
|
||||
// @BRIEF API client for translation dictionary CRUD — list, create, update, delete, entries, import.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @POST All methods return typed responses or throw normalized TranslateApiError.
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import type { TranslateApiError } from './jobs';
|
||||
|
||||
function normalizeTranslateError(error: unknown, defaultMessage: string = 'Translation API error'): TranslateApiError {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR' as const, retryable: true };
|
||||
}
|
||||
|
||||
export interface PaginationOptions {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
// #region dictionaryApi [C:3] [TYPE Block] [SEMANTICS translate, dictionaries, api, crud]
|
||||
// @BRIEF Dictionary CRUD operations — each method wraps api.* with error normalization.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.deleteApi]
|
||||
export const dictionaryApi = {
|
||||
// #region fetchDictionaries [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, list]
|
||||
async fetchDictionaries<T = unknown>(options: PaginationOptions = {}): Promise<T> {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi<T>(`/translate/dictionaries${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionaries');
|
||||
}
|
||||
},
|
||||
// #endregion fetchDictionaries
|
||||
|
||||
// #region createDictionary [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, create]
|
||||
async createDictionary<T = unknown>(payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>('/translate/dictionaries', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create dictionary');
|
||||
}
|
||||
},
|
||||
// #endregion createDictionary
|
||||
|
||||
// #region getDictionary [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, get]
|
||||
async getDictionary<T = unknown>(dictionaryId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/dictionaries/${dictionaryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionary');
|
||||
}
|
||||
},
|
||||
// #endregion getDictionary
|
||||
|
||||
// #region updateDictionary [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, update]
|
||||
async updateDictionary<T = unknown>(dictionaryId: string, payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.requestApi<T>(`/translate/dictionaries/${dictionaryId}`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update dictionary');
|
||||
}
|
||||
},
|
||||
// #endregion updateDictionary
|
||||
|
||||
// #region deleteDictionary [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, delete]
|
||||
async deleteDictionary<T = unknown>(dictionaryId: string): Promise<T> {
|
||||
try {
|
||||
return await api.deleteApi<T>(`/translate/dictionaries/${dictionaryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete dictionary');
|
||||
}
|
||||
},
|
||||
// #endregion deleteDictionary
|
||||
|
||||
// #region fetchEntries [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, entries, list]
|
||||
async fetchEntries<T = unknown>(dictionaryId: string, options: PaginationOptions = {}): Promise<T> {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi<T>(
|
||||
`/translate/dictionaries/${dictionaryId}/entries${query ? `?${query}` : ''}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionary entries');
|
||||
}
|
||||
},
|
||||
// #endregion fetchEntries
|
||||
|
||||
// #region createEntry [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, entries, create]
|
||||
async createEntry<T = unknown>(dictionaryId: string, payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/dictionaries/${dictionaryId}/entries`, payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create dictionary entry');
|
||||
}
|
||||
},
|
||||
// #endregion createEntry
|
||||
|
||||
// #region updateEntry [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, entries, update]
|
||||
async updateEntry<T = unknown>(dictionaryId: string, entryId: string, payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.requestApi<T>(
|
||||
`/translate/dictionaries/${dictionaryId}/entries/${entryId}`,
|
||||
'PUT',
|
||||
payload
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update dictionary entry');
|
||||
}
|
||||
},
|
||||
// #endregion updateEntry
|
||||
|
||||
// #region deleteEntry [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, entries, delete]
|
||||
async deleteEntry<T = unknown>(dictionaryId: string, entryId: string): Promise<T> {
|
||||
try {
|
||||
return await api.deleteApi<T>(`/translate/dictionaries/${dictionaryId}/entries/${entryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete dictionary entry');
|
||||
}
|
||||
},
|
||||
// #endregion deleteEntry
|
||||
|
||||
// #region importEntries [C:2] [TYPE Function] [SEMANTICS translate, dictionaries, entries, import]
|
||||
async importEntries<T = unknown>(dictionaryId: string, payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/dictionaries/${dictionaryId}/import`, payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to import dictionary entries');
|
||||
}
|
||||
},
|
||||
// #endregion importEntries
|
||||
};
|
||||
// #endregion dictionaryApi
|
||||
// #endregion TranslateDictionariesApi
|
||||
@@ -1,58 +0,0 @@
|
||||
// #region TranslateJobsApi [C:2] [TYPE Module] [SEMANTICS translate, jobs, api, crud]
|
||||
// @BRIEF API client for translation job CRUD — list, create, update, delete, duplicate.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
function normalizeTranslateError(error, defaultMessage = 'Translation API error') {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR', retryable: true };
|
||||
}
|
||||
|
||||
export async function fetchJobs(options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.status) params.append('status', options.status);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/jobs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load translation jobs');
|
||||
}
|
||||
}
|
||||
|
||||
export async function createJob(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/jobs', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create translation job');
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateJob(jobId, payload) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/jobs/${jobId}`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update translation job');
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteJob(jobId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/jobs/${jobId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete translation job');
|
||||
}
|
||||
}
|
||||
|
||||
export async function duplicateJob(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/duplicate`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to duplicate translation job');
|
||||
}
|
||||
}
|
||||
// #endregion TranslateJobsApi
|
||||
107
frontend/src/lib/api/translate/jobs.ts
Normal file
107
frontend/src/lib/api/translate/jobs.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
// #region TranslateJobsApi [C:2] [TYPE Module] [SEMANTICS translate, jobs, api, crud]
|
||||
// @BRIEF API client for translation job CRUD — list, create, update, delete, duplicate.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @POST All functions return typed responses or throw normalized TranslateApiError.
|
||||
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface TranslateApiError {
|
||||
message: string;
|
||||
code: 'TRANSLATE_API_ERROR';
|
||||
retryable: boolean;
|
||||
}
|
||||
|
||||
function normalizeTranslateError(error: unknown, defaultMessage: string = 'Translation API error'): TranslateApiError {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR' as const, retryable: true };
|
||||
}
|
||||
|
||||
export interface JobQueryOptions {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
// #region fetchJobs [C:2] [TYPE Function] [SEMANTICS translate, jobs, list]
|
||||
// @BRIEF Fetch paginated list of translation jobs.
|
||||
// @PRE options may contain page/page_size/status filters.
|
||||
// @POST Returns paginated job list response.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchJobs<T = unknown>(options: JobQueryOptions = {}): Promise<T> {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.status) params.append('status', options.status);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi<T>(`/translate/jobs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load translation jobs');
|
||||
}
|
||||
}
|
||||
// #endregion fetchJobs
|
||||
|
||||
// #region createJob [C:2] [TYPE Function] [SEMANTICS translate, jobs, create]
|
||||
// @BRIEF Create a new translation job.
|
||||
// @PRE payload contains valid job configuration.
|
||||
// @POST Returns created job response.
|
||||
// @SIDE_EFFECT Creates a new translation job on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function createJob<T = unknown>(payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>('/translate/jobs', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create translation job');
|
||||
}
|
||||
}
|
||||
// #endregion createJob
|
||||
|
||||
// #region updateJob [C:2] [TYPE Function] [SEMANTICS translate, jobs, update]
|
||||
// @BRIEF Update an existing translation job.
|
||||
// @PRE jobId is a non-empty string. payload contains fields to update.
|
||||
// @POST Returns updated job response.
|
||||
// @SIDE_EFFECT Modifies the job on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function updateJob<T = unknown>(jobId: string, payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.requestApi<T>(`/translate/jobs/${jobId}`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update translation job');
|
||||
}
|
||||
}
|
||||
// #endregion updateJob
|
||||
|
||||
// #region deleteJob [C:2] [TYPE Function] [SEMANTICS translate, jobs, delete]
|
||||
// @BRIEF Delete a translation job.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns success response.
|
||||
// @SIDE_EFFECT Removes the job and associated runs from the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.deleteApi]
|
||||
export async function deleteJob<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.deleteApi<T>(`/translate/jobs/${jobId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete translation job');
|
||||
}
|
||||
}
|
||||
// #endregion deleteJob
|
||||
|
||||
// #region duplicateJob [C:2] [TYPE Function] [SEMANTICS translate, jobs, duplicate]
|
||||
// @BRIEF Duplicate a translation job (deep copy including configuration).
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns duplicated job response.
|
||||
// @SIDE_EFFECT Creates a new job as a copy of the specified one.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function duplicateJob<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/jobs/${jobId}/duplicate`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to duplicate translation job');
|
||||
}
|
||||
}
|
||||
// #endregion duplicateJob
|
||||
// #endregion TranslateJobsApi
|
||||
@@ -1,127 +0,0 @@
|
||||
// #region TranslateRunsApi [C:2] [TYPE Module] [SEMANTICS translate, runs, api, control, status]
|
||||
// @BRIEF API client for translation run control — trigger, status, history, records, retry, cancel, metrics.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
function normalizeTranslateError(error, defaultMessage = 'Translation API error') {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR', retryable: true };
|
||||
}
|
||||
|
||||
export async function triggerRun(jobId, full = false) {
|
||||
try {
|
||||
const query = full ? '?full_translation=true' : '';
|
||||
return await api.postApi(`/translate/jobs/${jobId}/run${query}`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to start translation run');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRunStatus(runId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/runs/${runId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run status');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRunHistory(jobId, options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/runs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run history');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRunRecords(runId, options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.status) params.append('status', options.status);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/runs/${runId}/records${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run records');
|
||||
}
|
||||
}
|
||||
|
||||
export async function retryFailedBatches(runId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/retry`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to retry batches');
|
||||
}
|
||||
}
|
||||
|
||||
export async function retryInsert(runId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/retry-insert`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to retry insert');
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelRun(runId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/cancel`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to cancel run');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRunBatches(runId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/runs/${runId}/batches`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run batches');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllRuns(options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.job_id) params.append('job_id', options.job_id);
|
||||
if (options.status) params.append('status', options.status);
|
||||
if (options.trigger_type) params.append('trigger_type', options.trigger_type);
|
||||
if (options.created_by) params.append('created_by', options.created_by);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/runs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch runs');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRunDetail(runId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/runs/${runId}/detail`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run detail');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchJobMetrics(jobId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/metrics`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch job metrics');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllMetrics() {
|
||||
try {
|
||||
return await api.fetchApi('/translate/metrics');
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch metrics');
|
||||
}
|
||||
}
|
||||
// #endregion TranslateRunsApi
|
||||
221
frontend/src/lib/api/translate/runs.ts
Normal file
221
frontend/src/lib/api/translate/runs.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
// #region TranslateRunsApi [C:2] [TYPE Module] [SEMANTICS translate, runs, api, control, status]
|
||||
// @BRIEF API client for translation run control — trigger, status, history, records, retry, cancel, metrics.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @POST All functions return typed responses or throw normalized TranslateApiError.
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import type { TranslateApiError } from './jobs';
|
||||
|
||||
function normalizeTranslateError(error: unknown, defaultMessage: string = 'Translation API error'): TranslateApiError {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR' as const, retryable: true };
|
||||
}
|
||||
|
||||
export interface RunQueryOptions {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
export interface RunHistoryQueryOptions extends RunQueryOptions {
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface AllRunsQueryOptions extends RunQueryOptions {
|
||||
job_id?: string;
|
||||
status?: string;
|
||||
trigger_type?: string;
|
||||
created_by?: string;
|
||||
}
|
||||
|
||||
// #region triggerRun [C:2] [TYPE Function] [SEMANTICS translate, runs, trigger]
|
||||
// @BRIEF Trigger a new translation run for a job.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns created run response with status.
|
||||
// @SIDE_EFFECT Starts async translation processing on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function triggerRun<T = unknown>(jobId: string, full: boolean = false): Promise<T> {
|
||||
try {
|
||||
const query = full ? '?full_translation=true' : '';
|
||||
return await api.postApi<T>(`/translate/jobs/${jobId}/run${query}`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to start translation run');
|
||||
}
|
||||
}
|
||||
// #endregion triggerRun
|
||||
|
||||
// #region fetchRunStatus [C:2] [TYPE Function] [SEMANTICS translate, runs, status]
|
||||
// @BRIEF Fetch the current status of a translation run.
|
||||
// @PRE runId is a non-empty string.
|
||||
// @POST Returns run status object with progress.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchRunStatus<T = unknown>(runId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/runs/${runId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run status');
|
||||
}
|
||||
}
|
||||
// #endregion fetchRunStatus
|
||||
|
||||
// #region fetchRunHistory [C:2] [TYPE Function] [SEMANTICS translate, runs, history]
|
||||
// @BRIEF Fetch paginated run history for a job.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns paginated run list.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchRunHistory<T = unknown>(jobId: string, options: RunHistoryQueryOptions = {}): Promise<T> {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi<T>(`/translate/jobs/${jobId}/runs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run history');
|
||||
}
|
||||
}
|
||||
// #endregion fetchRunHistory
|
||||
|
||||
// #region fetchRunRecords [C:2] [TYPE Function] [SEMANTICS translate, runs, records]
|
||||
// @BRIEF Fetch paginated records for a translation run.
|
||||
// @PRE runId is a non-empty string.
|
||||
// @POST Returns paginated record list with translations.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchRunRecords<T = unknown>(runId: string, options: RunQueryOptions & { status?: string } = {}): Promise<T> {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.status) params.append('status', options.status);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi<T>(`/translate/runs/${runId}/records${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run records');
|
||||
}
|
||||
}
|
||||
// #endregion fetchRunRecords
|
||||
|
||||
// #region retryFailedBatches [C:2] [TYPE Function] [SEMANTICS translate, runs, retry, batches]
|
||||
// @BRIEF Retry failed batches in a translation run.
|
||||
// @PRE runId is a non-empty string.
|
||||
// @POST Returns retry status.
|
||||
// @SIDE_EFFECT Re-queues failed batches for processing.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function retryFailedBatches<T = unknown>(runId: string): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/runs/${runId}/retry`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to retry batches');
|
||||
}
|
||||
}
|
||||
// #endregion retryFailedBatches
|
||||
|
||||
// #region retryInsert [C:2] [TYPE Function] [SEMANTICS translate, runs, retry, insert]
|
||||
// @BRIEF Retry failed insert operations for a run.
|
||||
// @PRE runId is a non-empty string.
|
||||
// @POST Returns retry status.
|
||||
// @SIDE_EFFECT Re-attempts failed DB insert operations.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function retryInsert<T = unknown>(runId: string): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/runs/${runId}/retry-insert`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to retry insert');
|
||||
}
|
||||
}
|
||||
// #endregion retryInsert
|
||||
|
||||
// #region cancelRun [C:2] [TYPE Function] [SEMANTICS translate, runs, cancel]
|
||||
// @BRIEF Cancel a running translation run.
|
||||
// @PRE runId is a non-empty string referring to a RUNNING run.
|
||||
// @POST Run is cancelled and no further batches will be processed.
|
||||
// @SIDE_EFFECT Interrupts processing and marks run as CANCELLED.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function cancelRun<T = unknown>(runId: string): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/runs/${runId}/cancel`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to cancel run');
|
||||
}
|
||||
}
|
||||
// #endregion cancelRun
|
||||
|
||||
// #region fetchRunBatches [C:2] [TYPE Function] [SEMANTICS translate, runs, batches]
|
||||
// @BRIEF Fetch batch-level breakdown for a run.
|
||||
// @PRE runId is a non-empty string.
|
||||
// @POST Returns batch list with status and record counts.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchRunBatches<T = unknown>(runId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/runs/${runId}/batches`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run batches');
|
||||
}
|
||||
}
|
||||
// #endregion fetchRunBatches
|
||||
|
||||
// #region fetchAllRuns [C:2] [TYPE Function] [SEMANTICS translate, runs, all, list]
|
||||
// @BRIEF Fetch all translation runs across jobs with optional filters.
|
||||
// @POST Returns paginated cross-job run list.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchAllRuns<T = unknown>(options: AllRunsQueryOptions = {}): Promise<T> {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.job_id) params.append('job_id', options.job_id);
|
||||
if (options.status) params.append('status', options.status);
|
||||
if (options.trigger_type) params.append('trigger_type', options.trigger_type);
|
||||
if (options.created_by) params.append('created_by', options.created_by);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi<T>(`/translate/runs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch runs');
|
||||
}
|
||||
}
|
||||
// #endregion fetchAllRuns
|
||||
|
||||
// #region fetchRunDetail [C:2] [TYPE Function] [SEMANTICS translate, runs, detail]
|
||||
// @BRIEF Fetch detailed run information with aggregated stats.
|
||||
// @PRE runId is a non-empty string.
|
||||
// @POST Returns run detail with progress and record breakdown.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchRunDetail<T = unknown>(runId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/runs/${runId}/detail`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run detail');
|
||||
}
|
||||
}
|
||||
// #endregion fetchRunDetail
|
||||
|
||||
// #region fetchJobMetrics [C:2] [TYPE Function] [SEMANTICS translate, metrics, job]
|
||||
// @BRIEF Fetch aggregate metrics for a translation job.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns metrics object with counts and stats.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchJobMetrics<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/jobs/${jobId}/metrics`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch job metrics');
|
||||
}
|
||||
}
|
||||
// #endregion fetchJobMetrics
|
||||
|
||||
// #region fetchAllMetrics [C:2] [TYPE Function] [SEMANTICS translate, metrics, all]
|
||||
// @BRIEF Fetch aggregate metrics across all translation jobs.
|
||||
// @POST Returns cross-job metrics.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchAllMetrics<T = unknown>(): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>('/translate/metrics');
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch metrics');
|
||||
}
|
||||
}
|
||||
// #endregion fetchAllMetrics
|
||||
// #endregion TranslateRunsApi
|
||||
@@ -1,61 +0,0 @@
|
||||
// #region TranslateSchedulesApi [C:2] [TYPE Module] [SEMANTICS translate, schedules, api, cron]
|
||||
// @BRIEF API client for translation job scheduling — CRUD, enable/disable, next executions.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
function normalizeTranslateError(error, defaultMessage = 'Translation API error') {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR', retryable: true };
|
||||
}
|
||||
|
||||
export async function fetchSchedule(jobId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/schedule`, { suppressToast: true });
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch schedule');
|
||||
}
|
||||
}
|
||||
|
||||
export async function setSchedule(jobId, payload) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/schedule`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to set schedule');
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSchedule(jobId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/jobs/${jobId}/schedule`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete schedule');
|
||||
}
|
||||
}
|
||||
|
||||
export async function enableSchedule(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/schedule/enable`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to enable schedule');
|
||||
}
|
||||
}
|
||||
|
||||
export async function disableSchedule(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/schedule/disable`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to disable schedule');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchNextExecutions(jobId, n = 3) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/schedule/next-executions?n=${n}`, { suppressToast: true });
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch next executions');
|
||||
}
|
||||
}
|
||||
// #endregion TranslateSchedulesApi
|
||||
105
frontend/src/lib/api/translate/schedules.ts
Normal file
105
frontend/src/lib/api/translate/schedules.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// #region TranslateSchedulesApi [C:2] [TYPE Module] [SEMANTICS translate, schedules, api, cron]
|
||||
// @BRIEF API client for translation job scheduling — CRUD, enable/disable, next executions.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @POST All functions return typed responses or throw normalized TranslateApiError.
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import type { TranslateApiError } from './jobs';
|
||||
|
||||
function normalizeTranslateError(error: unknown, defaultMessage: string = 'Translation API error'): TranslateApiError {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
return { message, code: 'TRANSLATE_API_ERROR' as const, retryable: true };
|
||||
}
|
||||
|
||||
// #region fetchSchedule [C:2] [TYPE Function] [SEMANTICS translate, schedules, fetch]
|
||||
// @BRIEF Fetch the cron schedule for a translation job.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns schedule configuration or null if no schedule exists.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchSchedule<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/jobs/${jobId}/schedule`, { suppressToast: true });
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch schedule');
|
||||
}
|
||||
}
|
||||
// #endregion fetchSchedule
|
||||
|
||||
// #region setSchedule [C:2] [TYPE Function] [SEMANTICS translate, schedules, set, update]
|
||||
// @BRIEF Set or update the cron schedule for a translation job.
|
||||
// @PRE jobId is a non-empty string. payload contains cron expression and enabled flag.
|
||||
// @POST Returns updated schedule configuration.
|
||||
// @SIDE_EFFECT Modifies the job's schedule on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.requestApi]
|
||||
export async function setSchedule<T = unknown>(jobId: string, payload: Record<string, unknown>): Promise<T> {
|
||||
try {
|
||||
return await api.requestApi<T>(`/translate/jobs/${jobId}/schedule`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to set schedule');
|
||||
}
|
||||
}
|
||||
// #endregion setSchedule
|
||||
|
||||
// #region deleteSchedule [C:2] [TYPE Function] [SEMANTICS translate, schedules, delete]
|
||||
// @BRIEF Delete the cron schedule for a translation job.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Schedule is removed. No more automated runs will be triggered.
|
||||
// @SIDE_EFFECT Removes the schedule from the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.deleteApi]
|
||||
export async function deleteSchedule<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.deleteApi<T>(`/translate/jobs/${jobId}/schedule`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete schedule');
|
||||
}
|
||||
}
|
||||
// #endregion deleteSchedule
|
||||
|
||||
// #region enableSchedule [C:2] [TYPE Function] [SEMANTICS translate, schedules, enable]
|
||||
// @BRIEF Enable an existing schedule for a translation job.
|
||||
// @PRE jobId is a non-empty string with an existing schedule.
|
||||
// @POST Schedule is activated and will trigger runs per cron.
|
||||
// @SIDE_EFFECT Enables the schedule on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function enableSchedule<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/jobs/${jobId}/schedule/enable`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to enable schedule');
|
||||
}
|
||||
}
|
||||
// #endregion enableSchedule
|
||||
|
||||
// #region disableSchedule [C:2] [TYPE Function] [SEMANTICS translate, schedules, disable]
|
||||
// @BRIEF Disable an existing schedule without deleting it.
|
||||
// @PRE jobId is a non-empty string with an existing schedule.
|
||||
// @POST Schedule is deactivated. No runs will be triggered until re-enabled.
|
||||
// @SIDE_EFFECT Disables the schedule on the backend.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
export async function disableSchedule<T = unknown>(jobId: string): Promise<T> {
|
||||
try {
|
||||
return await api.postApi<T>(`/translate/jobs/${jobId}/schedule/disable`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to disable schedule');
|
||||
}
|
||||
}
|
||||
// #endregion disableSchedule
|
||||
|
||||
// #region fetchNextExecutions [C:2] [TYPE Function] [SEMANTICS translate, schedules, next-executions]
|
||||
// @BRIEF Fetch upcoming execution times for a job's schedule.
|
||||
// @PRE jobId is a non-empty string.
|
||||
// @POST Returns array of upcoming datetime strings.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.fetchApi]
|
||||
export async function fetchNextExecutions<T = unknown>(jobId: string, n: number = 3): Promise<T> {
|
||||
try {
|
||||
return await api.fetchApi<T>(`/translate/jobs/${jobId}/schedule/next-executions?n=${n}`, { suppressToast: true });
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch next executions');
|
||||
}
|
||||
}
|
||||
// #endregion fetchNextExecutions
|
||||
// #endregion TranslateSchedulesApi
|
||||
@@ -1,49 +0,0 @@
|
||||
// #region TranslateTargetSchemaApi [C:1] [TYPE Module] [SEMANTICS translate, target-schema, api, validation]
|
||||
// @BRIEF API client for target table schema validation.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
/**
|
||||
* Check target table schema — validate expected vs actual columns for translation inserts.
|
||||
* @param {{
|
||||
* environment_id: string,
|
||||
* target_database_id: string,
|
||||
* target_schema: string,
|
||||
* target_table: string,
|
||||
* target_key_cols: string[],
|
||||
* target_column: string|null,
|
||||
* translation_column: string|null,
|
||||
* target_language_column: string|null,
|
||||
* target_source_column: string|null,
|
||||
* target_source_language_column: string|null,
|
||||
* }} payload
|
||||
* @returns {Promise<{
|
||||
* table_exists: boolean,
|
||||
* error: string|null,
|
||||
* expected_columns: Array<{name: string, data_type: string|null, is_nullable: boolean}>,
|
||||
* actual_columns: Array<{name: string, data_type: string|null, is_nullable: boolean}>,
|
||||
* missing_columns: Array<{name: string, data_type: string|null, is_nullable: boolean}>,
|
||||
* extra_columns: Array<{name: string, data_type: string|null, is_nullable: boolean}>,
|
||||
* all_present: boolean,
|
||||
* }>}
|
||||
*/
|
||||
export async function checkTargetTableSchema(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/check-target-schema', payload);
|
||||
} catch (error) {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
'Failed to check target table schema';
|
||||
return {
|
||||
table_exists: false,
|
||||
error: message,
|
||||
expected_columns: [],
|
||||
actual_columns: [],
|
||||
missing_columns: [],
|
||||
extra_columns: [],
|
||||
all_present: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
// #endregion TranslateTargetSchemaApi
|
||||
68
frontend/src/lib/api/translate/target-schema.ts
Normal file
68
frontend/src/lib/api/translate/target-schema.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// #region TranslateTargetSchemaApi [C:2] [TYPE Module] [SEMANTICS translate, target-schema, api, validation]
|
||||
// @BRIEF API client for target table schema validation — checks expected vs actual columns for translation inserts.
|
||||
// @LAYER API
|
||||
// @RELATION DEPENDS_ON -> [ApiModule]
|
||||
// @POST Returns schema validation result with all_present flag, or a graceful error shape if the API call fails.
|
||||
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface TargetSchemaCheckPayload {
|
||||
environment_id: string;
|
||||
target_database_id: string;
|
||||
target_schema: string;
|
||||
target_table: string;
|
||||
target_key_cols: string[];
|
||||
target_column: string | null;
|
||||
translation_column: string | null;
|
||||
target_language_column: string | null;
|
||||
target_source_column: string | null;
|
||||
target_source_language_column: string | null;
|
||||
}
|
||||
|
||||
export interface ColumnInfo {
|
||||
name: string;
|
||||
data_type: string | null;
|
||||
is_nullable: boolean;
|
||||
}
|
||||
|
||||
export interface TargetSchemaCheckResponse {
|
||||
table_exists: boolean;
|
||||
error: string | null;
|
||||
expected_columns: ColumnInfo[];
|
||||
actual_columns: ColumnInfo[];
|
||||
missing_columns: ColumnInfo[];
|
||||
extra_columns: ColumnInfo[];
|
||||
all_present: boolean;
|
||||
}
|
||||
|
||||
// #region checkTargetTableSchema [C:2] [TYPE Function] [SEMANTICS translate, target-schema, check, validate]
|
||||
// @BRIEF Check target table schema — validate expected vs actual columns for translation inserts.
|
||||
// @PRE payload contains all required target table configuration fields.
|
||||
// @POST Returns schema validation result with all_present flag.
|
||||
// On API failure, returns a graceful error shape (table_exists=false, error=message).
|
||||
// @SIDE_EFFECT Sends POST request to validate target schema.
|
||||
// @RELATION DEPENDS_ON -> [ApiModule.postApi]
|
||||
// @RATIONALE On API failure, returns a graceful error object instead of throwing,
|
||||
// because the schema check is advisory — the caller should still allow the
|
||||
// user to proceed with a warning instead of blocking the flow entirely.
|
||||
export async function checkTargetTableSchema(payload: TargetSchemaCheckPayload): Promise<TargetSchemaCheckResponse> {
|
||||
try {
|
||||
return await api.postApi<TargetSchemaCheckResponse>('/translate/check-target-schema', payload);
|
||||
} catch (error) {
|
||||
const message =
|
||||
(error && typeof (error as Error).message === 'string' && (error as Error).message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
'Failed to check target table schema';
|
||||
return {
|
||||
table_exists: false,
|
||||
error: message,
|
||||
expected_columns: [],
|
||||
actual_columns: [],
|
||||
missing_columns: [],
|
||||
extra_columns: [],
|
||||
all_present: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
// #endregion checkTargetTableSchema
|
||||
// #endregion TranslateTargetSchemaApi
|
||||
Reference in New Issue
Block a user