Wit

External data — the DataLoader seam

@load is the one seam between a Wit document and the outside world. Whether the data comes from a CLI subprocess or an in-process object, the rest of the pipeline never knows the difference — by the time resolve runs, loaded data is indistinguishable from a hand-written #name: { … } !!.

Concept

@load <alias> load@ names data by alias only; a host supplies the value. loadExternalData(doc, source) runs after parse, before resolve and rewrites every @load into an ordinary DataDef whose loadedFrom is the alias.

@load meta load@
@load(as: refs, key: devlin2019) cite load@
@load(from: team)

The alias is the body text, or the from parameter when there is no body — then the use needs no close. as renames the resulting data (@refs.title instead of @cite.title). Every other parameter is passed to the loader.

Signatures

  • loadExternalData(doc: Document, source: DataSource) → Document.
  • type DataSource = DataLoader | Record<string, unknown> — a loader function or a plain dict { alias: value } (the simplest embedded case).
  • type DataLoader = (req: DataLoadRequest) => unknown.
  • interface DataLoadRequest = { alias: string; args: Record<string, string>; loc: Loc }. args are the use's parameters minus as and from — @load(as: refs, key: devlin2019) cite load@ gives { alias: 'cite', args: { key: 'devlin2019' } }.
  • toDataValue(value: unknown, loc: Loc) → DataValue — normalises plain JSON into the Wit value model. It is the only shape that crosses the seam.

Basic — the dict form

The simplest source is a plain object keyed by alias:

import { parse } from '@witlang/parser';
import { loadExternalData, resolve, expand } from '@witlang/runtime';
import { renderHtml } from '@witlang/render-html';

const doc = loadExternalData(
  parse('@load meta load@\n\n@meta.version', 'report.wit'),
  { meta: { version: '0.4.0' } });

// the first child is now dataDef{ name: 'meta', value: record{ version: '0.4.0' } }
renderHtml(expand(resolve(doc)));   // → <article class="wit-doc"><p>0.4.0</p></article>

Realistic — the function form

For dynamic data, pass a loader. It is called once per @load, lazily, per alias:

const loaded = loadExternalData(doc, (req) => {
  // req = { alias, args: { …parameters }, loc }
  return fetchSync(req.alias, req.args);   // your synchronous I/O
});

Edge — toDataValue

toDataValue is how plain JSON becomes a DataValue: an object becomes an ordered Record, an array a Collection, primitives become scalars, and null or undefined become NullValue. Strings come back with literal: true, so loaded text is never read as Wit markup — an underscore in a title stays an underscore.

import { toDataValue } from '@witlang/runtime';

toDataValue({ a: 1, b: [2, 3] }, loc);
// → record{ fields: [ { a: numberValue 1 },
//                      { b: collection[ numberValue 2, numberValue 3 ] } ] }

Every option / edge

  • Dict source, missing alias. A dict with no entry for an alias throws no data provided for alias "x", which surfaces as E_LOAD_FAILED at the load site.
  • Throwing loader. Any error your loader throws becomes E_LOAD_FAILED, carrying the message and the load loc.
  • Downstream, loaded data is ordinary data. It flows through @meta.version, (each @team as p), @table(rows: @downloads), and interpolation with no special casing.
  • The same alias in several files. Two chapters that both @load team load@ make one definition, not E_DUPLICATE_DEFINITION — the resolver compares loadedFrom.
  • Referenced files. loadExternalData only walks the document you hand it. To load inside referenced files, pass it as resolve's prepare hook: resolve(doc, { rootPath, prepare: (d) => loadExternalData(d, source) }).
  • Raw load is skipped. A @@load raw node is not treated as a load (the guard checks raw !== true).
  • The CLI is one implementation of this seam. Its makeDataLoader reads wit.sources.json, runs the aliased program (gated by --allow-exec), and parses stdout by declared format; env is a built-in source returning the process environment. See CLI reference.

Common mistakes

  • Expecting loadExternalData to fetch. It only calls your loader or reads your dict — you own the I/O.
  • Returning a class instance instead of plain JSON. Only object, array, and scalar values cross via toDataValue.
  • Forgetting it must run before resolve. Order is parse → load → resolve → expand → render.

See also

Resolve & expand · Embed Wit in an app · CLI reference (the wit.sources.json schema) · AST reference (DataValue).