Knowhere

Runtime Type Declarations

Knowhere runtime globals, as actually registered by the sandboxed VM (see runtime/pool.go). This is the authoritative reference for the 'runtime' MCP tool — knowhere://docs/runtime is the prose API guide and knowhere://docs/recipes has worked scripts (both also served by the 'docs' tool, slugs 'runtime' and 'recipes').

Note: this is hand-maintained; the shape test suite (services/runtime_shape_test.go) and deno check examples/ are what keep it honest against the Go source.

Globals

declare const assert: Assert;
declare const colors: Colors;
declare const geo: Geo;
declare const query: Query;
declare const tags: Tags;
declare const budget: Budget;
declare const address: Address;
declare const routing: Routing;

Properties

interface Properties {
  [key: string]: string | number | boolean;
}

Feature

// The live object behind every asFeature() is paulmach/orb's
// geojson.Feature crossing the Go/JS boundary, so it carries more than the
// three GeoJSON keys.
interface Feature {
  type: string; // "Feature"
  properties: FeatureProperties;
  geometry: Geometry;
  id: unknown; // null unless the source feature carried an id
  bbox: number[] | null; // [minLon, minLat, maxLon, maxLat] when present
  point(): OrbPoint; // center of the geometry's bounding box
  // marshalJSON / marshalBSON / unmarshalJSON / unmarshalBSON also cross
  // over (Go (de)serialisers trafficking in raw byte arrays). Of no use
  // from JS — return the feature itself and let the server serialise it.
}

FeatureProperties

// A feature's properties: the map handed to asFeature(), plus the result's
// tags when appendTags was true. Read keys directly.
interface FeatureProperties {
  [key: string]: unknown;
  // Go helper methods that also cross over. AVOID THEM: each panics when
  // the key is absent and no default is given, and the numeric ones panic
  // on any number that entered through JS (boxed as a type they refuse).
  // The panic is NOT a JS exception — try/catch cannot intercept it, the
  // script just dies. properties["key"] answers the same question safely.
  mustBool(key: string, def?: boolean): boolean;
  mustInt(key: string, def?: number): number;
  mustFloat64(key: string, def?: number): number;
  mustString(key: string, def?: string): string;
  clone(): FeatureProperties;
}

Geometry

// A raw orb geometry (feature.geometry, bounds.union()). Also array-like,
// holding coordinates in GeoJSON order: a Point is [lon, lat], a
// LineString an array of those, a Polygon an array of rings, a
// MultiPolygon an array of polygons.
interface Geometry {
  geoJSONType(): string; // "Point" | "LineString" | "Polygon" | "MultiPolygon" | ...
  dimensions(): number; // 0 point, 1 line, 2 area
  bound(): OrbBound;
}

OrbPoint

// A raw orb.Point: array-like [lon, lat] (GeoJSON order) with helper
// methods. Not the wrapper Point above — no asFeature()/asBound().
interface OrbPoint {
  0: number; // lon
  1: number; // lat
  lat(): number;
  lon(): number;
}

OrbBound

// The raw orb.Bound behind geometry.bound(). NOT the wrapper Bound above:
// no asFeature()/asBB(), extend() takes a point not meters, and pad() is
// in DEGREES. min/max are fields here, not methods. Point arguments accept
// a plain [lon, lat] array or a wrapper Point.
interface OrbBound {
  min: OrbPoint;
  max: OrbPoint;
  center(): OrbPoint;
  contains(point: OrbPoint | [number, number] | Point): boolean;
  intersects(other: OrbBound): boolean;
  extend(point: OrbPoint | [number, number] | Point): OrbBound;
  union(other: OrbBound): OrbBound;
  pad(degrees: number): OrbBound;
  left(): number; // minLon
  right(): number; // maxLon
  top(): number; // maxLat
  bottom(): number; // minLat
  isEmpty(): boolean;
  toPolygon(): Geometry;
  geoJSONType(): string;
}

Assert

interface Assert {
  geoJSON(payload: unknown): void;
  eq(value: boolean, message: string): void;
  stab(msg: string): void;
}

Colors

