import { API_BASE_URL, apiClient, apiEndpointKeys } from "./api"; import type { CloudDescriptor, CloudProvider, CloudServiceDescriptor, CloudServiceStatus, CloudServiceType, CloudStatus, } from "@/types/cloud"; import type { AppConfigConfigurationProfile, AppConfigDeployment, AppConfigDeploymentStrategy, AppConfigEnvironment, AppConfigHostedConfigurationVersion, ChildCollection, ChildItem, CloudResource, CollectionPage, CosmosContainer, CosmosItem, CosmosQueryResult, CreateDatabaseSnapshotInput, CreateKubernetesFargateProfileInput, CreateKubernetesNodegroupInput, DatabaseSnapshot, KubernetesFargateProfile, KubernetesNodegroup, LogsInsightsQueryInput, LogsInsightsQueryResult, NoSqlItem, SqlCredentials, SqlDatabase, SqlEngine, SqlQueryResult, SqlTable, StorageObjectList, } from "@/types/resource"; import type { ServiceSchema } from "@/types/schema"; import { getAccountId } from "@/lib/accountStore"; type CloudPathParams = Record; const DATABASE_MUTATION_TIMEOUT_MS = 5 * 60_000; const SQL_DATA_TIMEOUT_MS = 45_000; /** Floci completes Insights queries instantly by default, but bounded server-side polling can take longer. */ const LOGS_QUERY_TIMEOUT_MS = 20_000; /** Cold starts pull a container image; measured at ~60s on a first invoke. */ const INVOKE_TIMEOUT_MS = 120_000; export async function listClouds( signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.list, { signal }, ); return res.data; } export async function listCloudServices( cloud: CloudProvider, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.services, requestOptions(cloud, "cloud-proxy", { signal }), { cloud }, ); return res.data; } export async function getCloudStatus( cloud: CloudProvider, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.status, requestOptions(cloud, "cloud-proxy", { signal }), { cloud }, ); return res.data; } export async function getCloudServiceStatus( cloud: CloudProvider, service: CloudServiceType, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.serviceStatus, requestOptions(cloud, service, { signal }), { cloud, service }, ); return res.data; } export async function getServiceSchema( cloud: CloudProvider, service: CloudServiceType, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.schema, requestOptions(cloud, service, { signal }), { cloud, service }, ); return res.data; } export async function listCloudResources( cloud: CloudProvider, service: CloudServiceType, search?: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.resources.list, requestOptions(cloud, service, { signal, params: { search } }), { cloud, service }, ); return res.data; } export async function getCloudResource( cloud: CloudProvider, service: CloudServiceType, id: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.resources.get, requestOptions(cloud, service, { signal }), { cloud, service, id }, ); return res.data; } export async function createCloudResource( cloud: CloudProvider, service: CloudServiceType, values: Record, signal?: AbortSignal, ): Promise { const timeout = (cloud === "azure" || cloud === "aws") && service === "database" ? DATABASE_MUTATION_TIMEOUT_MS : undefined; const res = await apiClient.call>( apiEndpointKeys.clouds.resources.create, requestOptions(cloud, service, { signal, body: values, timeout }), { cloud, service }, ); return res.data; } export async function updateCloudResource( cloud: CloudProvider, service: CloudServiceType, id: string, values: Record, signal?: AbortSignal, ): Promise { const timeout = (cloud === "azure" || cloud === "aws") && service === "database" ? DATABASE_MUTATION_TIMEOUT_MS : undefined; const res = await apiClient.call>( apiEndpointKeys.clouds.resources.update, requestOptions(cloud, service, { signal, body: values, timeout }), { cloud, service, id }, ); return res.data; } export async function deleteCloudResource( cloud: CloudProvider, service: CloudServiceType, id: string, signal?: AbortSignal, ): Promise { await apiClient.call( apiEndpointKeys.clouds.resources.delete, requestOptions(cloud, service, { signal }), { cloud, service, id }, ); } export async function clearEmailInbox( cloud: CloudProvider, signal?: AbortSignal, ): Promise { await apiClient.call( apiEndpointKeys.clouds.email.inbox.clear, requestOptions(cloud, "email", { signal }), { cloud }, ); } export interface ServerlessInvokeResult { statusCode: number; payload: string; functionError?: string; logResult?: string; executionDuration?: number; } export interface CreateApiGatewayLambdaRouteInput { path: string; method: string; stage: string; functionName: string; } export interface ApiGatewayLambdaRoute { resourceId: string; deploymentId: string; invokeUrl: string; } export async function createApiGatewayLambdaRoute( cloud: CloudProvider, apiId: string, input: CreateApiGatewayLambdaRouteInput, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.resources.createApiGatewayRoute, requestOptions(cloud, "apigateway", { signal, body: input }), { cloud, id: apiId }, ); return res.data; } export type KmsEncryptionAlgorithm = | "SYMMETRIC_DEFAULT" | "RSAES_OAEP_SHA_1" | "RSAES_OAEP_SHA_256"; export interface KmsEncryptRequest { plaintextBase64: string; encryptionAlgorithm: KmsEncryptionAlgorithm; encryptionContext?: Record; } export interface KmsEncryptResponse { ciphertextBlobBase64: string; keyId: string; encryptionAlgorithm: KmsEncryptionAlgorithm; } export interface KmsDecryptRequest { ciphertextBlobBase64: string; encryptionAlgorithm: KmsEncryptionAlgorithm; encryptionContext?: Record; } export interface KmsDecryptResponse { plaintextBase64: string; keyId: string; encryptionAlgorithm: KmsEncryptionAlgorithm; } export async function invokeCloudResource( cloud: CloudProvider, service: CloudServiceType, id: string, payload: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.resources.invoke, requestOptions(cloud, service, { signal, body: { payload }, // A cold invoke can exceed a minute while the runtime pulls and starts the // function container, so the 10s default would abort a request that is // working. The user-visible alternative is a timeout error for a function // that in fact ran. timeout: INVOKE_TIMEOUT_MS, }), { cloud, service, id }, ); return res.data; } export async function encryptKmsResource( cloud: CloudProvider, id: string, body: KmsEncryptRequest, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.resources.encrypt, requestOptions(cloud, "kms", { signal, body }), { cloud, id }, ); return res.data; } export async function decryptKmsResource( cloud: CloudProvider, id: string, body: KmsDecryptRequest, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.resources.decrypt, requestOptions(cloud, "kms", { signal, body }), { cloud, id }, ); return res.data; } export async function listStorageObjects( cloud: CloudProvider, resourceId: string, prefix?: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.storage.objects.list, requestOptions(cloud, "storage", { signal, params: { prefix } }), storagePathParams(cloud, resourceId), ); return res.data; } export async function uploadStorageObject( cloud: CloudProvider, resourceId: string, key: string, file: File | Blob, signal?: AbortSignal, ): Promise { await apiClient.call( apiEndpointKeys.clouds.storage.objects.upload, requestOptions(cloud, "storage", { signal, rawBody: file, params: { key }, headers: { "Content-Type": file.type || "application/octet-stream" }, }), storagePathParams(cloud, resourceId), ); } export function storageObjectDownloadUrl( cloud: CloudProvider, resourceId: string, key: string, ): string { const path = `/clouds/${encodeURIComponent( cloud, )}/services/storage/resources/${encodeURIComponent(resourceId)}/object`; // This URL is opened directly by the browser (anchor/img), so it bypasses the // request interceptor — pass the account as a query param the API also reads. return `${API_BASE_URL}${path}?key=${encodeURIComponent( key, )}&account=${encodeURIComponent(getAccountId())}`; } export async function deleteStorageObject( cloud: CloudProvider, resourceId: string, key: string, signal?: AbortSignal, ): Promise { await apiClient.call( apiEndpointKeys.clouds.storage.objects.delete, requestOptions(cloud, "storage", { signal, params: { key } }), storagePathParams(cloud, resourceId), ); } export async function copyStorageObject( cloud: CloudProvider, srcResourceId: string, srcKey: string, destKey: string, destResourceId?: string, signal?: AbortSignal, ): Promise { await apiClient.call>( apiEndpointKeys.clouds.storage.objects.copy, requestOptions(cloud, "storage", { signal, body: { srcKey, destKey, ...(destResourceId ? { destResourceId } : {}) }, }), storagePathParams(cloud, srcResourceId), ); } export async function listCosmosContainers( cloud: CloudProvider, databaseId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.nosql.cosmos.containers.list, requestOptions(cloud, "nosql", { signal }), databasePathParams(cloud, databaseId), ); return res.data; } export async function createCosmosContainer( cloud: CloudProvider, databaseId: string, values: Record, signal?: AbortSignal, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.nosql.cosmos.containers.create, requestOptions(cloud, "nosql", { signal, body: values }), databasePathParams(cloud, databaseId), ); return res.data; } export async function deleteCosmosContainer( cloud: CloudProvider, databaseId: string, containerId: string, signal?: AbortSignal, ): Promise { await apiClient.call( apiEndpointKeys.clouds.nosql.cosmos.containers.delete, requestOptions(cloud, "nosql", { signal }), { ...databasePathParams(cloud, databaseId), containerId }, ); } export async function listCosmosItems( cloud: CloudProvider, databaseId: string, containerId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.nosql.cosmos.items.list, requestOptions(cloud, "nosql", { signal }), { ...databasePathParams(cloud, databaseId), containerId }, ); return res.data; } export async function upsertCosmosItem( cloud: CloudProvider, databaseId: string, containerId: string, document: Record, signal?: AbortSignal, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.nosql.cosmos.items.upsert, requestOptions(cloud, "nosql", { signal, body: document }), { ...databasePathParams(cloud, databaseId), containerId }, ); return res.data; } export async function deleteCosmosItem( cloud: CloudProvider, databaseId: string, containerId: string, itemId: string, partitionKey?: string | null, signal?: AbortSignal, ): Promise { await apiClient.call( apiEndpointKeys.clouds.nosql.cosmos.items.delete, requestOptions(cloud, "nosql", { signal, params: partitionKey ? { partitionKey } : undefined, }), { ...databasePathParams(cloud, databaseId), containerId, itemId }, ); } export async function queryCosmosItems( cloud: CloudProvider, databaseId: string, containerId: string, query: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.nosql.cosmos.items.query, requestOptions(cloud, "nosql", { signal, body: { query } }), { ...databasePathParams(cloud, databaseId), containerId }, ); return res.data; } export async function listChildCollections( cloud: CloudProvider, service: CloudServiceType, resourceId: string, cursor?: string, signal?: AbortSignal, ): Promise> { const res = await apiClient.call>( apiEndpointKeys.clouds.childCollections.list, requestOptions(cloud, service, { signal, params: cursor ? { cursor } : undefined }), { cloud, service, id: resourceId }, ); return res.data; } export async function listCollectionItems( cloud: CloudProvider, service: CloudServiceType, resourceId: string, collectionId: string, cursor?: string, signal?: AbortSignal, ): Promise> { const res = await apiClient.call>( apiEndpointKeys.clouds.childCollections.items.list, requestOptions(cloud, service, { signal, params: cursor ? { cursor } : undefined }), { cloud, service, id: resourceId, cid: collectionId }, ); return res.data; } export async function listSqlDatabases( cloud: CloudProvider, serverId: string, engine: SqlEngine, credentials: SqlCredentials, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.database.sql.databases, requestOptions(cloud, "database", { signal, body: { ...credentials, engine }, timeout: SQL_DATA_TIMEOUT_MS, }), { cloud, id: serverId }, ); return res.data; } export async function listSqlTables( cloud: CloudProvider, serverId: string, engine: SqlEngine, database: string, credentials: SqlCredentials, signal?: AbortSignal, ): Promise { const res = await apiClient.call< SqlTable[], SqlCredentials & { database: string; engine: SqlEngine } >( apiEndpointKeys.clouds.database.sql.tables, requestOptions(cloud, "database", { signal, body: { ...credentials, database, engine }, timeout: SQL_DATA_TIMEOUT_MS, }), { cloud, id: serverId }, ); return res.data; } export async function querySql( cloud: CloudProvider, serverId: string, engine: SqlEngine, database: string, credentials: SqlCredentials, query: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call< SqlQueryResult, SqlCredentials & { database: string; engine: SqlEngine; query: string } >( apiEndpointKeys.clouds.database.sql.query, requestOptions(cloud, "database", { signal, body: { ...credentials, database, engine, query }, timeout: SQL_DATA_TIMEOUT_MS, }), { cloud, id: serverId }, ); return res.data; } export async function queryLogs( cloud: CloudProvider, logGroupName: string, input: LogsInsightsQueryInput, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.logs.query, requestOptions(cloud, "logs", { signal, body: input, timeout: LOGS_QUERY_TIMEOUT_MS }), { cloud, id: logGroupName }, ); return res.data; } export async function listDatabaseSnapshots( cloud: CloudProvider, instanceIdentifier?: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.database.snapshots.list, requestOptions(cloud, "database", { signal, params: instanceIdentifier ? { instanceIdentifier } : undefined, }), { cloud }, ); return res.data; } export async function createDatabaseSnapshot( cloud: CloudProvider, input: CreateDatabaseSnapshotInput, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.database.snapshots.create, requestOptions(cloud, "database", { signal, body: input }), { cloud }, ); return res.data; } export async function listDatabaseOrderableClasses( cloud: CloudProvider, engine?: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.database.orderableClasses.list, requestOptions(cloud, "database", { signal, params: engine ? { engine } : undefined, }), { cloud }, ); return res.data; } export async function listNoSqlItems( cloud: CloudProvider, resourceId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.nosql.items.list, requestOptions(cloud, "nosql", { signal }), { cloud, id: resourceId }, ); return res.data; } export async function putNoSqlItem( cloud: CloudProvider, resourceId: string, document: Record, signal?: AbortSignal, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.nosql.items.put, requestOptions(cloud, "nosql", { signal, body: document }), { cloud, id: resourceId }, ); return res.data; } export async function listKubernetesNodegroups( cloud: CloudProvider, clusterId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.k8s.nodegroups.list, requestOptions(cloud, "k8s", {signal}), {cloud, id: clusterId}, ) return res.data } export async function createKubernetesNodegroup( cloud: CloudProvider, clusterId: string, input: CreateKubernetesNodegroupInput, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.k8s.nodegroups.create, requestOptions(cloud, "k8s", {body: input}), {cloud, id: clusterId}, ) return res.data } export async function deleteKubernetesNodegroup( cloud: CloudProvider, clusterId: string, nodegroupId: string, ): Promise { await apiClient.call( apiEndpointKeys.clouds.k8s.nodegroups.delete, requestOptions(cloud, "k8s"), {cloud, id: clusterId, nodegroupId}, ) } export async function listKubernetesFargateProfiles( cloud: CloudProvider, clusterId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.k8s.fargateProfiles.list, requestOptions(cloud, "k8s", {signal}), {cloud, id: clusterId}, ) return res.data } export async function createKubernetesFargateProfile( cloud: CloudProvider, clusterId: string, input: CreateKubernetesFargateProfileInput, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.k8s.fargateProfiles.create, requestOptions(cloud, "k8s", {body: input}), {cloud, id: clusterId}, ) return res.data } export async function deleteKubernetesFargateProfile( cloud: CloudProvider, clusterId: string, profileId: string, ): Promise { await apiClient.call( apiEndpointKeys.clouds.k8s.fargateProfiles.delete, requestOptions(cloud, "k8s"), {cloud, id: clusterId, profileId}, ) } export async function listAppConfigEnvironments( cloud: CloudProvider, applicationId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.configuration.environments.list, requestOptions(cloud, "configuration", {signal}), {cloud, id: applicationId}, ); return res.data; } export async function createAppConfigEnvironment( cloud: CloudProvider, applicationId: string, values: Record, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.configuration.environments.create, requestOptions(cloud, "configuration", {body: values}), {cloud, id: applicationId}, ); return res.data; } export async function deleteAppConfigEnvironment( cloud: CloudProvider, applicationId: string, environmentId: string, ): Promise { await apiClient.call( apiEndpointKeys.clouds.configuration.environments.delete, requestOptions(cloud, "configuration"), {cloud, id: applicationId, environmentId}, ); } export async function listAppConfigConfigurationProfiles( cloud: CloudProvider, applicationId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.configuration.configurationProfiles.list, requestOptions(cloud, "configuration", {signal}), {cloud, id: applicationId}, ); return res.data; } export async function createAppConfigConfigurationProfile( cloud: CloudProvider, applicationId: string, values: Record, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.configuration.configurationProfiles.create, requestOptions(cloud, "configuration", {body: values}), {cloud, id: applicationId}, ); return res.data; } export async function deleteAppConfigConfigurationProfile( cloud: CloudProvider, applicationId: string, profileId: string, ): Promise { await apiClient.call( apiEndpointKeys.clouds.configuration.configurationProfiles.delete, requestOptions(cloud, "configuration"), {cloud, id: applicationId, profileId}, ); } export async function listAppConfigHostedConfigurationVersions( cloud: CloudProvider, applicationId: string, profileId: string, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.list, requestOptions(cloud, "configuration", {signal}), {cloud, id: applicationId, profileId}, ); return res.data; } export async function getAppConfigHostedConfigurationVersion( cloud: CloudProvider, applicationId: string, profileId: string, versionNumber: number, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.get, requestOptions(cloud, "configuration", {signal}), {cloud, id: applicationId, profileId, versionNumber: String(versionNumber)}, ); return res.data; } export async function createAppConfigHostedConfigurationVersion( cloud: CloudProvider, applicationId: string, profileId: string, values: Record, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.create, requestOptions(cloud, "configuration", {body: values}), {cloud, id: applicationId, profileId}, ); return res.data; } export async function deleteAppConfigHostedConfigurationVersion( cloud: CloudProvider, applicationId: string, profileId: string, versionNumber: number, ): Promise { await apiClient.call( apiEndpointKeys.clouds.configuration.configurationProfiles.hostedVersions.delete, requestOptions(cloud, "configuration"), {cloud, id: applicationId, profileId, versionNumber: String(versionNumber)}, ); } export async function listAppConfigDeploymentStrategies( cloud: CloudProvider, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.configuration.deploymentStrategies.list, requestOptions(cloud, "configuration", {signal}), {cloud}, ); return res.data; } export async function createAppConfigDeploymentStrategy( cloud: CloudProvider, values: Record, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.configuration.deploymentStrategies.create, requestOptions(cloud, "configuration", {body: values}), {cloud}, ); return res.data; } export async function deleteAppConfigDeploymentStrategy( cloud: CloudProvider, strategyId: string, ): Promise { await apiClient.call( apiEndpointKeys.clouds.configuration.deploymentStrategies.delete, requestOptions(cloud, "configuration"), {cloud, strategyId}, ); } export async function startAppConfigDeployment( cloud: CloudProvider, applicationId: string, environmentId: string, values: Record, ): Promise { const res = await apiClient.call>( apiEndpointKeys.clouds.configuration.environments.deployments.start, requestOptions(cloud, "configuration", {body: values}), {cloud, id: applicationId, environmentId}, ); return res.data; } export async function getAppConfigDeployment( cloud: CloudProvider, applicationId: string, environmentId: string, deploymentNumber: number, signal?: AbortSignal, ): Promise { const res = await apiClient.call( apiEndpointKeys.clouds.configuration.environments.deployments.get, requestOptions(cloud, "configuration", {signal}), {cloud, id: applicationId, environmentId, deploymentNumber: String(deploymentNumber)}, ); return res.data; } function requestOptions( cloud: CloudProvider, service: string, options: { signal?: AbortSignal; params?: Record; body?: TBody; rawBody?: BodyInit; headers?: HeadersInit; /** Overrides the client default; needed for calls that can legitimately run long. */ timeout?: number; } = {}, ) { return { ...options, telemetry: { provider: cloud, service }, }; } function storagePathParams( cloud: CloudProvider, resourceId: string, ): CloudPathParams { return { cloud, id: resourceId }; } function databasePathParams( cloud: CloudProvider, databaseId: string, ): CloudPathParams { return { cloud, id: databaseId }; }