chore: import upstream snapshot with attribution
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) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
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) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { generateId } from '@sim/utils/id'
|
||||
import JSON5 from 'json5'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { normalizeName } from '@/executor/constants'
|
||||
import { useOperationQueueStore } from '@/stores/operation-queue/store'
|
||||
import type { Variable, VariablesStore } from '@/stores/variables/types'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
|
||||
const logger = createLogger('VariablesStore')
|
||||
|
||||
function validateVariable(variable: Variable): string | undefined {
|
||||
try {
|
||||
switch (variable.type) {
|
||||
case 'number':
|
||||
if (Number.isNaN(Number(variable.value))) {
|
||||
return 'Not a valid number'
|
||||
}
|
||||
break
|
||||
case 'boolean':
|
||||
if (!/^(true|false)$/i.test(String(variable.value).trim())) {
|
||||
return 'Expected "true" or "false"'
|
||||
}
|
||||
break
|
||||
case 'object':
|
||||
try {
|
||||
const valueToEvaluate = String(variable.value).trim()
|
||||
|
||||
if (!valueToEvaluate.startsWith('{') || !valueToEvaluate.endsWith('}')) {
|
||||
return 'Not a valid object format'
|
||||
}
|
||||
|
||||
const parsed = JSON5.parse(valueToEvaluate)
|
||||
|
||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
return 'Not a valid object'
|
||||
}
|
||||
|
||||
return undefined
|
||||
} catch (e) {
|
||||
logger.error('Object parsing error:', e)
|
||||
return 'Invalid object syntax'
|
||||
}
|
||||
case 'array':
|
||||
try {
|
||||
const parsed = JSON5.parse(String(variable.value))
|
||||
if (!Array.isArray(parsed)) {
|
||||
return 'Not a valid array'
|
||||
}
|
||||
} catch {
|
||||
return 'Invalid array syntax'
|
||||
}
|
||||
break
|
||||
}
|
||||
return undefined
|
||||
} catch (e) {
|
||||
return getErrorMessage(e, 'Invalid format')
|
||||
}
|
||||
}
|
||||
|
||||
export const useVariablesStore = create<VariablesStore>()(
|
||||
devtools((set, get) => ({
|
||||
variables: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
isEditing: null,
|
||||
|
||||
addVariable: (variable, providedId?: string) => {
|
||||
const id = providedId || generateId()
|
||||
|
||||
const workflowVariables = get().getVariablesByWorkflowId(variable.workflowId)
|
||||
|
||||
if (!variable.name || /^variable\d+$/.test(variable.name)) {
|
||||
const existingNumbers = workflowVariables
|
||||
.map((v) => {
|
||||
const match = v.name.match(/^variable(\d+)$/)
|
||||
return match ? Number.parseInt(match[1]) : 0
|
||||
})
|
||||
.filter((n) => !Number.isNaN(n))
|
||||
|
||||
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1
|
||||
|
||||
variable.name = `variable${nextNumber}`
|
||||
}
|
||||
|
||||
let uniqueName = variable.name
|
||||
let nameIndex = 1
|
||||
|
||||
while (workflowVariables.some((v) => v.name === uniqueName)) {
|
||||
uniqueName = `${variable.name} (${nameIndex})`
|
||||
nameIndex++
|
||||
}
|
||||
|
||||
if (variable.type === 'string') {
|
||||
variable.type = 'plain'
|
||||
}
|
||||
|
||||
const newVariable: Variable = {
|
||||
id,
|
||||
workflowId: variable.workflowId,
|
||||
name: uniqueName,
|
||||
type: variable.type,
|
||||
value: variable.value || '',
|
||||
validationError: undefined,
|
||||
}
|
||||
|
||||
const validationError = validateVariable(newVariable)
|
||||
if (validationError) {
|
||||
newVariable.validationError = validationError
|
||||
}
|
||||
|
||||
set((state) => ({
|
||||
variables: {
|
||||
...state.variables,
|
||||
[id]: newVariable,
|
||||
},
|
||||
}))
|
||||
|
||||
return id
|
||||
},
|
||||
|
||||
updateVariable: (id, update) => {
|
||||
set((state) => {
|
||||
if (!state.variables[id]) return state
|
||||
|
||||
if (update.name !== undefined) {
|
||||
const oldVariable = state.variables[id]
|
||||
const oldVariableName = oldVariable.name
|
||||
const newName = update.name.trim()
|
||||
|
||||
if (!newName) {
|
||||
update = { ...update }
|
||||
update.name = undefined
|
||||
} else if (newName !== oldVariableName) {
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
const targetWorkflowId = oldVariable.workflowId
|
||||
|
||||
if (targetWorkflowId) {
|
||||
const workflowValues = subBlockStore.workflowValues[targetWorkflowId] || {}
|
||||
const updatedWorkflowValues = { ...workflowValues }
|
||||
const changedSubBlocks: Array<{ blockId: string; subBlockId: string; value: any }> =
|
||||
[]
|
||||
|
||||
const oldVarName = normalizeName(oldVariableName)
|
||||
const newVarName = normalizeName(newName)
|
||||
const regex = new RegExp(`<variable\\.${oldVarName}>`, 'gi')
|
||||
|
||||
const updateReferences = (value: any, pattern: RegExp, replacement: string): any => {
|
||||
if (typeof value === 'string') {
|
||||
return pattern.test(value) ? value.replace(pattern, replacement) : value
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => updateReferences(item, pattern, replacement))
|
||||
}
|
||||
|
||||
if (value !== null && typeof value === 'object') {
|
||||
const result = { ...value }
|
||||
for (const key in result) {
|
||||
result[key] = updateReferences(result[key], pattern, replacement)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
Object.entries(workflowValues).forEach(([blockId, blockValues]) => {
|
||||
Object.entries(blockValues as Record<string, any>).forEach(
|
||||
([subBlockId, value]) => {
|
||||
const updatedValue = updateReferences(value, regex, `<variable.${newVarName}>`)
|
||||
|
||||
if (JSON.stringify(updatedValue) !== JSON.stringify(value)) {
|
||||
if (!updatedWorkflowValues[blockId]) {
|
||||
updatedWorkflowValues[blockId] = { ...workflowValues[blockId] }
|
||||
}
|
||||
updatedWorkflowValues[blockId][subBlockId] = updatedValue
|
||||
changedSubBlocks.push({ blockId, subBlockId, value: updatedValue })
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// Update local state
|
||||
useSubBlockStore.setState({
|
||||
workflowValues: {
|
||||
...subBlockStore.workflowValues,
|
||||
[targetWorkflowId]: updatedWorkflowValues,
|
||||
},
|
||||
})
|
||||
|
||||
// Queue operations for persistence via socket
|
||||
const operationQueue = useOperationQueueStore.getState()
|
||||
|
||||
for (const { blockId, subBlockId, value } of changedSubBlocks) {
|
||||
operationQueue.addToQueue({
|
||||
id: generateId(),
|
||||
operation: {
|
||||
operation: 'subblock-update',
|
||||
target: 'subblock',
|
||||
payload: { blockId, subblockId: subBlockId, value },
|
||||
},
|
||||
workflowId: targetWorkflowId,
|
||||
userId: 'system',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (update.type === 'string') {
|
||||
update = { ...update, type: 'plain' }
|
||||
}
|
||||
|
||||
const updatedVariable: Variable = {
|
||||
...state.variables[id],
|
||||
...update,
|
||||
validationError: undefined,
|
||||
}
|
||||
|
||||
if (update.type || update.value !== undefined) {
|
||||
updatedVariable.validationError = validateVariable(updatedVariable)
|
||||
}
|
||||
|
||||
const updated = {
|
||||
...state.variables,
|
||||
[id]: updatedVariable,
|
||||
}
|
||||
|
||||
return { variables: updated }
|
||||
})
|
||||
},
|
||||
|
||||
deleteVariable: (id) => {
|
||||
set((state) => {
|
||||
if (!state.variables[id]) return state
|
||||
|
||||
const { [id]: _, ...rest } = state.variables
|
||||
|
||||
return { variables: rest }
|
||||
})
|
||||
},
|
||||
|
||||
getVariablesByWorkflowId: (workflowId) => {
|
||||
return Object.values(get().variables).filter((variable) => variable.workflowId === workflowId)
|
||||
},
|
||||
}))
|
||||
)
|
||||
Reference in New Issue
Block a user