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
170 lines
5.8 KiB
TypeScript
170 lines
5.8 KiB
TypeScript
import { createLogger } from '@sim/logger'
|
|
import { generateId } from '@sim/utils/id'
|
|
import { create } from 'zustand'
|
|
import { devtools } from 'zustand/middleware'
|
|
import { getBlock } from '@/blocks'
|
|
import type { SubBlockConfig } from '@/blocks/types'
|
|
import { populateTriggerFieldsFromConfig } from '@/hooks/use-trigger-config-aggregation'
|
|
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
|
import type { SubBlockStore, SubBlockValue } from '@/stores/workflows/subblock/types'
|
|
import { isTriggerValid } from '@/triggers'
|
|
|
|
const logger = createLogger('SubBlockStore')
|
|
|
|
/**
|
|
* Stable empty fallback for `state.workflowValues[workflowId]` selectors.
|
|
* Using a module-level constant avoids returning a fresh `{}` on every
|
|
* selector call, which would defeat Zustand's `Object.is` equality.
|
|
*/
|
|
export const EMPTY_SUBBLOCK_VALUES: Record<string, Record<string, SubBlockValue>> = {}
|
|
|
|
/**
|
|
* Stable empty fallback for a single block's sub-block values.
|
|
*/
|
|
export const EMPTY_BLOCK_SUBBLOCK_VALUES: Record<string, SubBlockValue> = {}
|
|
|
|
/**
|
|
* SubBlockState stores values for all subblocks in workflows
|
|
*
|
|
* Important implementation notes:
|
|
* 1. Values are stored per workflow, per block, per subblock
|
|
* 2. When workflows are synced to the database, the mergeSubblockState function
|
|
* in utils.ts combines the block structure with these values
|
|
* 3. If a subblock value exists here but not in the block structure
|
|
* (e.g., inputFormat in starter block), the merge function will include it
|
|
* in the synchronized state to ensure persistence
|
|
*/
|
|
|
|
export const useSubBlockStore = create<SubBlockStore>()(
|
|
devtools((set, get) => ({
|
|
workflowValues: {},
|
|
|
|
setValue: (blockId: string, subBlockId: string, value: any) => {
|
|
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
|
if (!activeWorkflowId) return
|
|
|
|
let validatedValue = value
|
|
if (Array.isArray(value)) {
|
|
const isTableData =
|
|
value.length > 0 &&
|
|
value.some((item) => item && typeof item === 'object' && 'cells' in item)
|
|
|
|
if (isTableData) {
|
|
logger.debug('Validating table data for subblock', { blockId, subBlockId })
|
|
validatedValue = value.map((row: any) => {
|
|
if (!row || typeof row !== 'object') {
|
|
logger.warn('Fixing malformed table row', { blockId, subBlockId, row })
|
|
return {
|
|
id: generateId(),
|
|
cells: { Key: '', Value: '' },
|
|
}
|
|
}
|
|
|
|
if (!row.id) {
|
|
row.id = generateId()
|
|
}
|
|
|
|
if (!row.cells || typeof row.cells !== 'object') {
|
|
logger.warn('Fixing malformed table row cells', { blockId, subBlockId, row })
|
|
row.cells = { Key: '', Value: '' }
|
|
}
|
|
|
|
return row
|
|
})
|
|
}
|
|
}
|
|
|
|
set((state) => ({
|
|
workflowValues: {
|
|
...state.workflowValues,
|
|
[activeWorkflowId]: {
|
|
...state.workflowValues[activeWorkflowId],
|
|
[blockId]: {
|
|
...state.workflowValues[activeWorkflowId]?.[blockId],
|
|
[subBlockId]: validatedValue,
|
|
},
|
|
},
|
|
},
|
|
}))
|
|
},
|
|
|
|
getValue: (blockId: string, subBlockId: string) => {
|
|
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
|
if (!activeWorkflowId) return null
|
|
|
|
return get().workflowValues[activeWorkflowId]?.[blockId]?.[subBlockId] ?? null
|
|
},
|
|
|
|
clear: () => {
|
|
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
|
if (!activeWorkflowId) return
|
|
|
|
set((state) => ({
|
|
workflowValues: {
|
|
...state.workflowValues,
|
|
[activeWorkflowId]: {},
|
|
},
|
|
}))
|
|
},
|
|
|
|
initializeFromWorkflow: (workflowId: string, blocks: Record<string, any>) => {
|
|
const values: Record<string, Record<string, any>> = {}
|
|
|
|
Object.entries(blocks).forEach(([blockId, block]) => {
|
|
values[blockId] = {}
|
|
Object.entries(block.subBlocks || {}).forEach(([subBlockId, subBlock]) => {
|
|
values[blockId][subBlockId] = (subBlock as SubBlockConfig).value
|
|
})
|
|
})
|
|
|
|
set((state) => ({
|
|
workflowValues: {
|
|
...state.workflowValues,
|
|
[workflowId]: values,
|
|
},
|
|
}))
|
|
|
|
Object.entries(blocks).forEach(([blockId, block]) => {
|
|
const blockConfig = getBlock(block.type)
|
|
if (!blockConfig) return
|
|
|
|
const isTriggerBlock = blockConfig.category === 'triggers' || block.triggerMode === true
|
|
if (!isTriggerBlock) return
|
|
|
|
let triggerId: string | undefined
|
|
if (blockConfig.category === 'triggers') {
|
|
triggerId = block.type
|
|
} else if (block.triggerMode === true && blockConfig.triggers?.enabled) {
|
|
const selectedTriggerIdValue = block.subBlocks?.selectedTriggerId?.value
|
|
const triggerIdValue = block.subBlocks?.triggerId?.value
|
|
triggerId =
|
|
(typeof selectedTriggerIdValue === 'string' && isTriggerValid(selectedTriggerIdValue)
|
|
? selectedTriggerIdValue
|
|
: undefined) ||
|
|
(typeof triggerIdValue === 'string' && isTriggerValid(triggerIdValue)
|
|
? triggerIdValue
|
|
: undefined) ||
|
|
blockConfig.triggers?.available?.[0]
|
|
}
|
|
|
|
if (!triggerId || !isTriggerValid(triggerId)) {
|
|
return
|
|
}
|
|
|
|
const triggerConfigSubBlock = block.subBlocks?.triggerConfig
|
|
if (triggerConfigSubBlock?.value && typeof triggerConfigSubBlock.value === 'object') {
|
|
populateTriggerFieldsFromConfig(blockId, triggerConfigSubBlock.value, triggerId)
|
|
}
|
|
})
|
|
},
|
|
setWorkflowValues: (workflowId: string, values: Record<string, Record<string, any>>) => {
|
|
set((state) => ({
|
|
workflowValues: {
|
|
...state.workflowValues,
|
|
[workflowId]: values,
|
|
},
|
|
}))
|
|
},
|
|
}))
|
|
)
|