HighX
Server

Global storage

The D1 authority — what it is for, and why only actions may touch it.

There are two storage authorities, and no operation spans both.

TenantGlobal
Backed byDurable Object SQLiteD1
Accessctx.dbctx.global
Reachable fromquery, mutation, actionaction only
Transactionsyes, synchronousnone
Live subscriptionsyesno

A query cannot see global data because a query is reactive and global data is not live-subscribable. A mutation cannot, because a transaction that appeared to span two authorities would be a lie. Only an action holds both, and it composes them through a saga.

The types enforce this rather than the documentation:

mutation({
  handler: (ctx) => {
    ctx.global; // Property 'global' does not exist on type 'RequestContext'
  },
});

Setting it up

Give the tenant object a store, and create your tables on start:

highx/index.ts
import { createTenantObject, D1GlobalStore } from "@highx/cloudflare";

export const TenantObject = createTenantObject<Env>({
  schema: databaseSchema,
  functions: serverFunctions,
  subscribableQueries,
  globalStore: (env) => new D1GlobalStore(env.GLOBAL_DB),
  // D1 has no migration hook a tenant object participates in, so tables are
  // created inside blockConcurrencyWhile, before any request is served.
  onStart: async (env) => {
    await new D1GlobalStore(env.GLOBAL_DB).ensureTable("published_notes");
  },
});

highx generate infers the D1 binding from new D1GlobalStore(env.GLOBAL_DB) and highx provision creates the database — you never declare it by hand.

Skip globalStore and every ctx.global call rejects with "No global store is configured for this runtime". Returning empty data instead would turn a missing binding into silently absent records, which is far harder to notice in production.

The API

await ctx.global.get("published_notes", id);
await ctx.global.find("published_notes", { authorId });
await ctx.global.insert("published_notes", { title, body });
await ctx.global.update("published_notes", id, { title });
await ctx.global.delete("published_notes", id);

Asynchronous, and with no transaction method at all — there is nothing for tenant work to be nested inside.

How rows are stored

D1GlobalStore keeps each row as (id TEXT PRIMARY KEY, data TEXT) — opaque JSON. find() therefore scans the table and filters after decoding.

That is fine for a small read model and wrong for anything with volume. Promote the fields you filter on to real columns and own the migrations yourself; ensureTable is a convenience for getting started, not a schema tool.

Serving it publicly

Global data is read-through and eventually consistent. A public route says so explicitly:

cache-control: public, max-age=60
etag: W/"..."
x-highx-consistency: eventual

The payload deliberately carries no subscription ID and no sequence number, so a public reader cannot mistake it for a live view. A stale public page is acceptable; an authenticated live query must converge.

Next

On this page