Shared Packages
@lattice/api-contract
One Valibot schema (plus its v.InferOutput type) per request/response concept, barrel-exported from packages/api-contract/src/index.ts. This is the single source of truth for wire shapes — the gateway validates requests and types responses against these same schemas, and the dashboard imports the same types for its proxy routes, so the two apps cannot silently drift apart on what a field is called or what type it holds.
Authorization: Bearer lattice_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Request schemas that accept client input use v.strictObject (unknown fields rejected, not dropped — see Chat Completions); response schemas use v.object. Every schema documented in API Reference and Management API is defined here — those pages describe the same shapes this package encodes as runtime-checkable Valibot schemas.
type ProviderAdapter = {
id: ProviderId;
createChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): Promise<ChatCompletionResponse>;
streamChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): AsyncGenerator<ChatCompletionChunk, void, void>;
};
type ProviderRequestContext = {
apiKey?: string;
providerModel: string;
signal: AbortSignal;
};@xcvzlabs/env
A runtime-agnostic, Standard Schema-based env var loader — published independently at github.com/xcvzlabs/env rather than living in this repo's packages/ directory, since it's generic enough to use outside Lattice too. Both apps use it with Valibot leaves, but it works with any Standard Schema-compliant validator (Zod, ArkType, or a mix). See Security and Configuration Reference for what it validates in practice.
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1755878400,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 118,
"total_tokens": 160
}
}The schema is a plain object tree, not a single validator schema: Standard Schema exposes no portable way to introspect a validator's keys across vendors, so schema is a plain nested JS object whose leaves are Standard Schema instances — not a v.object(). Grouping is owned by @xcvzlabs/env itself, as plain objects.
Naming convention: a nested group's key path maps to a SCREAMING_SNAKE_CASE env var name, camelCase segments included — { db: { host: v.string() } } reads from DB_HOST. This is why apps/gateway/server/utils/env.ts's apiKeyPepper field reads API_KEY_PEPPER, and db.host reads DB_HOST.
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"Sure"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1755878400,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":42,"completion_tokens":118,"total_tokens":160}}
data: [DONE]Source: defaults to process.env, with no Bun-, Deno-, or edge-specific APIs involved — Bun populates process.env from .env files the same way it does Bun.env, so this works unchanged for tools that load config in a plain Node subprocess (drizzle-kit, notably — see drizzle.config.ts importing this same env object).
Failure mode: validation runs once, eagerly, at import time. Any invalid or missing variable throws EnvValidationError — a typed error carrying issues: EnvIssue[] (each with envVar, path, and message) — which crashes the process at boot rather than surfacing as a runtime undefined deep in request handling.
booleanEnv: one of a handful of zero-dependency leaf schemas @xcvzlabs/env ships for coercions most validators either lack or get subtly wrong. A naive Boolean(value) coercion is a JS truthiness check, so it can never actually produce false for a set env var — any non-empty string, including the literal text "false", is truthy in JavaScript. booleanEnv only accepts the literal strings "true"/"false" and transforms them to the real boolean, which is what makes DB_SSL=false actually mean false.
Why api-contract is a separate package, not a gateway-local utility
Both the gateway and the dashboard need typed environment parsing — that's @xcvzlabs/env, an external dependency, not something to centralize locally. But the dashboard's server/api/* routes also need the exact same request/response types the gateway's route handlers validate against. Duplicating those schemas would mean the two apps could drift — a field renamed on one side without the other noticing until a request fails at runtime. Centralizing them in packages/api-contract makes that drift a compile-time TypeScript error instead.