Skip to content

Using loadSpec()

Load and execute game specs at runtime without code generation. Use loadSpec() when you want to support custom or user-provided .randsum.json specs that aren’t part of the pre-generated game packages.

import { loadSpec } from '@randsum/games/schema'
// Load from a spec object
const spec = {
name: 'Simple d20',
shortcode: 'simple',
pools: { main: { sides: 20 } },
tables: {
outcome: {
ranges: [
{ min: 15, max: 20, result: 'success' },
{ min: 8, max: 14, result: 'partial' },
{ min: 1, max: 7, result: 'failure' }
]
}
},
outcomes: {
mainOutcome: { tableLookup: { "$ref": "#/tables/outcome" } }
},
roll: {
dice: { pool: { "$ref": "#/pools/main" }, quantity: 1 },
resolve: 'sum',
outcome: { "$ref": "#/outcomes/mainOutcome" }
}
}
const game = loadSpec(spec)
const result = game.roll()
console.log(result.result) // 'success' | 'partial' | 'failure'
console.log(result.total) // 1-20

Pass a file path string to read and parse a .randsum.json file from disk:

import { loadSpec } from '@randsum/games/schema'
const game = loadSpec('./my-game.randsum.json')
const result = game.roll()
console.log(result.result)

loadSpecAsync supports file paths, HTTP URLs, and spec objects. Use this for loading specs from remote sources or when you need non-blocking I/O:

import { loadSpecAsync } from '@randsum/games/schema'
// From a URL
const game = await loadSpecAsync('https://example.com/my-game.randsum.json')
const result = game.roll()
// From a local file (async)
const local = await loadSpecAsync('./my-game.randsum.json')
// From a spec object (same as loadSpec but async)
const inline = await loadSpecAsync(specObject)

Validate a spec before loading it. validateSpec returns a structured result:

import { validateSpec } from '@randsum/games/schema'
const result = validateSpec(unknownData)
if (!result.valid) {
for (const error of result.errors) {
console.error(error.path, error.message)
}
} else {
console.log('Spec is valid')
}

If your spec contains $ref entries pointing to external URLs, resolve them before loading:

import { resolveExternalRefs, loadSpec } from '@randsum/games/schema'
const spec = JSON.parse(rawJson)
const resolved = await resolveExternalRefs(spec)
const game = loadSpec(resolved)

resolveExternalRefs fetches any external $ref URLs and inlines the referenced data into the spec object.

A utility for looking up values in range-based tables. Used internally by the pipeline and available for custom integrations:

import { lookupByRange } from '@randsum/games/schema'
const table = {
'15-20': { label: 'Success', value: 'success' },
'8-14': { label: 'Partial', value: 'partial' },
'1-7': { label: 'Failure', value: 'failure' }
}
const match = lookupByRange(table, 18)
console.log(match.key) // '15-20'
console.log(match.result.label) // 'Success'

loadSpec returns a LoadedSpec — a record where each key is a roll definition name from the spec and each value is a callable roll function:

type LoadedSpec = Readonly<Record<
string,
(input?: RollInput) => GameRollResult<
string | number,
Readonly<Record<string, unknown>> | undefined,
RollRecord
>
>>

For the built-in games, there’s typically one roll definition (e.g., roll). Custom specs can define multiple roll definitions, each accessible as a separate function on the loaded spec.

When to use loadSpec vs pre-generated imports

Section titled “When to use loadSpec vs pre-generated imports”
Use caseApproach
Supported game (D&D 5e, Blades, etc.)import { roll } from '@randsum/games/fifth'
Custom game spec at build timeRun codegen, import the generated subpath
User-provided specs at runtimeloadSpec(userSpec)
Remote spec loadingloadSpecAsync(url)

Pre-generated imports are strongly typed and tree-shakeable. loadSpec is dynamic — the result type is generic (string | number) rather than game-specific union types. Use pre-generated imports when possible; use loadSpec when you need runtime flexibility.

The @randsum/games/schema subpath also exports codegen utilities used by the build pipeline:

  • generateCode(spec) — generates TypeScript source from a RandSumSpec. Returns a Promise<string> containing the generated module code. Used internally during bun run build to produce the .generated.ts files for each game.
  • specToFilename(name) — converts a game name to its generated filename (e.g., 'Blades in the Dark''blades'). Returns a string.
  • SchemaError — error class thrown by spec loading and pipeline functions. Has a code property: 'INVALID_SPEC', 'EXTERNAL_REF_FAILED', 'NO_TABLE_MATCH', 'REF_NOT_FOUND', 'INPUT_NOT_FOUND', 'INVALID_INPUT_TYPE', or 'CONDITION_TYPE_MISMATCH'.

These are primarily for contributors building the games package itself. Most consumers only need loadSpec or the pre-generated subpath imports.