# 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 & { /** `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; /** contentHash pins for the Cache Storage lookup, keyed `name@version`. */ pins?: Record; /** A CacheStorage to use instead of `globalThis.caches`. */ caches?: CacheStorage; local?: { packages?: Record }; }; ``` ## 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; export function open( spec: `${N}@${string}`, options?: OpenOptions, ): Promise>>; export function open(spec: string, options?: OpenOptions): Promise; ``` ```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; packages?: Record; }; }; ``` 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 { 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; truth(predicate: Rel, args: FactArg[], caseInput: CaseInput): Promise; /** * `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; whyNot(predicate: Rel, args: FactArg[], caseInput: CaseInput): Promise; collect(predicate: Rel, args: FactArg[] | { args: FactArg[]; free?: unknown }, caseInput: CaseInput): Promise; /** 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; calc(term: unknown, caseInput: CaseInput): Promise; positions(caseInput: CaseInput): Promise; /** * 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; /** * 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; } ``` `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; }; ``` ## 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); } export class PackageNotFoundError extends Error { packageName?: string; version?: string; constructor(message: string, details?: { name?: string; version?: string } & Record); } ``` 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.