chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
filterAcyclicEdges as filterAcyclicWorkflowEdges,
|
||||
isWorkflowBlockAncestorLocked,
|
||||
isWorkflowBlockProtected,
|
||||
wouldCreateCycle as wouldCreateWorkflowEdgeCycle,
|
||||
} from '@sim/workflow-types/workflow'
|
||||
import type { Edge } from 'reactflow'
|
||||
import type { BlockState, Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
|
||||
const DEFAULT_LOOP_ITERATIONS = 5
|
||||
const DEFAULT_PARALLEL_BATCH_SIZE = 20
|
||||
const MAX_PARALLEL_BATCH_SIZE = 20
|
||||
|
||||
export function clampParallelBatchSize(batchSize: unknown): number {
|
||||
const parsed = typeof batchSize === 'number' ? batchSize : Number.parseInt(String(batchSize), 10)
|
||||
if (Number.isNaN(parsed)) {
|
||||
return DEFAULT_PARALLEL_BATCH_SIZE
|
||||
}
|
||||
return Math.max(1, Math.min(MAX_PARALLEL_BATCH_SIZE, parsed))
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if adding an edge would create a cycle in the graph.
|
||||
* Delegates to the shared implementation in `@sim/workflow-types` so the
|
||||
* client store, the collaborative queueing layer, and the realtime
|
||||
* persistence layer all agree on the same cyclic edges.
|
||||
*/
|
||||
export function wouldCreateCycle(edges: Edge[], sourceId: string, targetId: string): boolean {
|
||||
return wouldCreateWorkflowEdgeCycle(edges, sourceId, targetId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a batch of candidate edges down to the ones that do not create a
|
||||
* cycle against `currentEdges`, evaluated incrementally within the batch.
|
||||
*/
|
||||
export function filterAcyclicEdges(edgesToAdd: Edge[], currentEdges: Edge[]): Edge[] {
|
||||
return filterAcyclicWorkflowEdges(edgesToAdd, currentEdges)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert UI loop block to executor Loop format
|
||||
*
|
||||
* @param loopBlockId - ID of the loop block to convert
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns Loop object for execution engine or undefined if not a valid loop
|
||||
*/
|
||||
export function convertLoopBlockToLoop(
|
||||
loopBlockId: string,
|
||||
blocks: Record<string, BlockState>
|
||||
): Loop | undefined {
|
||||
const loopBlock = blocks[loopBlockId]
|
||||
if (!loopBlock || loopBlock.type !== 'loop') return undefined
|
||||
|
||||
const loopType = loopBlock.data?.loopType || 'for'
|
||||
|
||||
const loop: Loop = {
|
||||
id: loopBlockId,
|
||||
nodes: findChildNodes(loopBlockId, blocks),
|
||||
iterations: loopBlock.data?.count || DEFAULT_LOOP_ITERATIONS,
|
||||
loopType,
|
||||
enabled: loopBlock.enabled,
|
||||
}
|
||||
|
||||
loop.forEachItems = loopBlock.data?.collection || ''
|
||||
loop.whileCondition = loopBlock.data?.whileCondition || ''
|
||||
loop.doWhileCondition = loopBlock.data?.doWhileCondition || ''
|
||||
|
||||
return loop
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert UI parallel block to executor Parallel format
|
||||
*
|
||||
* @param parallelBlockId - ID of the parallel block to convert
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns Parallel object for execution engine or undefined if not a valid parallel block
|
||||
*/
|
||||
export function convertParallelBlockToParallel(
|
||||
parallelBlockId: string,
|
||||
blocks: Record<string, BlockState>
|
||||
): Parallel | undefined {
|
||||
const parallelBlock = blocks[parallelBlockId]
|
||||
if (!parallelBlock || parallelBlock.type !== 'parallel') return undefined
|
||||
|
||||
const parallelType = parallelBlock.data?.parallelType || 'count'
|
||||
|
||||
const validParallelTypes = ['collection', 'count'] as const
|
||||
const validatedParallelType = validParallelTypes.includes(parallelType as any)
|
||||
? parallelType
|
||||
: 'collection'
|
||||
|
||||
const distribution =
|
||||
validatedParallelType === 'collection' ? parallelBlock.data?.collection || '' : undefined
|
||||
|
||||
const count = parallelBlock.data?.count || 5
|
||||
const batchSize = clampParallelBatchSize(parallelBlock.data?.batchSize)
|
||||
|
||||
return {
|
||||
id: parallelBlockId,
|
||||
nodes: findChildNodes(parallelBlockId, blocks),
|
||||
distribution,
|
||||
count,
|
||||
parallelType: validatedParallelType,
|
||||
batchSize,
|
||||
enabled: parallelBlock.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all nodes that are children of this container (loop or parallel)
|
||||
*
|
||||
* @param containerId - ID of the container to find children for
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns Array of node IDs that are direct children of this container
|
||||
*/
|
||||
export function findChildNodes(containerId: string, blocks: Record<string, BlockState>): string[] {
|
||||
return Object.values(blocks)
|
||||
.filter((block) => block.data?.parentId === containerId)
|
||||
.map((block) => block.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all descendant nodes, including children, grandchildren, etc.
|
||||
*
|
||||
* @param containerId - ID of the container to find descendants for
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns Array of node IDs that are descendants of this container
|
||||
*/
|
||||
export function findAllDescendantNodes(
|
||||
containerId: string,
|
||||
blocks: Record<string, BlockState>
|
||||
): string[] {
|
||||
const descendants: string[] = []
|
||||
const visited = new Set<string>()
|
||||
const stack = [containerId]
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop()!
|
||||
if (visited.has(current)) continue
|
||||
visited.add(current)
|
||||
for (const block of Object.values(blocks)) {
|
||||
if (block.data?.parentId === current) {
|
||||
descendants.push(block.id)
|
||||
stack.push(block.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return descendants
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any ancestor container of a block is locked.
|
||||
* Unlike {@link isBlockProtected}, this ignores the block's own locked state.
|
||||
*
|
||||
* @param blockId - The ID of the block to check
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns True if any ancestor is locked
|
||||
*/
|
||||
export function isAncestorProtected(blockId: string, blocks: Record<string, BlockState>): boolean {
|
||||
return isWorkflowBlockAncestorLocked(blockId, blocks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a block is protected from editing/deletion.
|
||||
* A block is protected if it is locked or if any ancestor container is locked.
|
||||
*
|
||||
* @param blockId - The ID of the block to check
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns True if the block is protected
|
||||
*/
|
||||
export function isBlockProtected(blockId: string, blocks: Record<string, BlockState>): boolean {
|
||||
return isWorkflowBlockProtected(blockId, blocks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complete collection of loops from the UI blocks
|
||||
*
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns Record of Loop objects for execution engine
|
||||
*/
|
||||
export function generateLoopBlocks(blocks: Record<string, BlockState>): Record<string, Loop> {
|
||||
const loops: Record<string, Loop> = {}
|
||||
|
||||
Object.entries(blocks)
|
||||
.filter(([_, block]) => block.type === 'loop')
|
||||
.forEach(([id, block]) => {
|
||||
const loop = convertLoopBlockToLoop(id, blocks)
|
||||
if (loop) {
|
||||
loops[id] = loop
|
||||
}
|
||||
})
|
||||
|
||||
return loops
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complete collection of parallel blocks from the UI blocks
|
||||
*
|
||||
* @param blocks - Record of all blocks in the workflow
|
||||
* @returns Record of Parallel objects for execution engine
|
||||
*/
|
||||
export function generateParallelBlocks(
|
||||
blocks: Record<string, BlockState>
|
||||
): Record<string, Parallel> {
|
||||
const parallels: Record<string, Parallel> = {}
|
||||
|
||||
Object.entries(blocks)
|
||||
.filter(([_, block]) => block.type === 'parallel')
|
||||
.forEach(([id, block]) => {
|
||||
const parallel = convertParallelBlockToParallel(id, blocks)
|
||||
if (parallel) {
|
||||
parallels[id] = parallel
|
||||
}
|
||||
})
|
||||
|
||||
return parallels
|
||||
}
|
||||
Reference in New Issue
Block a user