// TEMPORARY: remove after brett migration (SDK-1909). // // Adapts legacy `/user-applications` records into brett's `/applications` // classes at the query boundary, so demo orgs stay populated during the // transition. Pure functions — the mapping lives here, not in the app classes. // The manifest's workspaces become `activeDeployment.workspaces`. The endpoint // returns no interfaces, but every user app is a navigable app, so a synthetic // `app` interface is added to mark it reachable and carry dock placement. import type { Interface, InterfaceOfType } from "../applications/applications"; import type { BrettWorkspace } from "../applications/brett-applications"; import type { CoreApp } from "../applications/core-apps"; import type { Studio } from "../applications/studios"; import type { OrganizationId } from "../organizations"; import type { UserClientManifest, UserCoreApp, UserServerManifest, UserStudio, } from "./schema"; const USER_TIMESTAMP = new Date(0).toISOString(); type UserManifest = UserServerManifest | UserClientManifest; function toDockMetadata( manifest: { group?: string; priority?: number } | null | undefined, ): InterfaceOfType<"app">["metadata"] { if (manifest?.group === undefined && manifest?.priority === undefined) { return null; } return { group: manifest.group, priority: manifest.priority }; } /** * The `/user-applications` endpoint returns no interfaces, but every user app is * a navigable app — so synthesize the single `app` interface the workbench reads * to treat it as reachable (`hasAppView`) and to place it in the dock (its * `metadata`). The module the host loads is derived from the application id * (`${id}/App`), so the `moduleId` here is never fetched. */ function toAppInterface( applicationId: string, title: string, dockMetadata: InterfaceOfType<"app">["metadata"], ): Interface[] { return [ { id: `${applicationId}-app`, type: "app", name: applicationId, title, moduleId: `${applicationId}/App`, metadata: dockMetadata, }, ]; } function toWorkspaces( applicationId: string, manifest: UserManifest | null, ): BrettWorkspace[] | undefined { return manifest?.workspaces?.map((workspace) => { // A server manifest carries the descriptor directly; a client manifest only // signals a schema exists, so synthesize a stable id for it. let schemaDescriptorId: string | null = null; if ("schemaDescriptorId" in workspace && workspace.schemaDescriptorId) { schemaDescriptorId = workspace.schemaDescriptorId; } else if ("schema" in workspace && workspace.schema) { schemaDescriptorId = `${applicationId}-${workspace.name}`; } return { id: `${applicationId}-${workspace.name}`, name: workspace.name, title: workspace.title ?? null, subtitle: workspace.subtitle ?? null, projectId: workspace.projectId, dataset: workspace.dataset ?? "", schemaDescriptorId, basePath: workspace.basePath ?? null, icon: workspace.icon ?? null, }; }); } /** * Version lives in different slots across the legacy shape: the deployment for * internal studios, the client manifest's `studioVersion` for external ones, * the server manifest's `bundleVersion` otherwise. Resolve it into the single * `activeDeployment.version` slot the brett class reads. */ function toStudioVersion( legacy: UserStudio, manifest: UserManifest | null, ): string | null { if (legacy.activeDeployment?.version) return legacy.activeDeployment.version; if (manifest && "studioVersion" in manifest && manifest.studioVersion) { return manifest.studioVersion; } if (manifest && "bundleVersion" in manifest && manifest.bundleVersion) { return manifest.bundleVersion; } return null; } /** * Adapts a legacy studio into brett's `Studio`. Internal studios map their host * to `slug`, external ones to `externalUrl` — the brett `url` getter then yields * the same URL the legacy getter did. The most authoritative manifest * (deployment server manifest, then client manifest, then the deprecated * top-level one) feeds `activeDeployment.workspaces`; a missing title falls back * to the app host, since brett's `Studio` always carries one. */ /** * The qualified reference brett would compute: `/`. Legacy * `/user-applications` are never singletons (so the `sanity/` alias never * applies) and carry no server-side reference, so this adapter composes it * (removed with the whole file at SDK-1909). */ function applicationReference(app: { name: string; organizationId: string; }): string { return `${app.organizationId}/${app.name}`; } export function adaptUserStudio( legacy: UserStudio, organizationId: OrganizationId, ): Studio { const { id } = legacy; const manifest = legacy.activeDeployment?.manifest ?? legacy.manifestData?.value ?? legacy.manifest ?? null; // Identity mirrors brett's default: the slug (the app host, for internal apps) // or the id for slug-less external ones. const name = legacy.urlType === "internal" ? legacy.appHost : id; const common = { id, type: "studio" as const, title: legacy.title ?? legacy.appHost, name, reference: applicationReference({ name, organizationId }), isSingleton: false, visibility: legacy.dashboardStatus, organizationId, createdAt: legacy.createdAt, updatedAt: legacy.updatedAt, config: { studio: { projectId: legacy.projectId, autoUpdatingVersion: legacy.autoUpdatingVersion, }, }, activeDeployment: { id: legacy.activeDeployment?.id ?? `${id}-user`, applicationId: id, size: legacy.activeDeployment?.size ?? null, version: toStudioVersion(legacy, manifest), isAutoUpdating: legacy.activeDeployment?.isAutoUpdating ?? null, isActiveDeployment: true, deployedBy: legacy.activeDeployment?.deployedBy ?? null, createdAt: legacy.activeDeployment?.createdAt ?? USER_TIMESTAMP, updatedAt: legacy.activeDeployment?.updatedAt ?? USER_TIMESTAMP, interfaces: toAppInterface( id, legacy.title ?? legacy.appHost, toDockMetadata(manifest), ), workspaces: toWorkspaces(id, manifest), }, }; return legacy.urlType === "internal" ? { ...common, slug: legacy.appHost, externalUrl: null } : { ...common, slug: null, externalUrl: legacy.appHost }; } /** * Adapts a legacy core app into brett's `CoreApp`. The manifest's icon is * lifted onto the application record and its manifest rides the top-level * `manifest` slot; the synthetic `app` interface makes it navigable and carries * dock placement from the manifest — the same source a deployed brett app uses. */ export function adaptUserCoreApp(legacy: UserCoreApp): CoreApp { const { id } = legacy; // A legacy manifest carries no slug; use the app host (its slug for internal // apps). Inert for config matching, which only reads a local dev app's slug. const manifest = legacy.activeDeployment?.manifest ? { ...legacy.activeDeployment.manifest, slug: legacy.appHost } : null; // Identity mirrors brett's default: the slug (the app host, for internal apps) // or the id for slug-less external ones. const name = legacy.urlType === "internal" ? legacy.appHost : id; const common = { id, type: "coreApp" as const, title: legacy.title, name, reference: applicationReference({ name, organizationId: legacy.organizationId, }), icon: manifest?.icon ?? null, isSingleton: false, visibility: legacy.dashboardStatus, organizationId: legacy.organizationId, createdAt: legacy.createdAt, updatedAt: legacy.updatedAt, manifest, activeDeployment: { id: legacy.activeDeployment?.id ?? `${id}-user`, applicationId: id, size: legacy.activeDeployment?.size ?? null, version: legacy.activeDeployment?.version ?? null, isAutoUpdating: legacy.activeDeployment?.isAutoUpdating ?? null, isActiveDeployment: true, deployedBy: legacy.activeDeployment?.deployedBy ?? null, createdAt: legacy.activeDeployment?.createdAt ?? USER_TIMESTAMP, updatedAt: legacy.activeDeployment?.updatedAt ?? USER_TIMESTAMP, interfaces: toAppInterface(id, legacy.title, toDockMetadata(manifest)), }, }; return legacy.urlType === "internal" ? { ...common, slug: legacy.appHost, externalUrl: null } : { ...common, slug: null, externalUrl: legacy.appHost }; }