Markdown for LLMs
TypeScript
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# TypeScript
The facade is `@arxo/law` at version 0.3.3 with the canon package
`@arxo/canon-bgb-fristen` at 0.1.5, which carries the canon
`de.bgb.fristen` at 0.1.0. You need Node 20 or newer — 20 is the
floor, and a supported LTS (24 recommended) is the route for a new
setup. Everything below runs locally after install; the calls that
reach the network say so in their own chapter.
## Install
Two routes, different promises:
```bash
# Reproduce the pinned example: the versions in the compatibility record.
npm i @arxo/law@0.3.3 @arxo/canon-bgb-fristen@0.1.5
```
```bash
# Install current versions: newest compatible pair, not the pinned one.
npm i @arxo/law @arxo/canon-bgb-fristen
```
The first route installs the pair listed on the compatibility page.
The second may install newer releases; inside the starter prefer
`npm ci`, which reproduces the whole pinned tree from the lockfile,
and re-run the probe on the compatibility page when a version
changes.
## Lifecycle in four lines
A package goes from open, to query, to reading the answer, to archive:
```ts
const fristen = await open('de.bgb.fristen@0.1.0'); // open
const r = await fristen.truth('frist_ende', ['frist', '2026-03-20'], caseInput); // query
console.log(r.evaluationStatus, r.truthStatus); // read
r.document; // archive: canonical bytes
```
`document` is a `Uint8Array` of canonical bytes on a local answer —
what you store or hand to a reviewer. A host answer carries `document:
null` and keeps the raw payload in `host`; the results chapter covers
both shapes.
The rest of the page is the detail behind these four lines: the
[entry points](#entry-points) for Node and the browser, the
[`open` overloads](#open) and their options, and the full
[`LawPackage`](#lawpackage) declarations with the
[errors](#errors) they raise.
## Entry points
The package map exposes one surface per host:
```json
{
".": "the facade: open, LawPackage, errors",
"./browser": "no Node-only imports; wasm bytes are passed in",
"./router": "route a question in your own words to a canon",
"./case-package": "open a saved case package from disk (Node)",
"./arxo": "containers: pin several canons into one world",
"./task-guides": "guided passages over a canon (Node)"
}
```
The browser entry takes the engine bytes and the canons from the
bundle instead of the file system:
```ts
export type BrowserOpenOptions = Omit<OpenOptions, 'wasmPath' | 'cacheDir' | 'local'> & {
/** `law_wasm_core.wasm`: a fetch Response, raw bytes, or a URL to fetch. Required. */
wasm: Response | ArrayBuffer | Uint8Array | string | URL;
/** Canons the bundle carries, keyed `name@version`. */
canons?: Record<string, { ir: unknown; calendar?: string; meta?: { passport?: PassportMeasure } }>;
/** contentHash pins for the Cache Storage lookup, keyed `name@version`. */
pins?: Record<string, string>;
/** A CacheStorage to use instead of `globalThis.caches`. */
caches?: CacheStorage;
local?: { packages?: Record<string, { ir: unknown; calendar?: string }> };
};
```
## Open
`open` resolves a pinned spec to a package object. The spec always
carries a version; the typed overload completes predicate names from
the installed canon's registry entry.
```ts
export function open(spec: string, options: OpenOptions & { serve: ServeOptions }): Promise<ServePackage>;
export function open<N extends string>(
spec: `${N}@${string}`,
options?: OpenOptions,
): Promise<LawPackage<RelationsOf<N>>>;
export function open(spec: string, options?: OpenOptions): Promise<LawPackage>;
```
```ts
import { open } from '@arxo/law';
const fristen = await open('de.bgb.fristen@0.1.0');
```
After `open`, the predicate argument is typed by the canon: the editor
completes `frist_ende`, and a name the canon does not know fails type
checking before anything runs. A StableId of the form `urn:...#local`
is always accepted and checked at run time.
The options select where the canon comes from and how strictly the
facade stays offline:
```ts
export type OpenOptions = {
/** Connect to `law serve` without loading the local wasm engine. */
serve?: ServeOptions;
remote?: boolean;
offline?: boolean;
fetch?: typeof fetch;
cacheDir?: string;
registryUrl?: string;
mcpEndpoint?: string;
/** The act key the MCP host uses (`metadata.act`), when it differs from the canon name and no installed canon names it. */
hostPackage?: string;
wasmPath?: string | URL;
calendar?: string;
contentHash?: string;
passport?: PassportMeasure;
engine?: unknown;
local?: {
root?: string;
map?: Record<string, { clir: string; calendar?: string; passport?: string }>;
packages?: Record<string, { ir: unknown; calendar?: string; meta?: unknown }>;
};
};
```
With `serve`, `open` returns a `ServePackage` instead: no local engine
is loaded, and questions go to the server over HTTP. See the loading
chapter for what each source means.
## LawPackage
```ts
export declare class LawPackage<Rel extends string = string> {
readonly name: string;
readonly version: string;
readonly source?: string;
readonly via: 'local' | 'mcp';
/** The act key the MCP host uses for this package. */
readonly hostKey: string;
questions(lang?: string): FormManifest;
passport(): Passport;
ask(request: AskRequest): Promise<Answer>;
truth(predicate: Rel, args: FactArg[], caseInput: CaseInput): Promise<Answer>;
/**
* `focused_truth`: the same answer as `truth`, the proof and
* issues by the question's cone only. Not an audit document.
*/
focusedTruth(predicate: Rel, args: FactArg[], caseInput: CaseInput): Promise<Answer>;
whyNot(predicate: Rel, args: FactArg[], caseInput: CaseInput): Promise<Answer>;
collect(predicate: Rel, args: FactArg[] | { args: FactArg[]; free?: unknown }, caseInput: CaseInput): Promise<Answer>;
/** Calendar operation: `date` plus `days`/`unit`; `policy` defaults to the case's `deadlinePolicy`. */
deadline(
opArgs: { date: string; days?: number; unit?: 'business_day' | 'calendar_day'; afterTime?: string; policy?: unknown; suspensions?: Array<{ start: string; end: string }> },
caseInput: CaseInput,
): Promise<Answer>;
calc(term: unknown, caseInput: CaseInput): Promise<Answer>;
positions(caseInput: CaseInput): Promise<Answer>;
/**
* Term unfolding: the tree of producers of a predicate
* over the package's world and the text the engine's `unfold` prints. No rule is
* executed. `predicate`: a local name, `package::name`, or a full id.
*/
unfold(predicate: Rel | string, options?: { lang?: string; depth?: number; quote?: number; fold?: boolean }): { tree: unknown; text: string };
/** The host's `law_explain` over the document this answer carries. Needs the host. */
explain(answer: Answer, options?: { nodeId?: string }): Promise<Explanation>;
/**
* Answer verbalization: question and answer as controlled text
* by the template pack — the artifact `law.answer-verbalization/0.1`. Local answers only;
* needs the full engine (`options.engine`, or loaded lazily in Node).
*/
verbalize(answer: Answer, options?: { detail?: 'full' | 'brief'; pack?: unknown; engine?: unknown }): Promise<AnswerVerbalization>;
}
```
`ask` takes the uniform request; the named methods are wrappers over
it. The query chapter says when to use each kind.
```ts
export type AskRequest = {
kind: 'truth' | 'focused_truth' | 'why_not' | 'collect' | 'calendar_op' | 'term' | 'positions';
predicate?: string;
args?: FactArg[];
caseInput: CaseInput;
queryId?: string;
caseName?: string;
extra?: Record<string, unknown>;
};
```
## Errors
Two errors belong to the facade; the transport family is shared with
the client package:
```ts
export class FactError extends Error {
path?: string;
nearest?: string[];
constructor(message: string, details?: { path?: string; nearest?: string[] } & Record<string, unknown>);
}
export class PackageNotFoundError extends Error {
packageName?: string;
version?: string;
constructor(message: string, details?: { name?: string; version?: string } & Record<string, unknown>);
}
```
A misspelt predicate is refused with the nearest real ones:
```js
await fristen.truth('frist_endee', ['frist', '2026-03-20'], caseInput);
// FactError: unknown relation "frist_endee"
// path: "query", nearest: ["frist_ende", "frist_ende_kalender", "frist_ereignis", ...]
```
The shared client errors are `LawClientError` (the common ancestor),
`TransportError`, `ProtocolError`, `RpcError`, `ValidationError`, and
`IntegrityError`. The results chapter tables every class with the
condition that raises it.
## Answers
```ts
export type Answer = LocalAnswer | RemoteAnswer;
```
A local answer carries `document: Uint8Array` and `via: 'local'`; a
host answer carries `document: null`, `via: 'mcp'`, and the raw payload
in `host`. Both carry `evaluationStatus`, `hashes`, and, where the kind
produces them, `truthStatus`, `value`, `whyNot`, and `missingInputs`.
## Example: compute the end date
The frist scenario from the section map, asked through `collect`:
```js
const end = await fristen.collect('frist_ende', ['frist', '?end'], {
legalTime: '2026-09-17',
timezone: 'Europe/Berlin',
deadlinePolicy: 'urn:de:corpus:clir:bgb-fristen#BGB_FRISTEN_TAG',
answers: [
{ predicate: 'frist_ereignis', args: ['frist', '2026-03-06'] },
{ predicate: 'frist_dauer_tage', args: ['frist', '14 calendar_day'] },
],
});
end.evaluationStatus; // 'COMPUTED'
end.value[0].value; // '2026-03-20'
```
Ask the same question with a wrong candidate date through `truth` and
the answer is `COMPUTED` with `NEITHER`; drop one fact and it is still
`COMPUTED` with `NEITHER`, with two blockers under `whyNot`.
## Limits
- Predicate names complete only for installed canons that register
their relations; otherwise the argument stays a plain string.
- `explain` needs the host: it calls `law_explain` over the carried
document, so an offline package cannot explain.
- `verbalize` needs the full engine and a template pack; local answers
only.
- `focusedTruth` returns a slice by the question's cone, not an audit
document: it has no full `result` hash.
## Where next
Back to the [SDK map](/build/sdk/). For an application, read on in
this order: [loading and pinning](/build/sdk/loading-and-pinning/)
for reproducible versions, [facts and context](/build/sdk/facts-and-context/)
for the call shape, and [results and errors](/build/sdk/results-and-errors/)
for the answer shape.