Files
simstudioai--sim/apps/sim/executor/variables/resolvers/reference.ts
T
wehub-resource-sync d25d482dc2
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
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

202 lines
6.1 KiB
TypeScript

import {
materializeLargeValueRefSync,
materializeLargeValueRefSyncOrThrow,
} from '@/lib/execution/payloads/cache'
import {
isLargeArrayManifest,
type LargeArrayManifest,
} from '@/lib/execution/payloads/large-array-manifest-metadata'
import { assertNoLargeValueRefs, isLargeValueRef } from '@/lib/execution/payloads/large-value-ref'
import type { ExecutionState, LoopScope } from '@/executor/execution/state'
import type { ExecutionContext } from '@/executor/types'
export interface PathNavigationExecutionContext {
workflowId: string
workspaceId?: string
executionId?: string
largeValueExecutionIds?: string[]
largeValueKeys?: string[]
fileKeys?: string[]
allowLargeValueWorkflowScope?: boolean
userId?: string
metadata?: { requestId?: string }
base64MaxBytes?: number
}
export interface PathNavigationContext {
executionContext: PathNavigationExecutionContext
allowLargeValueRefs?: boolean
}
export interface ResolutionContext {
executionContext: ExecutionContext
executionState: ExecutionState
currentNodeId: string
loopScope?: LoopScope
allowLargeValueRefs?: boolean
}
export interface Resolver {
canResolve(reference: string): boolean
resolve(reference: string, context: ResolutionContext): any
resolveAsync?(reference: string, context: ResolutionContext): Promise<any>
}
export type AsyncPathNavigator = (
obj: any,
path: string[],
context: PathNavigationContext
) => Promise<any>
/**
* Sentinel value indicating a reference was resolved to a known block
* that produced no output (e.g., the block exists in the workflow but
* didn't execute on this path). Distinct from `undefined`, which means
* the reference couldn't be matched to any block at all.
*/
export const RESOLVED_EMPTY = Symbol('RESOLVED_EMPTY')
export function splitLeadingBracketPath(part: string): { property: string; pathParts: string[] } {
const bracketMatch = part.match(/^([^[]+)((?:\[\d+\])+)$/)
if (!bracketMatch) {
return { property: part, pathParts: [] }
}
const indices = bracketMatch[2].match(/\[(\d+)\]/g) ?? []
return {
property: bracketMatch[1],
pathParts: indices.map((indexMatch) => indexMatch.slice(1, -1)),
}
}
function readManifestIndexSync(
manifest: LargeArrayManifest,
index: number,
executionContext?: ExecutionContext
): unknown {
if (!Number.isInteger(index) || index < 0 || index >= manifest.totalCount) {
return undefined
}
let offset = 0
for (const chunk of manifest.chunks) {
const nextOffset = offset + chunk.count
if (index < nextOffset) {
const materialized = materializeLargeValueRefSync(chunk.ref, executionContext)
if (materialized === undefined) {
return undefined
}
if (!Array.isArray(materialized)) {
throw new Error('Large array manifest chunk must materialize to an array.')
}
if (materialized.length !== chunk.count) {
throw new Error('Large array manifest chunk count does not match materialized data.')
}
return materialized[index - offset]
}
offset = nextOffset
}
return undefined
}
function navigateManifestMetadataOrIndexSync(
manifest: LargeArrayManifest,
part: string,
executionContext?: ExecutionContext
): unknown {
if (part === 'length' || part === 'totalCount') {
return manifest.totalCount
}
if (part === 'chunkCount' || part === 'byteSize' || part === 'preview') {
return manifest[part]
}
if (/^\d+$/.test(part)) {
return readManifestIndexSync(manifest, Number.parseInt(part, 10), executionContext)
}
return undefined
}
/**
* Navigate through nested object properties using a path array.
* Supports dot notation and array indices.
*
* @example
* navigatePath({a: {b: {c: 1}}}, ['a', 'b', 'c']) => 1
* navigatePath({items: [{name: 'test'}]}, ['items', '0', 'name']) => 'test'
*/
export function navigatePath(
obj: any,
path: string[],
options: { allowLargeValueRefs?: boolean; executionContext?: ExecutionContext } = {}
): any {
let current = obj
for (const part of path) {
if (isLargeValueRef(current)) {
current = materializeLargeValueRefSyncOrThrow(current, options.executionContext)
}
if (current === null || current === undefined) {
return undefined
}
if (isLargeArrayManifest(current)) {
current = navigateManifestMetadataOrIndexSync(current, part, options.executionContext)
continue
}
const arrayMatch = part.match(/^([^[]+)(\[.+)$/)
if (arrayMatch) {
const [, prop, bracketsPart] = arrayMatch
current =
typeof current === 'object' && current !== null
? (current as Record<string, unknown>)[prop]
: undefined
if (isLargeValueRef(current)) {
current = materializeLargeValueRefSyncOrThrow(current, options.executionContext)
}
if (current === undefined || current === null) {
return undefined
}
const indices = bracketsPart.match(/\[(\d+)\]/g)
if (indices) {
for (const indexMatch of indices) {
if (current === null || current === undefined) {
return undefined
}
if (isLargeValueRef(current)) {
current = materializeLargeValueRefSyncOrThrow(current, options.executionContext)
}
if (isLargeArrayManifest(current)) {
current = navigateManifestMetadataOrIndexSync(
current,
indexMatch.slice(1, -1),
options.executionContext
)
continue
}
const idx = Number.parseInt(indexMatch.slice(1, -1), 10)
current = Array.isArray(current) ? current[idx] : undefined
}
}
} else if (/^\d+$/.test(part)) {
const index = Number.parseInt(part, 10)
current = isLargeArrayManifest(current)
? readManifestIndexSync(current, index, options.executionContext)
: Array.isArray(current)
? current[index]
: undefined
} else {
current =
typeof current === 'object' && current !== null
? (current as Record<string, unknown>)[part]
: undefined
}
}
if (!options.allowLargeValueRefs) {
assertNoLargeValueRefs(current)
}
return current
}