1import * as translate from "@distilled.cloud/gcp/translate_v3";
2import type * as Duration from "effect/Duration";
3import * as Effect from "effect/Effect";
4import * as Predicate from "effect/Predicate";
5import {
6 OperationFailed,
7 waitForOperation as waitForGcpOperation,
8} from "../Operation.ts";
9
10/**
11 * Translate reports some failures (e.g. a model created from an empty
12 * dataset) as `done: true` with the error in `metadata.error` and no
13 * top-level `error`.
14 */
15const metadataFailure = (operation: translate.Operation) => {
16 const error = operation.metadata?.error;
17 if (!Predicate.isObject(error)) return undefined;
18 const code =
19 Predicate.hasProperty(error, "code") && Predicate.isNumber(error.code)
20 ? error.code
21 : undefined;
22 const message =
23 Predicate.hasProperty(error, "message") && Predicate.isString(error.message)
24 ? error.message
25 : "operation failed";
26 return new OperationFailed({
27 operation: operation.name ?? "",
28 code,
29 reason: undefined,
30 message,
31 });
32};
33
34/**
35 * Wait for a Translate long-running operation, then re-read it so the
36 * returned operation carries its `response`. Model create/delete and
37 * glossary create/delete are LROs; Adaptive MT datasets and glossary
38 * entries are synchronous. ALREADY_EXISTS (code 6) counts as success
39 * (create race); with `notFoundOk`, so does NOT_FOUND (code 5, delete race)
40 * and an operation that is already gone.
41 */
42export const waitForOperation = (
43 operation: translate.Operation,
44 options?: { notFoundOk?: boolean; budget?: Duration.Input },
45) =>
46 waitForGcpOperation(
47 operation,
48 (name) => translate.getProjectsLocationsOperations({ name }),
49 { budget: options?.budget ?? "10 minutes" },
50 ).pipe(
51 Effect.flatMap(() =>
52 operation.name
53 ? translate.getProjectsLocationsOperations({ name: operation.name })
54 : Effect.succeed(operation),
55 ),
56 Effect.flatMap((done) => {
57 const failed = metadataFailure(done);
58 return failed ? Effect.fail(failed) : Effect.succeed(done);
59 }),
60 Effect.catchIf(
61 (error) =>
62 (error._tag === "GCP.OperationFailed" &&
63 (error.code === 6 ||
64 (options?.notFoundOk === true && error.code === 5))) ||
65 (options?.notFoundOk === true && error._tag === "NotFound"),
66 () => Effect.succeed(operation),
67 ),
68 );