interface Colors {
  pick(index: number): string;
}

Bound

interface Bound {
  extend(distanceMeters: number): Bound;
  center(): Point;
  intersects(other: Locatable): boolean; // throws on a non-location argument
  contains(point: Locatable | [number, number]): boolean; // raw array is [lon, lat]; throws on a non-location
  min(): [number, number]; // [minLon, minLat]
  max(): [number, number]; // [maxLon, maxLat]
  left(): number; // minLon
  right(): number; // maxLon
  top(): number; // maxLat
  bottom(): number; // minLat
  asFeature(properties?: Properties, appendTags?: boolean): Feature;
  asBB(): string; // "minLon,minLat,maxLon,maxLat" — usable in (bb=...)
}

BoundArray

interface BoundArray extends Array<Bound> {
  asFeature(properties?: Properties, appendTags?: boolean): Feature;
  asBound(): Bound; // union bounding box
  union(): Geometry; // true polygon union (a MultiPolygon, not just a bbox)
}

Point

// Also array-like: p[0] is LON, p[1] is LAT (GeoJSON order — the opposite
// of asPoint's argument order). Prefer the methods.
interface Point {
  asFeature(properties?: Properties, appendTags?: boolean): Feature;
  asBound(): Bound;
  lat(): number;
  lon(): number;
}

Area

interface Area {
  name: string;
  fullName: string;
  minLat: number;
  minLon: number;
  maxLat: number;
  maxLon: number;
  asBound(): Bound;
}

Locatable

// Anything location-shaped. Every geometry entry point accepts any of
// these and THROWS on everything else — never a silent (0, 0).
type Locatable = Result | Point | Bound | Area | { lat: number; lon: number };

ResultCopy

// A plain-object copy of a Result ({...r}), recognized by carrying the
// bounding box. The read-only refusal on a Result recommends making one;
// asResults accepts it back.
type ResultCopy = {
  min_lat: number;
  min_lon: number;
  max_lat: number;
  max_lon: number;
};

Geo

interface Geo {
  asPoint(lat: number, lon: number): Point; // throws if lat/lon out of range
  point(coords: { lat: number; lon: number }): Point; // named keys — order can't be swapped
  bound(box: { minLat: number; minLon: number; maxLat: number; maxLon: number }): Bound;
  asResults(...results: (Result | ResultCopy)[]): ResultArray;
  asBounds(...bounds: Locatable[]): BoundArray; // a point collapses to a degenerate box
  distance(a: Locatable, b: Locatable): number; // meters, between centers
}

Result

// Reading any property NOT declared here throws UNKNOWN_PROPERTY (with the
// full roster and a did-you-mean) instead of returning undefined — a typo
// fails at the line that made it. Feature-test with ("x" in r); tag access
// (r.tags.anything) is ordinary and stays probe-free. Results are read-only.
interface Result {
  asFeature(properties?: Properties, appendTags?: boolean): Feature;
  name(): string; // method, not a property — reads the "name" tag if present
  id: number;
  osm_id: number;
  osm_type: number; // 1=node, 2=way, 3=relation
  lat: number; // bbox centroid — same value the search tool reports
  lon: number;
  min_lat: number;
  min_lon: number;
  max_lat: number;
  max_lon: number;
  tags: { [key: string]: unknown };
  asBound(): Bound;
  asPoint(): Point;
}

ResultArray

interface ResultArray extends Array<Result> {
  tagCount(): { [key: string]: number };
}

Query

interface Query {
  // Both stop at a row cap (50,000 by default) and THROW TOO_MANY_RESULTS
  // rather than returning a shortened array — a truncated set reads exactly
  // like a complete one. Use count() when you only need how many.
  union(...queries: string[]): ResultArray;
  execute(queryString: string): ResultArray; // sync — no await
  count(queryString: string): number; // COUNT(*), no rows materialised
  areas(): Area[];
  // area defaults to the address's state, snake_cased, if omitted.
  fromAddress(fullAddress: string, area?: string): ResultArray;
}

Tags

interface Tags {
  // the tag_keys / tag_values tools, in-runtime; both ranked by frequency.
  keys(area: string): { key: string; count: number }[];
  values(area: string, key: string): { value: string; count: number }[];
}

