[ Data & storage ]
@sdxc/database-middleware
Middleware that publishes a remix/data-table database as ctx.db on a request or a job
- Depends on
remix- Used by
- uptime, auth-saas, blog
Middleware that publishes a remix/data-table database as ctx.db on a request or a job, so every handler, MCP tool, job and later middleware reads the database off the context it was given.
Installation
npm add @sdxc/database-middleware
It goes on a remix/router middleware chain and publishes a remix/data-table Database, so remix (v3) installs alongside it. The ./jobs entry point goes on an @sdxc/jobs dispatcher, which installs alongside it when you use it.
Usage
Publish The Database
import { database } from "@sdxc/database-middleware";
import { Database, sql } from "remix/data-table";
import { createRouter } from "remix/router";
let db = new Database(adapter);
let router = createRouter({ middleware: [database(db)] });
router.get("/posts", async (ctx) => {
let posts = await ctx.db.exec(sql`select * from posts`);
return Response.json(posts.rows);
});
Publish It To Jobs
@sdxc/database-middleware/jobs exports the same database() for a job dispatcher's middleware, under the same Database key, so a job handler reads ctx.db the way a route does:
import { createJobDispatcher } from "@sdxc/jobs";
import { database } from "@sdxc/database-middleware/jobs";
export const dispatcher = createJobDispatcher({ middleware: [database(createDatabase)] });
It takes the instance or a function opening it; a function is called once per job run, so a long-lived dispatcher reads whichever database the source returns for that run, which is how a test swaps in a fresh one. The dispatcher's context type carries ctx.db from the middleware's transform.
Install A Test Database
The middleware publishes whichever instance it is given, so a test builds the same router over an in-memory database:
import { DatabaseSync } from "node:sqlite";
import { database } from "@sdxc/database-middleware";
import { createSqliteDatabase } from "remix/data-table/sqlite";
import { createRouter } from "remix/router";
let db = createSqliteDatabase(new DatabaseSync(":memory:"));
let router = createRouter({ middleware: [database(db)] });
The Longhand
database(db) stands in for this hand-written middleware and the context key it sets:
import type { Database } from "remix/data-table";
import { createContextKey } from "remix/router";
let DatabaseKey = createContextKey<Database>();
let publishDatabase = (db: Database) => (ctx, next) => {
ctx.set(DatabaseKey, db, { property: "db" });
return next();
};
API
database(db: Database): Middleware
Returns a middleware for a router's, controller's, or route's middleware chain that sets db as ctx.db and under the Database key for every request it runs on. Its context transform types ctx.db on a router whose context is derived from the middleware list; importing the package also adds db to remix/router's RequestContext, so a handler typed against the plain context reads it too.
database(db: Database | (() => Database)): JobMiddleware from ./jobs
Returns job middleware for a dispatcher's middleware chain that sets the database as ctx.db and under the Database key for every job run, calling db once per run when it is a function. The dispatcher derives the job context's type from it.
Database
The context key the instance is published under. ctx.get(Database) returns the same object as ctx.db, for code that receives a context without knowing which middleware ran.
Pattern: Feeding A Model Registry
A middleware later in the chain reads ctx.db when it runs, so @sdxc/data-model binds its models to the published database:
import { database } from "@sdxc/database-middleware";
import { models } from "@sdxc/data-model/router";
import { Database } from "remix/data-table";
import { createRouter } from "remix/router";
let router = createRouter({
middleware: [database(new Database(adapter)), models(registry, (ctx) => ({ db: ctx.db }))],
});
Pattern: One Instance Per Request
A Worker whose binding is read from the request's environment builds the router per request and hands the middleware the instance opened for it:
import { database } from "@sdxc/database-middleware";
import { Database } from "remix/data-table";
import { createRouter } from "remix/router";
export default {
fetch(request: Request, env: Env) {
let router = createRouter({ middleware: [database(new Database(createAdapter(env.DB)))] });
return router.fetch(request);
},
};