App Plugins
App plugins extend Baeta at the application level. They attach middlewares, read or set metadata across modules, and inspect the compiled schema. The built-in authorization and complexity features are themselves app plugins, and you can stack any number of them in a single app.
Registration
Pass plugins to createApplication:
import { createApplication } from "@baeta/core";
import { authAppPlugin } from "./lib/auth.ts";
import { complexityAppPlugin } from "./lib/complexity.ts";
import modules from "./modules/index.ts";
const baeta = createApplication({
modules,
plugins: [authAppPlugin, complexityAppPlugin],
});
Using a plugin helper (e.g. auth(...), complexity(...)) without registering the matching app plugin throws at startup, when createApplication builds the schema. No silent no-ops.
Plugin ordering
The order in plugins matters. Two invariants:
-
Registration order = setup order. Each plugin's setup phase runs in array order, so later plugins can read metadata set by earlier ones.
-
Earlier plugins wrap later ones at runtime. Middlewares attached by the first plugin run outside middlewares attached by later plugins. With
plugins: [A, B]and a resolverR:A pre → B pre → R → B post → A postThe first-registered plugin's pre-hook runs first; its post-hook runs last.
Choosing an order
When two plugins both gate field access, the order shapes a real tradeoff. Auth vs. complexity is the canonical example:
plugins: [authAppPlugin, complexityAppPlugin](recommended). Auth runs first; unauthenticated requests bounce before complexity runs. This avoids leaking schema shape through complexity errors and matches how most large GraphQL APIs (GitHub, Shopify, GitLab) behave.plugins: [complexityAppPlugin, authAppPlugin]. Complexity runs first; over-budget queries are rejected before auth's scope loaders run. Saves I/O on expensive-but-doomed queries, but unauthenticated callers can probe complexity errors to learn about your schema.