Routing

interface Routing {
  // travel modes this area has a graph for, e.g. ["car", "foot"].
  // Empty means the area carries no routing at all.
  profiles(area: string): string[];
  // travel seconds, or null when unreachable. Cheaper than route():
  // no shortcut expansion, no geometry.
  time(area: string, from: PointLike, to: PointLike, options?: RouteOptions): number | null;
  // one origin against many destinations, sharing one search setup.
  // Aligned with destinations by index; null where unreachable. This is
  // the one to use for ranking — N separate time() calls redo the setup.
  times(
    area: string,
    origin: PointLike,
    destinations: PointLike[],
    options?: RouteOptions,
  ): (number | null)[];
  // full trip: time, distance, snapped ends, and geometry on request.
  route(area: string, from: PointLike, to: PointLike, options?: RouteOptions): Leg | null;
  // resolve a coordinate onto the graph; null if nothing routable is near.
  snap(area: string, at: PointLike, options?: RouteOptions): Snapped | null;
  // every junction reachable ON FOOT within maxWalkMeters, measured along
  // real paths. null only when the origin does not snap onto the network.
  //
  // Walking only, and profile defaults to "foot" here for that reason:
  // the car graph is contracted, where a one-sided search returns the
  // search space rather than the reachable set, so asking for "car"
  // stops the script instead of answering wrongly.
  //
  // Capped at 5000 m — the neighbourhood is held in memory and grows with
  // the square of the radius. Over that throws rather than clamping.
  //
  // This is the raw primitive: thousands of junctions, for bucketing and
  // filtering in a script. For "what places can I walk to" prefer the
  // walk_reach tool, which annotates POIs instead.
  reach(area: string, origin: PointLike, options?: RouteOptions): Reached[] | null;
}

PointLike

// Routing coordinates share the geometry layer's coercion, so anything
// location-shaped works: a Result straight from query.execute, a Point, a
// Bound or Area (centre used), or a plain {lat, lon} object.
type PointLike = Locatable;

RouteOptions

interface RouteOptions {
  profile?: "car" | "foot"; // default "car"
  maxSnapMeters?: number; // reject an endpoint further than this from a road
  maxWalkMeters?: number; // foot only; default 10000, max 25000 (reach(): default and max 5000)
  geometry?: boolean; // route() only — off by default, a long route is 1000s of points
  steps?: boolean; // route() only — the road log; costs about half a route again
}

Snapped

interface Snapped {
  point: Point;
  offsetMeters: number; // how far the coordinate moved to reach the graph
  // The road this landed on. The router resolves this edge to choose the
  // junction, so it costs nothing extra — and it is the answer to "which
  // street is this", measured against the road's real shape rather than
  // its bounding-box centre. null only on an artifact built before the
  // edge-to-way mapping existed.
  road: Road | null;
}

Road

interface Road {
  osmId: number;
  name: string; // "Dwight D. Eisenhower Highway"; "" on the 73% of ways with none
  ref: string; // route number: "I 70", "US 40"; "" on an unnumbered street
  highway: string; // OSM class: motorway, trunk, primary, residential, service
}

Step

// One run of a route along a single road. NOT a turn instruction: there
// are no turn angles, no lane tags and no "turn left". Consecutive
// stretches of the same road are one step, and slip roads are folded
// into the road they lead onto.
interface Step {
  road: Road;
  atMeters: number; // how far into the trip this road begins; steps tile
  atMiles: number;
  meters: number;
  miles: number;
  via: string; // the motorway junction reached through, e.g. "exit 232"; "" if none
}

Reached

interface Reached {
  point: Point;
  seconds: number;
  minutes: number;
  // Network distance derived from cost at walking pace, not measured from
  // geometry — unpacking polylines for thousands of nodes would cost more
  // than the search. Exact enough to pick candidates; use route() when a
  // distance is going to be reported to someone.
  meters: number;
  miles: number;
}

Leg

