Reference / Records API for Bun and Node
Records API for Bun and Node
Every public class, function and type of the Records engine in @tinyshed/tinystore, generated from its source. The Records guide explains how to use them, and the Python, Go and Rust pages list the same API.
Level
type Level = 'debug' | 'info' | 'warn' | 'error' | numberA level a logger logs at: one of its four names, or slog's number between them.
EventLevel
type EventLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'An event's level: what loggers and the programs whose lines are kept write.
Fields
type Fields = Record<string, unknown> | readonly (readonly [string, unknown])[]What happened, or what it happened in: an object, or its pairs in their order.
Event
interface Event {
/** its time, which the file keeps to the nanosecond */
at: Date
stream: string
/** what happened; `log` for a logger's line */
name: string
level?: EventLevel
message?: string
/** each value as JSON.parse reads it, an integer past 2^53 a bigint */
fields: Record<string, unknown>
context: Record<string, unknown>
/** 32 hex digits */
traceId?: string
/** 16 hex digits */
spanId?: string
}An event as a read gives it.
NewEvent
interface NewEvent {
name: string
fields?: Fields
/** its time: now unless given, from what the stream keeps to ten minutes ahead */
at?: Date
context?: Fields
level?: EventLevel | number
message?: string
/** a trace's 32 hex digits, with its span's 16: the current trace's unless given */
traceId?: string
spanId?: string
}An event to append: its name, what happened, and what it carries beside.
AppendOptions
type AppendOptions = Omit<NewEvent, 'name' | 'fields'>EventsOptions
interface EventsOptions {
/** how long the stream keeps its events: its own, kept in the file; 14 days until one says */
keep?: Duration
}Query
interface Query {
/** the span before now, '1h'; or from and to */
since?: Duration
/** the range's start, included */
from?: Date
/** the range's end, excluded */
to?: Date
/** any of these streams, for every stream's read */
streams?: readonly string[]
/** any of these names: ['log'] is the lines */
names?: readonly string[]
/** this level and worse; an event without a level is not found */
minLevel?: EventLevel
traceId?: string
/** each key with this value, by the JSON it was written as, in fields or in context */
where?: Record<string, unknown>
/** this text in the message, the name or a value, its case ignored */
search?: string
/** newest first; oldest first without it */
newest?: boolean
/** the events of a page: 1000, and 10,000 at most */
limit?: number
/** a page's next, with the rest of the query as it was */
after?: string
}What a read finds: every condition given holds; one given as undefined is InvalidError.
Counted
type Counted = Omit<Query, 'newest' | 'limit' | 'after'>A query of every event it finds, for what counts them.
Page
interface Page {
events: Event[]
/** the page after it; the last page has none */
next?: string
}Top
interface Top {
value: unknown
count: number
}Stats
interface Stats {
samples: number
sum: number | bigint
min: number | bigint
max: number | bigint
avg: number
}A field's numbers in a row: a sum of integers exact, a bigint past 2^53.
Row
interface Row {
/** where the row's span begins, counted from 1970 in UTC */
at: Date
count: number
by: Record<string, unknown>
stats: Record<string, Stats>
distinct: Record<string, number>
}Aggregate
interface Aggregate extends Counted {
/** the span of a row: the range is one without it */
every?: Duration
/** keys whose every value has rows of its own */
by?: readonly string[]
/** fields whose numbers a row sums */
stats?: readonly string[]
/** fields whose different values a row counts */
distinct?: readonly string[]
}StreamInfo
interface StreamInfo {
name: string
keep: DurationText
/** its oldest event, or where its keep begins while it holds older */
retainedFrom: Date
newest: Date
}PollBatch
interface PollBatch {
events: Event[]
/** where the next poll begins, kept once these events are safe */
cursor: string
/** the events keep removed before the cursor reached them */
expired: number
}TailQuery
type TailQuery = Pick<Query, 'streams' | 'names' | 'minLevel' | 'traceId' | 'where' | 'search'>What a tail finds: a query's conditions, and no range, order or limit.
TailBatch
interface TailBatch {
events: Event[]
/** the events that left the store's hold before the tail read them */
missed: number
}A tail's batch: the events written since the last, and how many it missed.
Damage
interface Damage {
/** a head row holds events not packed yet, a segment packed ones */
part: 'head row' | 'segment'
id: number
stream: string
/** its events' oldest and newest times */
first: Date
last: Date
/** what broke */
error: string
}A head row or a segment that no longer reads, as damaged lists it and drop takes it.
Events
class Events extends Reads {
readonly stream: string
}A stream of events: written with append, which waits for the file, and read back.
Events.append
append(name: string, fields?: Fields, options?: AppendOptions): Promise<void>
append(events: readonly NewEvent[]): Promise<void>Writes an event, or a list of them whole or not at all: it returns once they are written, and a read finds them at once. A refused event names its place in the list and its name.
Events.appendLines
appendLines(lines: readonly ConsoleLine[]): Promise<void>A logger's lines, each an event of this stream.
Events.lines
lines(): LinesA writer of another program's output into this stream: each line it is given becomes an event at the time it arrived, a stack trace's or a JSON value's lines one, and a write never waits.
const out = store.events('worker').lines()
child.stdout.on('data', chunk => out.write(chunk))
child.on('close', () => out.end())AllEvents
class AllEvents extends Reads {}Every stream's events, to read: store.events().
AllEvents.streams
streams(): Promise<StreamInfo[]>Every stream with events, by name, with what it keeps, where that begins and its newest event's time.
AllEvents.poll
poll(cursor?: string, options?: {
limit?: number
}): Promise<PollBatch>Every event once, a batch a call, from a cursor the program keeps, the
oldest event kept without one: { events, cursor, expired }. With no
event past the cursor, the batch is empty and its cursor the one given.
AllEvents.damaged
damaged(): Promise<Damage[]>What reads met that no longer reads, which drop removes: each head
row and segment with its stream and its events' times. What the store
has not met since it opened is not listed.
AllEvents.drop
drop(damage: Damage): Promise<void>Removes a head row or a segment that damaged listed, so that the
reads over it work again; what still reads is refused.
for (const damage of await store.events().damaged()) await store.events().drop(damage)Lines
class Lines {}A writer of another program's output into a stream, as events.lines()
begins it: each line becomes an event, and a write never waits. It sends
whole lines, so that what it has no room for, dropped counts as lines.
Lines.dropped
get dropped(): numberThe lines dropped: past what the writer holds, and, once it ended, by the store.
Lines.write
write(chunk: Uint8Array | string): voidTakes a piece of output, cut anywhere; it never waits.
Lines.end
end(): Promise<void>Ends the writer: what it holds goes, a last line without its newline too, and it returns once the store wrote them.
StoreLoggerOptions
interface StoreLoggerOptions extends LoggerOptions {
/** how long the stream keeps its events, as store.events takes it */
keep?: Duration
}What a store's logger is told: a logger's options, and how long its stream keeps.
LoggerOptions
interface LoggerOptions {
/** the lines held between writes; past it a line is dropped and counted: 1024 */
buffer?: number
/** the least level a line is kept at; every level when absent */
level?: Level
/** how a line is written as it is logged: pretty on a terminal and JSON otherwise when absent */
console?: ConsoleFormat
/** how a pretty line shows its time: 'clock' when absent */
time?: ConsoleTime
/** leaves the stream out of a pretty line; the record keeps it */
hideStream?: boolean
/** where the console lines go: process.stderr when absent */
to?: ConsoleOut
/**
* secrets whose fields are hidden, in the store and on the console, at any
* depth: a key hides its value when some of its words in a row, written
* together, are a name's, so 'api key' hides api_key and apiKey; `secrets`
* holds the usual ones
*/
redact?: readonly string[]
/** leaves a URL's password in a value as it is, which is otherwise hidden */
keepUrlPasswords?: boolean
/** changes each field's value before it is hidden, kept and shown */
replace?: (key: string, value: unknown) => unknown
/**
* where LOG_LEVEL, LOG_FORMAT and LOG_TIME are read, which win over the
* options: `env('APP')` reads APP_LOG_LEVEL and the rest, and false
* reads nothing; the bare names when absent
*/
env?: EnvLayer | false
/** adds where each line was logged: source={"function":…,"file":…,"line":…} */
source?: boolean
}ConsoleOut
interface ConsoleOut {
write(text: string): unknown
readonly isTTY?: boolean
}Where console lines go: process.stdout, a file's stream, or any other writer.
logger
function logger(stream: string, options?: LoggerOptions): LoggerA logger of the console alone, for a program that wants the logger and not
the records: its lines are written as a store's logger writes them, and
store.logger(stream) in its place keeps them too.
const log = logger('app', { redact: ['password'] })
const db = log.with({ module: 'db' }) // passed to what needs itLogger
class Logger {}A logger of one stream. info, warn and the rest return at once; a line
goes to the console as it is logged, and becomes a record named log, its
message the body, the call's fields its attributes and the fields of with
its context, who is speaking.
const log = store.logger('api')
log.info('server started', { port: 3000 })
log.with({ requestId }).warn('slow request', { ms: 1200 })
log.event('user.created', { userId: 42 }) // a record named for the eventLogger.dropped
get dropped(): numberThe lines dropped since the logger began: a full buffer, or a write that failed. Each is also said on stderr, at most once in ten minutes.
Logger.debug
debug(message: string, attrs?: Fields): voidLogger.info
info(message: string, attrs?: Fields): voidLogger.warn
warn(message: string, attrs?: Fields): voidLogger.error
error(message: string, attrs?: Fields): voidLogger.event
event(name: string, attrs?: Fields): voidAn event of the stream: a record named for it, with no level and no body.
Logger.with
with(context: Fields): LoggerThe logger of the same stream, buffer and console, its context these fields besides its own.
Logger.flush
flush(): Promise<void>Hands what the buffer holds to the server now, as the timer does every second.
Logger.stop
stop(): Promise<void>Hands over what the buffer holds and stops the timer; a later line starts it again.
ConsoleFormat
type ConsoleFormat = 'pretty' | 'json' | 'off'How a logger writes its lines as they are logged; without one, pretty on a terminal and JSON otherwise.
ConsoleTime
type ConsoleTime = 'clock' | 'full' | 'off'How a pretty line shows its time: 11:02:11.123, 2026-10-02 11:02:11.123 +03:00 or none.
secrets
const secrets: readonly string[]The names redact takes to hide the usual secrets, as Go's console.Secrets and Python's SECRETS.
Trace
interface Trace {
traceId: Uint8Array | string
spanId?: Uint8Array | string
}A trace's 16 bytes and the span's 8, as bytes or hex.
withTrace
function withTrace<T>(trace: Trace, fn: () => T): TRuns fn in a trace: a logger's lines, and records appended without a trace of their own, inside it and in what it awaits, take its trace and span.
await withTrace({ traceId, spanId }, async () => {
log.info('charged') // a record of that trace and span
})