1import * as retail from "@distilled.cloud/gcp/retail_v2";
2import * as Effect from "effect/Effect";
3import { waitForOperation as waitForGcpOperation } from "../Operation.ts";
4
5export const resourceNameFromOperation = (
6 operation: retail.GoogleLongrunningOperation,
7): string | undefined => {
8 const response = operation.response;
9 if (response && typeof response === "object" && "name" in response) {
10 const name = (response as { name?: unknown }).name;
11 if (typeof name === "string" && name.length > 0) return name;
12 }
13 const metadata = operation.metadata;
14 if (metadata && typeof metadata === "object") {
15 const record = metadata as { target?: unknown; name?: unknown };
16 if (typeof record.target === "string" && record.target.length > 0) {
17 return record.target;
18 }
19 if (typeof record.name === "string" && record.name.length > 0) {
20 return record.name;
21 }
22 }
23 return undefined;
24};
25
26const getOperation = (name: string) =>
27 name.includes("/catalogs/")
28 ? retail.getProjectsLocationsCatalogsOperations({ name })
29 : name.includes("/locations/")
30 ? retail.getProjectsLocationsOperations({ name })
31 : retail.getProjectsOperations({ name });
32
33/**
34 * Wait for a Retail operation (model creation takes several minutes).
35 * ALREADY_EXISTS (code 6) counts as success (create race); with
36 * `notFoundOk`, so does NOT_FOUND (code 5, delete race) and an operation
37 * that is already gone.
38 */
39export const waitForOperation = (
40 operation: retail.GoogleLongrunningOperation,
41 options?: { notFoundOk?: boolean },
42) =>
43 waitForGcpOperation(operation, getOperation, { budget: "30 minutes" }).pipe(
44 Effect.catchIf(
45 (error) =>
46 (error._tag === "GCP.OperationFailed" &&
47 (error.code === 6 ||
48 (options?.notFoundOk === true && error.code === 5))) ||
49 (options?.notFoundOk === true && error._tag === "NotFound"),
50 () => Effect.succeed(operation),
51 ),
52 );