d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import { db } from '@sim/db'
|
|
import {
|
|
workspace,
|
|
workspaceForkBlockMap,
|
|
workspaceForkDependentValue,
|
|
workspaceForkPromoteRun,
|
|
workspaceForkResourceMap,
|
|
} from '@sim/db/schema'
|
|
import { createLogger } from '@sim/logger'
|
|
import { and, eq } from 'drizzle-orm'
|
|
import {
|
|
acquireForkEdgeLock,
|
|
type ForkEdge,
|
|
setForkLockTimeout,
|
|
} from '@/ee/workspace-forking/lib/lineage/lineage'
|
|
|
|
const logger = createLogger('ForkUnlink')
|
|
|
|
export interface UnlinkForkResult {
|
|
/** False when the edge was already dissolved by a concurrent unlink (idempotent no-op). */
|
|
unlinked: boolean
|
|
}
|
|
|
|
/**
|
|
* Permanently dissolve a fork edge: null the child's `forkedFromWorkspaceId` (the
|
|
* edge's single source of truth) and purge the edge's fork state — resource map,
|
|
* block map, dependent values, and promote-run undo points. Both workspaces are
|
|
* left untouched; only the association and its metadata are removed.
|
|
*
|
|
* Runs in one transaction under the edge advisory lock, which every promote and
|
|
* rollback on the edge also holds, so an in-flight sync either finishes before the
|
|
* unlink or re-resolves the edge afterwards and fails with "not a direct fork edge".
|
|
* The edge is re-verified inside the lock; a concurrently-dissolved edge is an
|
|
* idempotent success rather than an error.
|
|
*/
|
|
export async function unlinkForkEdge(
|
|
edge: ForkEdge,
|
|
requestId?: string
|
|
): Promise<UnlinkForkResult> {
|
|
const { childWorkspaceId, parentWorkspaceId } = edge
|
|
|
|
const unlinked = await db.transaction(async (tx) => {
|
|
await setForkLockTimeout(tx)
|
|
await acquireForkEdgeLock(tx, childWorkspaceId)
|
|
|
|
const updated = await tx
|
|
.update(workspace)
|
|
.set({ forkedFromWorkspaceId: null, updatedAt: new Date() })
|
|
.where(
|
|
and(
|
|
eq(workspace.id, childWorkspaceId),
|
|
eq(workspace.forkedFromWorkspaceId, parentWorkspaceId)
|
|
)
|
|
)
|
|
.returning({ id: workspace.id })
|
|
if (updated.length === 0) return false
|
|
|
|
await tx
|
|
.delete(workspaceForkResourceMap)
|
|
.where(eq(workspaceForkResourceMap.childWorkspaceId, childWorkspaceId))
|
|
await tx
|
|
.delete(workspaceForkBlockMap)
|
|
.where(eq(workspaceForkBlockMap.childWorkspaceId, childWorkspaceId))
|
|
await tx
|
|
.delete(workspaceForkDependentValue)
|
|
.where(eq(workspaceForkDependentValue.childWorkspaceId, childWorkspaceId))
|
|
await tx
|
|
.delete(workspaceForkPromoteRun)
|
|
.where(eq(workspaceForkPromoteRun.childWorkspaceId, childWorkspaceId))
|
|
return true
|
|
})
|
|
|
|
logger.info(`[${requestId ?? 'unlink'}] Fork edge ${unlinked ? 'dissolved' : 'already gone'}`, {
|
|
childWorkspaceId,
|
|
parentWorkspaceId,
|
|
})
|
|
return { unlinked }
|
|
}
|