Files
WeHub Mirror 6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (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 / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
WeHub snapshot of cb28d14c6f2c081de7a0d8729a8c816c9adef67a
2026-08-10 11:17:50 +08:00

88 lines
3.7 KiB
TypeScript

/**
* Pure `select`-option helpers, with no database dependency.
*
* These live in their own leaf module rather than in `validation.ts` on
* purpose. `validation.ts` imports `@sim/db`, `drizzle-orm`, and `next/server`,
* so anything importing it — `select-values.ts`, and transitively
* `cell-format.ts` and `export-format.ts` — becomes server-only. That taint is
* the sole reason the tables grid used to hand-roll its own copy of the
* id-resolution logic client-side, and why a select cell's stored ids could
* drift from the names the client resolved them to.
*
* Keeping the option primitives here lets both sides share one implementation.
*/
import type { ColumnDefinition, JsonValue, SelectOption } from '@/lib/table/types'
/** Set of valid option ids for a `select`/`multiselect` column. */
export function optionIds(column: ColumnDefinition): Set<string> {
return new Set((column.options ?? []).map((o) => o.id))
}
/**
* Resolves a raw cell value to a declared option id, accepting either the
* stable id or (tolerant for tool/import writes) the option's display name.
* Returns null when no option matches.
*
* Id wins over name, and an exact name wins over a case-folded one, so an
* option whose *name* equals another option's *id* can never repoint a cell.
*/
export function resolveSelectOptionId(value: JsonValue, options: SelectOption[]): string | null {
// The block builder serializes without schema access, so an option NAME that
// looks numeric or boolean ("123", "true") arrives scalar-coerced. Stringify
// scalars so the name still resolves; arrays/objects stay unresolvable.
const text =
typeof value === 'string'
? value
: typeof value === 'number' || typeof value === 'boolean'
? String(value)
: null
if (text === null) return null
const byId = options.find((o) => o.id === text)
if (byId) return byId.id
const byName =
options.find((o) => o.name === text) ??
options.find((o) => o.name.toLowerCase() === text.toLowerCase())
return byName ? byName.id : null
}
/**
* Splits a raw value into the parts a multi-select cell should resolve. A cell
* may arrive as an array (canonical) or as a single comma-delimited string —
* the shape a multi cell exports, copies, and converts to text as — so both the
* write-path coercion and the column-conversion compatibility check read it
* through here rather than each deciding for itself. Option names that
* themselves contain commas are an accepted ambiguity.
*/
export function splitMultiSelectInput(value: JsonValue): JsonValue[] {
if (Array.isArray(value)) return value
if (typeof value !== 'string') return [value]
return value
.split(',')
.map((part) => part.trim())
.filter((part) => part !== '')
}
/**
* Resolves a raw value to the canonical stored shape for a select column: an
* array of option ids when `multiple`, otherwise a single id (or `null`).
*
* This is the one place the multi/single split is decided. Unresolvable parts
* are dropped and duplicates collapse to their first occurrence.
*/
export function resolveSelectCellValue(value: JsonValue, column: ColumnDefinition): JsonValue {
const options = column.options ?? []
if (column.multiple) {
const ids: string[] = []
for (const entry of splitMultiSelectInput(value)) {
const id = resolveSelectOptionId(entry, options)
if (id !== null && !ids.includes(id)) ids.push(id)
}
return ids
}
// Tolerate an array left behind by a multiple→single toggle by resolving its
// first element, rather than dropping the cell wholesale.
const single = Array.isArray(value) ? value[0] : value
return single === undefined ? null : resolveSelectOptionId(single, options)
}