Docs

Telemetry

Telemetry stores a property's value over time. Reach for it when history matters — a project's progress over its life, a department's headcount month to month, any reading or counter you want to keep the full series of, not just the current value.

A telemetry property keeps every point you append, and the object record always reflects the latest one.

Declare a telemetry property

Set mode: "telemetry" on a property in the ontology. Without it, a property is "static": a single fact on the object record.

TS
import { defineObjectType, prop, stringEnum } from "@sixb/core/ontology"

export const Project = defineObjectType({
  id: "Project",
  name: "Project",
  properties: [
    prop("id", "string", { required: true, primary: true }),
    prop("name", "string", { required: true }),
    prop("status", stringEnum(["draft", "active", "paused", "completed", "cancelled"])),
    prop("progress", "integer", { mode: "telemetry" }),
  ],
})

progress is a plain integer counter — no unit, no semantic type. If a reading represents a physical quantity (a temperature, a pressure), add a semanticType so every point carries a unit; see units and semantics. Money is never telemetry-with-units — model it as amount (double) plus a currency enum.

Telemetry properties cannot use the "fileRef" schema.

Append a point

Reach a single object with .byId(id), select the telemetry property with the typed token Type.p.<name>, and call .append(...).

TS
import { Project } from "./ontology/project"

await sixb.objects(Project).byId("proj-001").telemetry(Project.p.progress).append({
  value: 45,
  at: new Date(),
})

append(...) takes:

FieldRequiredMeaning
valueYesThe reading. Validated against the property schema.
atYesThe instant of the reading, as a Date.
unitWhen the property has a semanticTypeA valid unit id for the property's quantitative type.

The object must already exist — appending to a missing object throws. The property must be telemetry mode and the token must belong to the object type; both are checked before the write.

A property with no semanticType rejects a unit. A property with one requires it, and the unit must belong to its family. See units and semantics.

Append in batch

appendTelemetryBatch writes many points across many objects of one type in a single call. Pass the bare value, or wrap it as { value, unit } when the property carries a unit.

TS
await sixb.objects(Department).appendTelemetryBatch([
  {
    id: "dept-eng",
    properties: { headcount: 42 },
  },
  {
    id: "dept-sales",
    properties: { headcount: 17 },
    at: new Date("2026-06-23T12:00:00Z"),
  },
])

Each item accepts:

FieldRequiredMeaning
idYesPrimary id of an existing object of this type.
propertiesYesMap of telemetry property id to a value, or { value, unit }.
atNoInstant for every property in the item. Defaults to now.

Every value is validated and every required unit is checked before any point is written. To record points from table data instead of code, use a telemetry projection.

Identity: (series, at)

A telemetry point is uniquely identified by its series — the tuple (objectType, object, property) — and its instant, at. Writing the same instant on the same series again is a last-write-wins upsert: it updates the value rather than adding a duplicate.

This makes telemetry writes idempotent under replay — re-appending or re-projecting the same (series, at) never produces a second point.

Read history and latest

Reads go through the time-series store at sixb.storage.timeseries.

TS
const history = await sixb.storage.timeseries.getHistory({
  projectId: sixb.id,
  objectTypeId: "Project",
  objectId: "proj-001",
  propertyId: "progress",
  from: new Date("2026-06-01T00:00:00Z"),
  to: new Date("2026-06-23T00:00:00Z"),
  order: "desc",
  limit: 100,
})

const latest = await sixb.storage.timeseries.getLatest({
  projectId: sixb.id,
  objectTypeId: "Project",
  objectId: "proj-001",
  propertyId: "progress",
})

getHistory parameters:

ParamRequiredMeaning
projectIdYesThe project id, usually sixb.id.
objectTypeIdYesThe object type id.
objectIdYesThe object's primary id.
propertyIdYesThe telemetry property id.
fromNoInclusive lower bound on at.
toNoInclusive upper bound on at.
orderNo"asc" (default) or "desc".
limitNoMaximum number of points to return.

getLatest takes the four identity params and returns the most recent point, or null when the series is empty. Each point has value, optional unit, at, and the four identity fields.

Over HTTP

The server exposes the same operations as routes on an object:

MethodPathOperation
POST/api/objects/:objectTypeId/:objectId/telemetry/:propertyIdAppend a point
GET/api/objects/:objectTypeId/:objectId/telemetry/:propertyId/historyRead history
GET/api/objects/:objectTypeId/:objectId/telemetry/:propertyId/latestRead latest point

The append body is { value, unit?, at? }. History accepts from, to, order, and limit as query parameters. See the HTTP reference for the full object API.

The telemetry.appended event

Every appended point emits a telemetry.appended domain event. Its payload is:

TS
{
  objectTypeId: string
  objectId: string
  propertyId: string
  value: unknown
  unit?: string
  at: string // ISO 8601
}

Subscribe to it to react to new readings — drive rules, push live updates to the UI, or fan out to a broker. See events for how to consume domain events.

Search docs

Search the documentation