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.

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 named captures — @load cite |key devlin2019| load@ gives { key: 'devlin2019' }. |as name| rebinds the resulting DataDef name; |from alias| supplies the alias when there is no body prose.
  • 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 &#39;@witlang/parser&#39;;
import { loadExternalData, resolve, expand } from &#39;@witlang/runtime&#39;;

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

// the first child is now dataDef{ name: &#39;meta&#39;, value: record{ version: &#39;0.4.0&#39; } }
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: { …captures }, 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.

import { toDataValue } from &#39;@witlang/runtime&#39;;

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.
  • 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).