API Freeze · v1.0
Status: frozen as of v1.0.0 (2026-08-09, extended 2026-08-11 to cover
@zebra-web/observabilityand@zebra-web/redis). This document is the authoritative record of the v1 stable API surface. Everything listed here ships with a stability promise; anything not listed here is internal and may change at any time without a major version bump.
1. Stability promise
The following is stable across 1.x releases and can be relied on by downstream consumers:
- Exports are stable. Every export listed in §3 keeps its name, its declared type/signature, and its runtime semantics for the whole
1.xline. New exports may be added (minor); removals or renames are breaking (major). - Class and interface members listed in §3 are stable. Methods and properties documented on
Zebra,GroupApi,RequestSession, and the store / middleware interfaces are part of the contract. Members not listed (including anything markedprotected/private/internal) are not. - Behavioral semantics are stable. Request routing, validation outcomes (422 prefixing, output re-validation/stripping), Problem+Json shape, status codes, cookie semantics, rate-limit header names, and observability middleware behavior behave as documented and tested. Bug fixes that change observable behavior are treated as breaking when they change documented semantics, and shipped in majors.
- Runtime requirements are stable for the
1.xline: Bun runtime, TypeScript withexperimentalDecorators+emitDecoratorMetadata, andreflect-metadataimported once at the entry point. A change to these requirements is breaking.
What is NOT covered by the freeze
- Anything exported from
src/paths other than the packageindex.ts(packages/*/src/**internals are reachable in the repo but not published API — imports like@zebra-web/core/src/...are not supported). - Types and helpers marked
Internalin their doc comments (e.g. the session package'sRequestSessionInternal, which is used by@zebra-web/session's middleware but is not part of the public index). VERSION's value: the string constant tracks the package version; its mere presence is stable, its value is not (it changes every release).- Error message text (but not error classes,
name, orstatusfields). - Diagnostic/Dev-only helpers,
consoleoutput, and logging internals.
2. Version policy (SemVer)
All packages (and the @zebra-web/zebra facade) are released in lockstep at the same version number. The policy below applies to each package and to the facade.
Requires a major (2.0)
Any change that can break a consumer who uses the API as documented:
- Signature changes: removing, renaming, reordering, or changing the type of any parameter, return type, or generic parameter of a listed export.
- Export changes: removing or renaming any listed export; changing an export from a value to a type or vice versa; changing a type-only export to require runtime support (or the reverse).
- Behavioral semantics: changing documented runtime behavior — request routing outcomes, validation behavior, status codes, cookie/header semantics, error class thrown, DI resolution or scope rules, lifecycle ordering, observability middleware semantics.
- Requirement changes: dropping Bun / decorator /
reflect-metadatasupport, or requiring a newer Bun or TypeScript than the documented minimum. - Type-leak fixes that change inference: e.g. a parameter previously typed
anynow being strictly typed, if consumers relied on the looser type.
Minor (1.x) is allowed for
- Adding new exports, overloads, optional parameters, or widening accepted input types (e.g. accepting
string | undefinedwherestringwas accepted before). - Adding new members to interfaces and classes (additive only).
- New packages published under the
@zebra-web/*scope. - Backwards-compatible additions to error classes (new fields, new subclasses).
Patch (1.x.y) is allowed for
- Bug fixes that only change behavior in cases that were previously broken (crashes, wrong-but-clearly-buggy output) and that do not change documented semantics.
- Internal refactors with no observable change.
- Documentation, typo fixes, and dependency patch updates.
Rules of thumb
- If you are unsure whether a change is breaking, it is a major.
- Internal code that is not in the frozen surface can change freely at any time (a change to
packages/*/src/**that does not touch the index exports or documented behavior is never itself a breaking change — unless it leaks into observable behavior of the frozen surface). - The facade
@zebra-web/zebrainherits the breakage rules of everything it re-exports: a breaking change in any dependency package is a breaking change of the facade, hence a major for all.
3. Frozen surface (per package)
@zebra-web/zebra (facade)
Re-exports the full surfaces of @zebra-web/core, @zebra-web/cors, and @zebra-web/session, plus rateLimit, checkLimit, createLimiter, RateLimitMemoryStore (aliased MemoryStore), and the rate-limit types IncrementResult, Limiter, RateLimitMemoryStoreOptions (aliased MemoryStoreOptions), RateLimitOptions, RateLimitResult, RateLimitStore.
Known asymmetry (documented, frozen): rate-limit's MemoryStore / MemoryStoreOptions collide with @zebra-web/session's re-exports and are aliased in the facade. Import them unprefixed from @zebra-web/rate-limit directly when needed.
@zebra-web/contract, @zebra-web/client, @zebra-web/testing, @zebra-web/observability, and @zebra-web/redis are intentionally NOT re-exported by the facade (keeps the facade tree-shakeable and dependency-light); import them from their own packages.
@zebra-web/core
- App:
Zebra, typeZebraOptions,RouteHandler,DepsSpec,ResolvedDeps,RegisteredRoute,GroupApi,LifecycleEvent,LifecycleHandler,PathParams,JoinPath,SessionOptions,ListenOptions,WsTransportOptions,validateGraph,VERSION.Zebrainstance members:use,on,once,off,emit,events,prepare,listen,stop,disposeSession,injectValue,injectSingleton,injectRequest,injectTransient,injectSession,injectFactorySingleton,injectFactoryRequest,injectFactoryTransient,injectFactorySession,implement,get,post,put,patch,delete,head,options,route,group,static,ws,dispatch,routeTable. ZebraOptions.signalHandlers?: boolean: defaults totrue.listen()installs automatic SIGINT/SIGTERM handlers unless explicitly disabled;falseleaves signal ownership to the application, which must awaitstop()and its own cleanup. User signal listeners are never removed by Zebra.- Events:
EventBus,EventEmitter(compat alias ofEventBus), typesEventHandler,EventPayload,EventArgs,Awaitable,EventPublisher,ZebraEventMap,BeforeRequestEvent,AfterRequestEvent,RequestErrorEvent,BeforeMiddlewareEvent,AfterMiddlewareEvent,MiddlewareErrorEvent. - Event-name stability: the built-in event names (
boot,ready,shutdown,before.request,after.request,request.error,before.middleware,after.middleware,middleware.error) and their payload shapes are stable for the whole1.xline. The globalZebraEvents/ZebraMiddlewareEventsinterfaces are intentionally extensible (viadeclare global) — additions are always backwards compatible; renaming/removing a built-in event or changing its payload shape is breaking (major). Lifecycle listeners freeze atlisten(); other events remain registerable at runtime. - Contract (implement):
isContractProcedure, typesContractProcedureDef,ContractHandler,ContractRequest,ContractParams,ContractQuery,ContractBody,ContractReturn,ContractProcedure,ContractRouter,ProcedureImpl,RouterImpl,ImplementOptions,Method,ErrorSpec,StandardSchemaV1. - DI:
Container,token,isToken,injectable,inject,isInjectable,getConstructorDeps,ScopeKind,scopeRank,canDependOn,Disposable,isDisposable,CircularDependencyError,UnboundTokenError,ScopeMismatchError, typesToken,Identifier,ClassConstructor,AbstractConstructor. - HTTP:
ZebraRequest,buildRequest,HttpError,ValidationError,toProblemJson,json,text,html,redirect,stream, typesProblemJson,ValidationIssue. - Middleware:
middleware,getMiddlewareDeps, typeMiddleware. - WebSocket:
wsUpgrade, typesWsHandler,WsData,WsRoute,WsUpgrade<Up>,WsUpgradeOptions(upgrade/handler surface wired toapp.ws).
Note:
head/options/routeare listed as stableZebramembers — they have been part of the routing surface since the freeze audit (C1).
@zebra-web/contract
zc, prefix, METHODS, types StandardSchemaV1, ContractProcedure, ContractProcedureDef, ContractRouter, ErrorSpec, Method, ProcedureMeta, InferParams, InferQuery, InferBody, InferOutput, PathParams, JoinPath.
@zebra-web/client
createClient, ClientError, types StandardSchemaV1, ContractProcedureDef, Method, ErrorSpec, ProblemJson, isProcedure, ClientArgs, ClientOutput, ClientProcedure, ContractClient, ClientOptions, ContractProcedure, ContractRouter.
@zebra-web/testing
createTestApp, createTestClient, type TestApp.
@zebra-web/session
sessionMiddleware, createSession, getSession, SESSION_KEY, PENDING_SET_COOKIES, MemoryStore, sign, verify, parseCookies, parseSignedCookie, serializeCookie, types SessionCookieOptions, SessionMiddleware, SessionMiddlewareOptions, SessionResolver, RequestSession, MemoryStoreOptions, SessionStore, CookieSerializeOptions.
v1.0.0 cookie semantics (pre-release hardening, 2026-08-14): the default cookie carries
HttpOnly+SameSite=Lax(SECURE_COOKIE);cookie: { preset: "plain" }restores a flag-free cookie. Error responses carry the same Set-Cookie the success path would have issued (viaPENDING_SET_COOKIES, appended by the core error middleware).
RequestSessionInternalwas removed from the public index during the C1 freeze audit (v1.0.0): it is middleware-internal and was exported by accident.
@zebra-web/cors
cors, DEFAULT_ORIGIN, matchOrigin, resolveAllowOrigin, types CorsOptions, CorsOrigin.
@zebra-web/rate-limit
rateLimit, checkLimit, createLimiter, MemoryStore, types RateLimitOptions, Limiter, RateLimitResult, IncrementResult, MemoryStoreOptions, RateLimitStore.
@zebra-web/observability (frozen 2026-08-11)
requestId, getRequestId, REQUEST_ID_KEY, accessLog, errorReporter, metrics, health, types RequestIdOptions, AccessLogEntry, AccessLogOptions, ErrorReporterInfo, MetricsOptions, LatencyHistogram, MetricsHandle, MetricsMiddleware, MetricsSnapshot, HealthOptions, Probe.
@zebra-web/redis (frozen 2026-08-11)
RedisRateLimitStore, RedisSessionStore, types RedisRateLimitStoreOptions, RedisSessionStoreOptions, RedisLike.
4. Freeze status
| Package | Version | Status |
|---|---|---|
| @zebra-web/zebra | 1.0.0 | frozen |
| @zebra-web/core | 1.0.0 | frozen |
| @zebra-web/contract | 1.0.0 | frozen |
| @zebra-web/client | 1.0.0 | frozen |
| @zebra-web/testing | 1.0.0 | frozen |
| @zebra-web/session | 1.0.0 | frozen |
| @zebra-web/cors | 1.0.0 | frozen |
| @zebra-web/rate-limit | 1.0.0 | frozen |
| @zebra-web/observability | 1.0.0 | frozen (2026-08-11) |
| @zebra-web/redis | 1.0.0 | frozen (2026-08-11) |
All packages are frozen at v1.0.0 (see §2 for the version policy). The @zebra-web/zebra facade is the only place where aliasing intentionally deviates from dependency-package names (rate-limit MemoryStore collision, §3 @zebra-web/zebra).
5. Audit record
C1 (2026-08-09)
- README / llms.txt export lists reconciled against the actual
index.tsexports of all eight packages (at the time: @zebra-web/zebra, core, contract, client, testing, session, cors, rate-limit). - Removed
RequestSessionInternalfrom@zebra-web/session's public index (internal type leak).
C5 (2026-08-11)
- Freeze extended to the two packages added by the 07-lightweight-http work:
@zebra-web/observability(middleware suite: requestId / accessLog / errorReporter / metrics / health) and@zebra-web/redis(Redis session & rate-limit stores). Export lists reconciled against theirindex.ts. head/options/routeand the response helpers (json/text/html/redirect/stream) recorded explicitly in the@zebra-web/corefrozen surface.
Additive preparation and WebSocket APIs (2026-09-08)
- Public
Zebra.prepare(): Promise<void>retains the existing idempotent and concurrent boot, DI validation and registration freeze. It opens no listener and emits no ready event;listen()reuses this same preparation. WsHandler.upgradeadditionally accepts a rejectionResponse(preserving status/body/headers) or a successfulwsUpgrade(data, { headers })control result. A symbol discriminator avoids collisions with ordinarydata/headerskeys in legacy Up objects. Existing object inference, false=401, throw=500, reserved fields and request-scope disposal remain unchanged. An explicit subprotocol must be a single valid client-offered token or the upgrade is rejected with 500; omission retains Bun's existing negotiation.ListenOptions.websocket?: WsTransportOptionsforwards onlymaxPayloadLength,idleTimeout,backpressureLimit, andcloseOnBackpressureLimit. Callbacks/data cannot be overridden and omission retains Bun defaults. Oversize closure is owned by the Bun transport: current stable Bun 1.4.2 (2026-09-08) closes with 1006 before the oversized message reaches the route handler, without increasing or disabling the configured cap. See WebSocket for observed behavior.- All additions are public through core and facade and follow the additive policy. Package versions remain at the development baseline 1.0.0; release versioning is handled by the later release process.
Additive signal ownership option (2026-09-08)
ZebraOptions.signalHandlers?: booleanis public through core and facade. Omission andtruepreserve automatic SIGINT/SIGTERM handling and its existing failure logging;falseinstalls no framework signal handlers onlisten().prepare()still installs no signal handlers.stop()retains its lifecycle ordering, concurrent/idempotent behavior and cached rejection on failure; it never removes user signal listeners. Application retry, flush, resource-close ordering and exit codes remain application policy. See Lifecycle.- This is an additive option; package versions stay at the development baseline pending the release process.