Dev Server
Run a local in-memory API server for offline development. No remote connection required.
Quick Start
Run your app with the dev server using a single command.
# Run with dev mode (in-memory server, no remote connection needed)
REOPT_DEV_MODE=true next dev
# Or use the npm script
pnpm dev:localcreateDevServer()
Create an in-memory API server with your schema definition.
import { createDevServer } from "@reopt-ai/brandapp-sdk/dev";
const devServer = await createDevServer({
port: 4300,
schema: {
entities: {
contacts: {
name: "contacts",
description: "Contact management",
icon: "👤",
attributes: {
name: {
label: "Name",
dataType: "string",
isRequired: true,
},
email: {
label: "Email",
dataType: "email",
isRequired: true,
isUnique: true,
},
},
},
},
},
dataPath: ".reopt/dev-runtime.json",
verbose: true,
});
console.log("Dev server running at:", devServer.url);Seed Data
Define seed data that is automatically loaded when the dev server starts.
// reopt.seed.ts — automatically applied when dev server starts
export default {
contacts: [
{ values: { name: "Alice Johnson", email: "alice@example.com", status: "active" } },
{ values: { name: "Bob Smith", email: "bob@example.com", status: "active" } },
{ values: { name: "Carol Davis", email: "carol@example.com", status: "inactive" } },
],
};Instrumentation
Auto-start the dev server when your Next.js app boots in dev mode.
// instrumentation.ts — auto-start dev server on app startup
export async function register() {
// Gate on NEXT_RUNTIME === "nodejs": the dev server imports Node built-ins
// (http / fs / path), so this must NOT run on the Edge runtime.
if (
process.env.NEXT_RUNTIME === "nodejs" &&
process.env.REOPT_DEV_MODE === "true"
) {
const { startDevServer } = await import("./lib/dev-server");
await startDevServer();
}
}Bootstrap Module
The dev server bootstrap module overrides environment variables so existing SDK clients automatically connect to the local server.
// lib/dev-server.ts — Dev Server bootstrap
import type { DevServer, DevSchema } from "@reopt-ai/brandapp-sdk/dev";
let devServer: DevServer | null = null;
export async function startDevServer() {
if (devServer) return devServer;
const { createDevServer } = await import("@reopt-ai/brandapp-sdk/dev");
const schema = (await import("./eav.schema")).default;
// Convert the defineSchema() output to the DevSchema shape createDevServer()
// expects: flatten each entity's attributes into label/dataType/isRequired/
// isUnique/options, and carry over name/description/icon per entity.
const devSchema: DevSchema = { entities: {} };
for (const [key, entity] of Object.entries(schema.entities)) {
const attrs: DevSchema["entities"][string]["attributes"] = {};
for (const [attrKey, attr] of Object.entries(entity.attributes)) {
const a = attr as Record<string, unknown>;
attrs[attrKey] = {
label: (a.label as string) ?? attrKey,
dataType: (a.dataType as string) ?? "text",
isRequired: (a.isRequired as boolean) ?? false,
isUnique: (a.isUnique as boolean) ?? false,
options: a.options as DevSchema["entities"][string]["attributes"][string]["options"],
};
}
devSchema.entities[key] = {
name: entity.name ?? key,
description: entity.description,
icon: entity.icon,
attributes: attrs,
};
}
devServer = await createDevServer({
port: 4300,
schema: devSchema,
dataPath: ".reopt/dev-runtime.json",
verbose: true,
});
// Override env for existing SDK clients
process.env.REOPT_BASE_URL = devServer.url;
process.env.BRANDAPP_CLIENT_ID = "dev";
process.env.BRANDAPP_CLIENT_SECRET = "dev";
process.env.BRANDAPP_ID = "dev-brandapp";
// Apply seed data
const seedData = (await import("../reopt.seed")).default;
await devServer.seed(seedData, { mode: "upsert" });
return devServer;
}Faithful OIDC mock
3.3.0The dev server is a faithful OIDC identity provider: it signs real EdDSA tokens with an in-memory Ed25519 keypair, serves the matching JWKS at /api/auth/jwks, and mounts /api/auth/oauth2/end-session. dev.mintLogoutToken() drives an OIDC Single Logout receiver end-to-end with no reopt server — this is what powers the Single Logout demo.
import { createDevServer } from "@reopt-ai/brandapp-sdk/dev";
import { createBackchannelLogoutHandler } from "@reopt-ai/brandapp-sdk/logout";
const dev = await createDevServer({ brandappId: "dev" });
// The receiver verifies against the dev IdP's discovery + JWKS (idBaseUrl: dev.url).
const handler = createBackchannelLogoutHandler({
clientId: "my-brandapp",
idBaseUrl: dev.url,
onLogout: async ({ sid }) => {
await db.sessions.deleteMany({ where: { reoptSid: sid } });
},
});
// Mint a spec-compliant, EdDSA-signed logout_token from the dev IdP …
const logoutToken = await dev.mintLogoutToken({
clientId: "my-brandapp",
sessionId: "idp-session-1",
userId: "user-1",
// issuedAtMs: Date.now() - 10 * 60_000, // backdate to test the max-age branch
});
// … then deliver it the way reopt does: form-encoded POST.
const res = await handler(
new Request("http://localhost/api/auth/backchannel-logout", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ logout_token: logoutToken }),
}),
);
// res.status === 200 — the local sessions for sid "idp-session-1" are revoked.
// Requires Ed25519 Web Crypto (Node 20+). Where it's unavailable, discovery
// omits JWKS/EdDSA/end-session (reduced-but-coherent) instead of faking them.Proxy Mode
Forward selected modules to the real Reopt server while keeping the rest in-memory. Here AI routes hit the real server (so you get real completions and credits) while EAV, CMS, Files, and Auth stay offline.
import { createDevServer } from "@reopt-ai/brandapp-sdk/dev";
import schema from "./eav.schema";
const devServer = await createDevServer({
schema,
proxy: {
// Where "passthrough" routes are forwarded
remoteUrl: "https://brand.local.reopt.io:3443",
// Credentials used for the proxied (real) calls
remoteCredentials: {
clientId: process.env.BRANDAPP_CLIENT_ID!,
clientSecret: process.env.BRANDAPP_CLIENT_SECRET!,
brandappId: process.env.BRANDAPP_ID!,
},
// Per-module strategy: "passthrough" hits the real server, "local" stays in-memory
routes: {
ai: "passthrough", // real AI completions / credits
eav: "local",
cms: "local",
files: "local",
auth: "local",
},
},
});
console.log("Dev server (AI proxied):", devServer.url);