Files
simstudioai--sim/apps/sim/lib/execution/payloads/cache.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

174 lines
4.2 KiB
TypeScript

import {
getLargeValueMaterializationError,
isLargeValueRef,
type LargeValueRef,
} from '@/lib/execution/payloads/large-value-ref'
const FALLBACK_TTL_MS = 15 * 60 * 1000
const MAX_IN_MEMORY_BYTES = 256 * 1024 * 1024
interface LargeValueCacheScope {
workspaceId?: string
workflowId?: string
executionId?: string
largeValueExecutionIds?: string[]
largeValueKeys?: string[]
allowLargeValueWorkflowScope?: boolean
}
const inMemoryValues = new Map<
string,
{
value: unknown
size: number
expiresAt: number
scope?: LargeValueCacheScope
recoverable: boolean
}
>()
let inMemoryBytes = 0
export function clearLargeValueCacheForTests(): void {
inMemoryValues.clear()
inMemoryBytes = 0
}
function cleanupExpiredValues(now = Date.now()): void {
for (const [id, entry] of inMemoryValues.entries()) {
if (entry.expiresAt <= now) {
inMemoryValues.delete(id)
inMemoryBytes -= entry.size
}
}
}
export function cacheLargeValue(
id: string,
value: unknown,
size: number,
scope?: LargeValueCacheScope,
options: { recoverable?: boolean } = {}
): boolean {
if (size > MAX_IN_MEMORY_BYTES) {
return false
}
cleanupExpiredValues()
const existing = inMemoryValues.get(id)
if (existing) {
inMemoryValues.delete(id)
inMemoryBytes -= existing.size
}
while (inMemoryBytes + size > MAX_IN_MEMORY_BYTES && inMemoryValues.size > 0) {
const oldestRecoverableId = Array.from(inMemoryValues.entries()).find(
([, entry]) => entry.recoverable
)?.[0]
if (!oldestRecoverableId) break
const oldest = inMemoryValues.get(oldestRecoverableId)
inMemoryValues.delete(oldestRecoverableId)
inMemoryBytes -= oldest?.size ?? 0
}
if (inMemoryBytes + size > MAX_IN_MEMORY_BYTES) {
if (existing) {
inMemoryValues.set(id, existing)
inMemoryBytes += existing.size
}
return false
}
inMemoryValues.set(id, {
value,
size,
scope,
recoverable: options.recoverable ?? false,
expiresAt: Date.now() + FALLBACK_TTL_MS,
})
inMemoryBytes += size
return true
}
function scopeMatchesRef(
ref: LargeValueRef,
cachedScope: LargeValueCacheScope | undefined,
callerScope?: LargeValueCacheScope
): boolean {
if (!cachedScope?.executionId) {
return false
}
if (ref.executionId && ref.executionId !== cachedScope.executionId) {
return false
}
if (!callerScope) {
return Boolean(ref.key) && (!ref.executionId || ref.executionId === cachedScope.executionId)
}
const allowedExecutionIds = new Set([
callerScope.executionId,
...(callerScope.largeValueExecutionIds ?? []),
])
if (ref.key && callerScope.largeValueKeys?.includes(ref.key)) {
return true
}
const workflowScopeAllowed =
callerScope.allowLargeValueWorkflowScope &&
callerScope.workspaceId === cachedScope.workspaceId &&
callerScope.workflowId === cachedScope.workflowId
return allowedExecutionIds.has(cachedScope.executionId) || Boolean(workflowScopeAllowed)
}
export function materializeLargeValueRefSync(
ref: LargeValueRef,
callerScope?: LargeValueCacheScope
): unknown {
cleanupExpiredValues()
const cached = inMemoryValues.get(ref.id)
if (!cached || !scopeMatchesRef(ref, cached.scope, callerScope)) {
return undefined
}
return cached.value
}
export function materializeLargeValueRefSyncOrThrow(
ref: LargeValueRef,
callerScope?: LargeValueCacheScope
): unknown {
const materialized = materializeLargeValueRefSync(ref, callerScope)
if (materialized === undefined) {
throw getLargeValueMaterializationError(ref)
}
return materialized
}
export function materializeLargeValueRefsSync(
value: unknown,
seen = new WeakSet<object>()
): unknown {
if (isLargeValueRef(value)) {
return materializeLargeValueRefsSync(materializeLargeValueRefSyncOrThrow(value), seen)
}
if (!value || typeof value !== 'object') {
return value
}
if (seen.has(value)) {
return value
}
seen.add(value)
if (Array.isArray(value)) {
return value.map((item) => materializeLargeValueRefsSync(item, seen))
}
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [
key,
materializeLargeValueRefsSync(entryValue, seen),
])
)
}