System tables

Read pending scheduled jobs and stored file metadata through ctx.db.system — a read-only, eventually consistent view outside the mutation snapshot.

Last updated:

ctx.db.system is a read-only reader over Lunora's system tables. It lets a query or mutation introspect state the framework manages on your behalf, without that state having to live in your schema.

TableRowsBacked by
_scheduled_functionsPending scheduled jobsSchedulerDO
_storageStored object metadataR2

The one caveat that matters

Unlike ctx.db.<table> — which reads the same transactional SQLite snapshot the mutation writes into — the data behind these tables does not live in the shard. Scheduled functions live in the SchedulerDO; storage objects live in R2. Every read reaches across a network boundary.

Three consequences follow, and all three are load-bearing:

  • Eventually consistent. Each call issues a fresh read. A job a mutation just scheduled may not be visible yet; a job that just fired may still look pending.
  • Not part of the transaction snapshot. Reading a system table inside a mutation does not pin it. There is no OCC guard and no subscription dependency is recorded — so a live query does not re-run when a system table changes.
  • Read-only. There is no insert, patch, or delete. Change scheduled jobs through ctx.scheduler and stored objects through ctx.storage.

Treat every result as a point-in-time best effort. If your logic must be correct with respect to a scheduled job, record the fact in your own table inside the same mutation.

The read surface

Deliberately minimal — no indexes, no filters, no pagination:

import { query } from "@/lunora/_generated/server";

export const pendingJobs = query.query(async ({ ctx }) => {
    const jobs = await ctx.db.system.query("_scheduled_functions").collect();

    return jobs.filter((job) => job.scheduledFor > Date.now());
});

get(table, id) resolves a single row, returning null when absent — the id is the job id for _scheduled_functions, and the object key for _storage.

_scheduled_functions

One row per pending invocation:

FieldTypeMeaning
idstringThe job id
functionPathstringFully-qualified function to invoke
argsRecord<string, unknown>Arguments it will be dispatched with
scheduledFornumberWhen it fires (epoch ms)
enqueuedAtnumberWhen it was enqueued (epoch ms)
attemptsnumber | undefinedDispatch attempts so far; absent until a retry
shardKeystring | undefinedRouting hint so dispatch lands on the right shard

_storage

One row per stored object:

FieldTypeMeaning
keystringThe object key
sizenumberBody length in bytes
contentTypestring | undefinedRecorded Content-Type
sha256string | undefinedHex SHA-256, when R2 carries a checksum
uploadednumber | undefinedLast write (epoch ms), when reported
customMetadataRecord<string, string> | undefinedMetadata set at upload time

_storage reads require a storage binding to be configured; without one, they report that storage is not configured. The _scheduled_functions half is independent and keeps working either way.

See also