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

251 lines
7.1 KiB
TypeScript

import {
isLargeArrayManifest,
LARGE_ARRAY_MANIFEST_PREVIEW_MAX_BYTES,
LARGE_ARRAY_MANIFEST_VERSION,
type LargeArrayManifest,
type LargeArrayManifestChunk,
} from '@/lib/execution/payloads/large-array-manifest-metadata'
import {
assertInlineMaterializationSize,
MAX_INLINE_MATERIALIZATION_BYTES,
} from '@/lib/execution/payloads/materialization.server'
import type { LargeValueStoreContext } from '@/lib/execution/payloads/store'
import { materializeLargeValueRef, storeLargeValue } from '@/lib/execution/payloads/store'
export {
isLargeArrayManifest,
LARGE_ARRAY_MANIFEST_MARKER,
LARGE_ARRAY_MANIFEST_PREVIEW_MAX_BYTES,
LARGE_ARRAY_MANIFEST_VERSION,
type LargeArrayManifest,
type LargeArrayManifestChunk,
} from '@/lib/execution/payloads/large-array-manifest-metadata'
export const LARGE_ARRAY_MANIFEST_CHUNK_TARGET_BYTES = Math.floor(
MAX_INLINE_MATERIALIZATION_BYTES / 2
)
const LARGE_ARRAY_MANIFEST_JSON_SERIALIZATION_ERROR =
'Large array manifest chunks must be JSON-serializable.'
export interface LargeArrayManifestReadOptions extends LargeValueStoreContext {
maxBytes?: number
}
export interface LargeArrayManifestWriteOptions extends LargeValueStoreContext {
chunkTargetBytes?: number
}
function measureJson(value: unknown): { json: string; size: number } {
let json: string | undefined
try {
json = JSON.stringify(value)
} catch {
throw new Error(LARGE_ARRAY_MANIFEST_JSON_SERIALIZATION_ERROR)
}
if (json === undefined) {
throw new Error(LARGE_ARRAY_MANIFEST_JSON_SERIALIZATION_ERROR)
}
return { json, size: Buffer.byteLength(json, 'utf8') }
}
function assertArray(value: unknown): asserts value is unknown[] {
if (!Array.isArray(value)) {
throw new Error('Large array manifest chunks must materialize to arrays.')
}
}
function assertChunkCount(chunk: unknown[], expectedCount: number): void {
if (chunk.length !== expectedCount) {
throw new Error('Large array manifest chunk count does not match materialized data.')
}
}
function getPreview(items: unknown[]): unknown[] {
const preview: unknown[] = []
for (const item of items.slice(0, 3)) {
const candidate = [...preview, item]
try {
if (measureJson(candidate).size > LARGE_ARRAY_MANIFEST_PREVIEW_MAX_BYTES) {
break
}
} catch {
break
}
preview.push(item)
}
return preview
}
function measureArrayElementJsonSize(item: unknown): number {
const measured = measureJson([item])
return Math.max(0, measured.size - 2)
}
async function storeArrayChunk(
items: unknown[],
context: LargeArrayManifestWriteOptions
): Promise<LargeArrayManifestChunk> {
const measured = measureJson(items)
const ref = await storeLargeValue(items, measured.json, measured.size, {
...context,
requireDurable: true,
})
return { ref, count: items.length, byteSize: measured.size }
}
function chunkArrayItems(items: unknown[], targetBytes: number): unknown[][] {
const chunks: unknown[][] = []
let current: unknown[] = []
let currentBytes = 2
for (const item of items) {
const itemBytes = measureArrayElementJsonSize(item)
const separatorBytes = current.length > 0 ? 1 : 0
if (current.length > 0 && currentBytes + separatorBytes + itemBytes > targetBytes) {
chunks.push(current)
current = []
currentBytes = 2
}
current.push(item)
currentBytes += (current.length > 1 ? 1 : 0) + itemBytes
}
if (current.length > 0) {
chunks.push(current)
}
return chunks
}
async function storeArrayChunks(
items: unknown[],
context: LargeArrayManifestWriteOptions
): Promise<LargeArrayManifestChunk[]> {
const targetBytes = Math.max(
2,
Math.min(
context.chunkTargetBytes ?? LARGE_ARRAY_MANIFEST_CHUNK_TARGET_BYTES,
MAX_INLINE_MATERIALIZATION_BYTES
)
)
const chunks = chunkArrayItems(items, targetBytes)
const storedChunks: LargeArrayManifestChunk[] = []
for (const chunk of chunks) {
storedChunks.push(await storeArrayChunk(chunk, context))
}
return storedChunks
}
function assertLargeArrayManifest(value: LargeArrayManifest): void {
if (!isLargeArrayManifest(value)) {
throw new Error('Invalid large array manifest.')
}
}
export async function createLargeArrayManifest(
items: unknown[],
context: LargeArrayManifestWriteOptions
): Promise<LargeArrayManifest> {
if (items.length === 0) {
return {
__simLargeArrayManifest: true,
version: LARGE_ARRAY_MANIFEST_VERSION,
kind: 'array',
totalCount: 0,
chunkCount: 0,
byteSize: 0,
chunks: [],
preview: [],
}
}
const chunks = await storeArrayChunks(items, context)
const byteSize = chunks.reduce((sum, chunk) => sum + chunk.byteSize, 0)
return {
__simLargeArrayManifest: true,
version: LARGE_ARRAY_MANIFEST_VERSION,
kind: 'array',
totalCount: items.length,
chunkCount: chunks.length,
byteSize,
chunks,
preview: getPreview(items),
}
}
export async function appendLargeArrayManifest(
manifest: LargeArrayManifest,
items: unknown[],
context: LargeArrayManifestWriteOptions
): Promise<LargeArrayManifest> {
if (items.length === 0) {
return manifest
}
const chunks = await storeArrayChunks(items, context)
const byteSize = chunks.reduce((sum, chunk) => sum + chunk.byteSize, 0)
return {
...manifest,
totalCount: manifest.totalCount + items.length,
chunkCount: manifest.chunkCount + chunks.length,
byteSize: manifest.byteSize + byteSize,
chunks: [...manifest.chunks, ...chunks],
preview: manifest.preview.length > 0 ? manifest.preview : getPreview(items),
}
}
export async function readLargeArrayManifestSlice(
manifest: LargeArrayManifest,
start: number,
limit: number,
context: LargeArrayManifestReadOptions
): Promise<unknown[]> {
assertLargeArrayManifest(manifest)
const normalizedStart = Math.max(0, Math.floor(start))
const normalizedLimit = Math.max(0, Math.floor(limit))
if (normalizedLimit === 0 || normalizedStart >= manifest.totalCount) {
return []
}
const end = Math.min(manifest.totalCount, normalizedStart + normalizedLimit)
const results: unknown[] = []
let cursor = 0
for (const chunkEntry of manifest.chunks) {
const chunkStart = cursor
const chunkEnd = cursor + chunkEntry.count
if (chunkEnd <= normalizedStart || chunkStart >= end) {
cursor = chunkEnd
continue
}
const chunk = await materializeLargeValueRef(chunkEntry.ref, context)
if (chunk === undefined) {
throw new Error('Large array manifest chunk is unavailable.')
}
assertArray(chunk)
assertChunkCount(chunk, chunkEntry.count)
const from = Math.max(0, normalizedStart - chunkStart)
const to = Math.min(chunk.length, end - chunkStart)
results.push(...chunk.slice(from, to))
cursor = chunkEnd
if (cursor >= end) {
break
}
}
return results
}
export async function materializeLargeArrayManifest(
manifest: LargeArrayManifest,
context: LargeArrayManifestReadOptions
): Promise<unknown[]> {
assertLargeArrayManifest(manifest)
assertInlineMaterializationSize(manifest.byteSize, context.maxBytes)
return readLargeArrayManifestSlice(manifest, 0, manifest.totalCount, context)
}