interface Leg {
  seconds: number;
  minutes: number;
  meters: number; // along the road, not straight-line
  miles: number;
  from: Snapped;
  to: Snapped;
  geometry: Point[]; // empty unless options.geometry was set
  steps: Step[]; // empty unless options.steps was set
}

Budget

interface Budget {
  remainingMs(): number; // ms until the VM is interrupted at the deadline (0 if past)
  elapsedMs(): number; // ms this call has been running
}

URL

// The only web globals in the VM: WHATWG URL parsing (from goja_nodejs).
// There is no fetch — the runtime cannot reach the network.
declare class URL {
  constructor(url: string, base?: string);
  href: string;
  protocol: string;
  host: string;
  hostname: string;
  port: string;
  pathname: string;
  search: string;
  hash: string;
  searchParams: URLSearchParams;
  toString(): string;
}

URLSearchParams

declare class URLSearchParams {
  constructor(init?: string);
  get(name: string): string | null;
  getAll(name: string): string[];
  has(name: string): boolean;
  set(name: string, value: string): void;
  append(name: string, value: string): void;
  delete(name: string): void;
  toString(): string;
}

Address

interface Address {
  // Returns [parsedParts, found]. found is false (and parsedParts empty)
  // when the address could not be parsed.
  parse(fullAddress: string): [{ [key: string]: string }, boolean];
}

input

// ---------------------------------------------------------------------------
// Checks
//
// These apply only to a script run as a CHECK (server/checks) — a question
// asked once and then run against many addresses. A plain `runtime` tool call
// has no `input` and may export any JSON it likes.
// ---------------------------------------------------------------------------
// The house being checked. Go resolves the address and binds this before the
// VM runs, because the runtime has no geocoder and a script that guessed its
// own anchor would measure every number from the wrong place.
declare const input: CheckInput;

CheckInput

interface CheckInput {
  address: string; // exactly what was typed
  label: string; // what the geocoder resolved it to
  lat: number; // the anchor. Measure from here.
  lon: number;
  area: string; // snake_case; every query and every route needs it
  kind: "address" | "poi" | "street";
  // False when this area's build carries no foot graph. A walking criterion
  // must then report "unknown" rather than "fail": the house is not wrong,
  // we simply cannot measure it.
  walkGraph: boolean;
  // Present only when the target was a listing URL.
  listing?: CheckListing;
}

CheckListing

interface CheckListing {
  source_url: string;
  price?: number;
  beds?: number;
  baths?: number;
  sqft?: number;
  lot_size?: number;
  lot_units?: string;
  year_built?: number;
  water_source?: string;
  sewer?: string;
  hoa_fee?: number;
  hoa_frequency?: string;
  taxes_annual?: number;
  days_on_market?: number;
}

Criterion

// One named finding.
//
// `status` is OPTIONAL, and that is the point: a criterion carrying one is a
// test, a criterion carrying only a value is a measurement. Omit it whenever
// the question has no threshold ("how many", "how far") rather than inventing
// one — measurements are what make a column sortable across addresses.
interface Criterion {
  name: string;
  status?: "pass" | "fail" | "unknown";
  // Omit entirely when there is nothing to measure. Never write 0 as a
  // placeholder: "0 min walk" renders as the BEST possible answer.
  value?: number | null;
  unit?: string;
  // Built from the numbers just computed, so it cannot contradict the value
  // printed beside it.
  detail?: string;
  // Evidence: where the thing that matched actually is. Dropped by the server
  // if it is further than 200km from the anchor, because at that distance it
  // names something else.
  at?: { lat: number; lon: number };
  items?: CriterionItem[]; // capped at 25
}

CriterionItem

interface CriterionItem {
  label: string;
  value?: number | null;
  unit?: string;
  at?: { lat: number; lon: number };
}

CheckPayload

// What a check exports. Note there is no overall pass/fail: the server
// derives it (any fail -> fail, else any unknown -> unknown, else pass), so a
// summary boolean can never disagree with the criteria it summarises.
interface CheckPayload {
  criteria: Criterion[]; // at least one, at most 12
  // Anything else, stored and returned untouched. The escape hatch for what
  // the criteria shape cannot express.
  data?: unknown;
}