Skip to content

Conditional Extraction

Four spec-level combinators handle missing data, fallbacks, value transforms, and validation. All four work across every format and compose freely with each other.

Wrap any extraction so failures return a default value (or undefined) instead of failing. Works across all formats and failure kinds.

import { optional } from '@origints/core'
// Returns undefined on failure
optional($.get('nickname').string())
// Returns null on failure
optional($.get('score').number(), null)
// Returns 0 on failure
optional($.get('count').number(), 0)
optional(row.colWhere(header, cell.equals('Ownership')).number(), null)

Ordered fallback chain. Tries specs in order, returns the first success. If all fail, returns the last failure.

import { tryExtract, mapSpec, literal } from '@origints/core'
tryExtract(
// Try extracting as number directly
$.get('price').number(),
// Fall back to parsing a string as float
mapSpec($.get('price').string(), v => parseFloat(v as string), 'parseFloat'),
// Last resort: null
literal(null)
)

TrySpec type:

interface TrySpec<T extends Spec = Spec> {
readonly kind: 'try'
readonly specs: readonly T[]
}

Transform a successful extraction result with a function.

import { mapSpec } from '@origints/core'
mapSpec(
$.get('date').string(),
v => new Date(v as string).getFullYear(),
'getYear'
)

MapSpec type:

interface MapSpec<T extends Spec = Spec> {
readonly kind: 'map'
readonly source: T
readonly fn: (value: unknown) => unknown
readonly description: string
}

Validate extracted values. Fails with 'constraint' kind if the predicate returns false.

import { guard } from '@origints/core'
guard($.get('age').number(), v => (v as number) > 0, 'Age must be positive')

GuardSpec type:

interface GuardSpec<T extends Spec = Spec> {
readonly kind: 'guard'
readonly source: T
readonly predicate: (value: unknown) => boolean
readonly description: string
readonly reason?: string
}

These combinators compose freely. A guard inside a tryExtract becomes a fallback when validation fails:

tryExtract(
guard(
$.get('age').number(),
v => (v as number) >= 0,
'Age must be non-negative'
),
literal(0) // fallback when guard fails
)

All four combinators can be chained using FluentSpec:

import { fluent, literal } from '@origints/core'
// Chain map → guard → fallback
fluent($.get('price').string())
.map(v => parseFloat(v as string), 'parseFloat')
.guard(v => (v as number) > 0, 'Must be positive')
.or(literal(0))
// Chain optional
fluent($.get('nickname').string()).optional()
fluent($.get('score').number()).optional(0)

fluent(spec) returns a FluentSpec<T> with .map(), .guard(), .or(), and .optional() methods. All consumer positions accept SpecLike (Spec | FluentSpec) and unwrap automatically.

All four are supported in:

  • executeSpec — runtime execution
  • walkSpec — source map derivation
  • deriveOutputSchemaFromSpec — JSON Schema derivation
  • InferOutput — TypeScript type inference