v2.0.0-next.16
Pre-release@baeta/auth@2.0.0-next.16
Major Changes
-
The auth package was renamed from
@baeta/extension-authto@baeta/authand rewritten. The whole authorization model changed: by @andreisergiu98 in #290- Setup is now an app plugin (
createAuth(...)returnsauthAppPlugin) instead of an extension registered viacreateExtensions(...). - Scopes and grants are generic type parameters on
createAuth, not a globaldeclare global { namespace AuthExtension { ... } }augmentation. - Rules are built with a typed
scopeaccessor andrulecombinators (rule.and/rule.or/rule.chain/rule.race) instead of plain nested objects with$or/$and/$grantedkeys. - Rules are applied by chaining
.$use(auth(...))on the field/type/module builder instead of the side-effecting$auth(...)/$postAuth(...)methods.$postAuthis nowauthAfter. - Grants now attach to the result object's identity (a
WeakMap), with support for arrays and customtargetfunctions, instead of being keyed by resolver path. - New per-request scope caching with an optional
cacheKeyMapfor non-serializable scope arguments.
1. Setup: extension -> app plugin
Scopes and grants used to be declared by augmenting a global
AuthExtensionnamespace and the extension was created withauthExtension(...)then registered throughcreateExtensions(...).v1:
// src/extensions/auth-extension.ts import { authExtension } from "@baeta/extension-auth"; import { UnauthenticatedError } from "@baeta/errors"; import type { Context } from "../types/context.ts"; declare global { export namespace AuthExtension { export interface Scopes { isPublic: boolean; isLoggedIn: boolean; hasAccess: "guest" | "user" | "admin"; } export interface GrantsMap { readUserPhotos: boolean; } } } export const authExt = authExtension<Context>( async (ctx) => ({ isPublic: true, isLoggedIn: async () => { if (ctx.userId == null) throw new UnauthenticatedError(); return true; }, hasAccess: (access: string) => ["guest", "user"].includes(access), }), { defaultScopes: { Query: { isLoggedIn: true }, Mutation: { isLoggedIn: true }, Subscription: { subscribe: { isLoggedIn: true } }, }, } ); // src/extensions/index.ts import { createExtensions } from "@baeta/core"; import { authExt } from "./auth-extension.ts"; export default createExtensions(authExt);
v2: scopes/grants are type parameters, and
createAuthreturns the building blocks directly.defaultScopesis now a callback that hands you the typedscopeandrulebuilders, and theSubscriptiondefault is flat (nosubscribewrapper).// src/lib/auth.ts import { createAuth } from "@baeta/auth"; import { UnauthenticatedError } from "@baeta/errors"; import type { Context } from "../types/context.ts"; export type Scopes = { isPublic: boolean; isLoggedIn: boolean; hasAccess: "guest" | "user" | "admin"; }; export type Grants = "readUserPhotos"; export const { auth, authAfter, authAppPlugin, rule, scope } = createAuth< Context, Scopes, Grants >( async (ctx) => ({ isPublic: true, isLoggedIn: async () => { if (ctx.userId == null) throw new UnauthenticatedError(); return true; }, hasAccess: (access) => ["guest", "user"].includes(access), }), { defaultScopes: ({ scope }) => ({ Query: scope.isLoggedIn, Mutation: scope.isLoggedIn, Subscription: scope.isLoggedIn, }), } );
Register the returned
authAppPluginon the application (extensions are gone):// v2 — src/app.ts import { createApplication } from "@baeta/core"; import { authAppPlugin } from "./lib/auth.ts"; import modules from "./modules/index.ts"; const baeta = createApplication({ modules, plugins: [authAppPlugin], });
If a module calls
auth(...)/authAfter(...)butauthAppPluginisn't registered, Baeta throws at schema build time.2. Building rules: typed
scope+ruleinstead of object literalsScope rules were plain objects keyed by scope name, with
$and/$or/$chain/$raceand$grantedas special keys. They're now constructed with the typedscopeaccessor andrulecombinators.scope.isLoggedIn— boolean scope (read as a property)scope.hasAccess('admin')— parameterized scope (called with its typed argument)scope.$granted('readUserPhotos')— granted-permission checkrule.and(...)/rule.or(...)/rule.chain(...)/rule.race(...)— combinators (two or more rules each)
3. Applying a rule to a resolver:
$use(auth(...))instead of$auth(...)v1 called the side-effecting
$auth(pre-resolution) /$postAuth(post-resolution) methods. v2 chains.$use(auth(...))/.$use(authAfter(...))on the builder.authandauthAftertake the same(rules, options?)shape, whereoptionsstill carriesgrantsandskipDefaults.v1:
import { ForbiddenError } from "@baeta/errors"; import { db } from "../../lib/db/prisma.ts"; import { getUserModule } from "./typedef.ts"; const { Query, Mutation } = getUserModule(); Query.user.$auth( { $or: { isPublic: true, isLoggedIn: true } }, { skipDefaults: true, grants: ["readUserPhotos"] } ); // Admin-only Mutation.createUser.$auth({ hasAccess: "admin" }); // Dynamic: return true / false / a scope object Mutation.updateUser.$auth(async (params) => { const user = await db.user.findFirst({ where: params.args.where }); if (user && user.id === params.ctx.userId) return true; if (!user) throw new ForbiddenError(); return { hasAccess: "admin" }; });
v2:
import { auth, rule, scope } from "../../lib/auth.ts"; import { db } from "../../lib/db/prisma.ts"; import { UserModule } from "./typedef.ts"; const { Query, Mutation } = UserModule; const userQuery = Query.user .$use( auth(rule.or(scope.isPublic, scope.isLoggedIn), { skipDefaults: true, grants: ["readUserPhotos"], }) ) .resolve(({ args }) => db.user.findFirst({ where: args.where })); // Admin-only const createUser = Mutation.createUser .$use(auth(scope.hasAccess("admin"))) .resolve(({ args }) => db.user.create({ data: args.data })); // Dynamic: return true / false / a ScopeRules value const updateUser = Mutation.updateUser .$use( auth(async ({ args, ctx }) => { const user = await db.user.findFirst({ where: args.where }); if (user && user.id === ctx.userId) return true; return scope.hasAccess("admin"); }) ) .resolve(/* ... */);
auth(...)chains at the type level (Type.$use(auth(...)).$fields({...})) and module level (Module.$use(auth(...)).$schema({...})) too. For subscriptions, each.$use(auth(...))only protects the phase that follows it (subscribe vs resolve).Post-resolution checks move from
$postAuthtoauthAfter(it cannot be used on mutations, since it runs after the resolver):// v1 Query.user.$postAuth((params, user) => user.id === params.ctx.userId ? true : { hasAccess: "admin" } ); // v2 const userQuery = Query.user .$use( authAfter((params, user) => user && user.id === params.ctx.userId ? true : scope.hasAccess("admin") ) ) .resolve(({ args }) => db.user.findFirst({ where: args.where }));
4. Grants
Grants are attached the same way (via the
grantsoption) and consumed via$granted, but the internal model changed: v1 keyed grants by the resolver path; v2 keys them by the result object's identity (aWeakMap). For array results the grant attaches to each element, and you can redirect a grant to a nested value with aGrantConfig{ grant, target }.v1 (consume):
User.photos.$auth({ $granted: "readUserPhotos" });
v2 (consume):
const userPhotosResolver = User.photos .$use(auth(scope.$granted("readUserPhotos"))) .resolve(({ source }) => db.userPhoto.findMany({ where: { userId: source.id } }) );
Because grants now key off the result object, a child resolver only sees the grant when its
params.sourceis the exact object the granting resolver returned. New in v2, you can target a nested object:auth(scope.isLoggedIn, { grants: { grant: "readUserPhotos", target: (entry) => entry.user }, });
5. Scope caching (new)
Within a single request each scope-with-argument runs at most once; the cache is per-context. Serializable arguments (primitives, plain objects, arrays of those) are auto-keyed; anything else falls back to reference identity. Provide a
cacheKeyMapto give non-serializable arguments (class instances,Date, etc.) a stable key:createAuth<Context, Scopes, Grants>(loader, { cacheKeyMap: { canAccessProject: (project) => project.id, }, });
Error handling is unchanged in shape:
createAuthstill accepts anerrorResolver, andaggregateErrorResolver/ScopeErrorResolverare still exported. - Setup is now an app plugin (
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c,046dc5c]:- @baeta/core@2.0.0-next.16
- @baeta/errors@2.0.0-next.16
- @baeta/util-log@2.0.0-next.5
@baeta/cache@2.0.0-next.16
Major Changes
-
The cache system has been fully rewritten and split out of the GraphQL framework into a standalone
@baeta/cachepackage. It no longer depends on@baeta/coreorgraphqland can be used on its own. by @andreisergiu98 in #290The biggest conceptual change: the old GraphQL extension API is gone. There is no more
cacheExtension(...),$createCache,$useCache,$cacheRef,$cacheRevision, or$cacheClear. You no longer register a cache as a Baeta extension. Instead you build a cache object directly withcreateCache(client, options), declare cached queries withdefineQuery, and wire it into resolvers with the normal resolver builder (.map()/.resolve()) — the cache is just a library you call.Building a cache
In v1 the storage adapter wrapped a client and you created per-type caches off a GraphQL type via the extension:
// v1 import { cacheExtension } from "@baeta/extension-cache"; import { RedisStore } from "@baeta/extension-cache-redis"; import Redis from "ioredis"; const redis = new Redis("redis://localhost:6379"); const redisStore = new RedisStore(redis); export const cacheExt = cacheExtension(redisStore, { ttl: 3600 }); // in a module const userCache = User.$createCache({ revision: 2 });
In v2 you pass a
CacheClient(the adapter) directly tocreateCache, give the cache aname, and provideparse/serialize.parse/serializeare now required, which lets you validate cached values on read (e.g. with Zod). TTL is now in milliseconds (ttlMs) instead of seconds (ttl):// v2 import { createCache, defineQuery } from "@baeta/cache"; import { RedisCacheClient } from "@baeta/cache-ioredis"; import Redis from "ioredis"; import { z } from "zod"; const redis = new Redis("redis://localhost:6379"); const redisClient = new RedisCacheClient(redis); const UserSchema = z.object({ id: z.string(), email: z.string(), name: z.string(), }); export const userCache = createCache(redisClient, { name: "UserCache", revision: 2, ttlMs: 60 * 60 * 1000, // defaults to 1 hour parse: (value) => UserSchema.parse(JSON.parse(value)), serialize: (value) => JSON.stringify(value), }) .withQueries({ findUser: defineQuery({ resolve: async (args: { id: string | null; email: string | null }) => { return db.user.findUnique({ where: { id: args.id ?? undefined, email: args.email ?? undefined }, }); }, }), findUsers: defineQuery({ resolve: async () => db.user.findMany(), }), }) .build();
createCache(...)returns a factory: call.build()for an item-only cache, or.withQueries({...}).build()to attach typed, cached queries. By defaultgetRefextracts theidfield; passgetReffor items keyed differently.Reading through the cache
$useCache(...)as a resolver middleware is gone. A cached query is just an async function (userCache.queries.findUser(args)) that you call from a normal resolver. The resolver builder's.map()turns resolver params into query args:// v1 Query.user.$useCache(userCache); // v2 export default Query.$fields({ user: Query.user.map(({ args }) => userCache.queries.findUser({ id: args.where.id, email: args.where.email, }) ), users: Query.users .map(() => userCache.queries.findUsers({})) .map(({ source }) => source ?? []), });
Reconciling on mutations
The single
save()method is replaced by explicitinsert()(new items),update()(existing items) anddelete(). These give the cache enough information to reconcile every related query automatically, instead of manually clearing query results:// v1 Mutation.updateUser.$use(async (params, next) => { const user = await next(); await userCache.save(user); return user; }); // v2 const createUserMutation = Mutation.createUser.resolve(async ({ args }) => { const user = await db.user.create({ data: args.data }); await userCache.insert(user); // new item return user; }); const updateUserMutation = Mutation.updateUser .$use(async (next) => { const user = await next(); if (user) { await userCache.update(user); // existing item — updates all queries } return user; }) .resolve(({ args }) => db.user.update({ where: { id: args.where.id }, data: args.data }) );
Targeted invalidation with
indexArgsByRelationship queries ("all photos for user X") can declare
indexArgsByso invalidation is surgical.defineQueryalso accepts per-queryonInsert/onUpdate/onDeletehooks, which receivehelpers(invalidateAll,invalidateByArgs) to invalidate only the matching index keys:// v2 export const userPhotoCache = createCache(redisClient, { name: "UserPhotoCache", parse: (value) => JSON.parse(value), serialize: (value) => JSON.stringify(value), }) .withQueries({ findUserPhotos: defineQuery({ resolve: async (args: { userId: string }) => db.userPhoto.findMany({ where: { userId: args.userId } }), indexArgsBy: { userId: true }, onInsert(items, helpers) { return helpers.invalidateByArgs( items.map((item) => ({ userId: item.userId })) ); }, onDelete(pairs, helpers) { return helpers.invalidateByArgs( pairs.flatMap((p) => p.previous ? [{ userId: p.previous.userId }] : [] ) ); }, }), }) .build();
Other changes
- TTL is now milliseconds (
ttlMs) everywhere, replacing v1's seconds-basedttl. - A cache must have a
name;revision,ttlMsandnamespacecan be set on the cache or defaulted from the client. - By default, re-resolving a cached query does not overwrite items already in the cache (preventing a stale query result from clobbering a concurrent mutation). Set
replaceExistingItems: trueon a query to opt back into overwriting. - Custom hooks fire asynchronously by default; pass
{ waitForHooks: true }toinsert/update/deleteto await them.
See the caching guide for an end-to-end setup.
- TTL is now milliseconds (
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/util-encoding@2.0.0-next.5
- @baeta/util-log@2.0.0-next.5
@baeta/cache-cloudflare@2.0.0-next.16
Major Changes
-
The Cloudflare adapter was renamed from
@baeta/extension-cache-cloudflareto@baeta/cache-cloudflareand rebuilt for the decoupled v2 cache API. It exportsCloudflareCacheClient(extending theCacheClientbase class from@baeta/cache) plus theBaetaCacheDurable Object, replacing the v1CloudflareStoreAdapterintegration. by @andreisergiu98 in #290// v1 import { cacheExtension } from "@baeta/extension-cache"; import { CloudflareCacheClient } from "@baeta/extension-cache-cloudflare"; // v2 import { createCache } from "@baeta/cache"; import { CloudflareCacheClient, BaetaCache } from "@baeta/cache-cloudflare"; export const cloudflareClient = new CloudflareCacheClient(env.BAETA_CACHE); // pass cloudflareClient to createCache(cloudflareClient, { ... })
CloudflareCacheClientis backed by theBaetaCacheDurable Object and is intended for Cloudflare Workers deployments. -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
@baeta/cache-ioredis@2.0.0-next.16
Major Changes
-
The ioredis adapter was renamed from
@baeta/extension-cache-redisto@baeta/cache-ioredisand rebuilt for the decoupled v2 cache API. Instead of the oldRedisStore(a BaetaStore), it now exports aRedisCacheClientthat extends theCacheClientbase class from@baeta/cacheand is passed directly tocreateCache. by @andreisergiu98 in #290// v1 import { cacheExtension } from "@baeta/extension-cache"; import { RedisStore } from "@baeta/extension-cache-redis"; import Redis from "ioredis"; const redisStore = new RedisStore(new Redis("redis://localhost:6379")); export const cacheExt = cacheExtension(redisStore, { ttl: 3600 }); // v2 import { createCache } from "@baeta/cache"; import { RedisCacheClient } from "@baeta/cache-ioredis"; import Redis from "ioredis"; export const redisClient = new RedisCacheClient( new Redis("redis://localhost:6379") ); // pass redisClient to createCache(redisClient, { ... })
RedisCacheClientaccepts both a standaloneRedisand aClusterconnection, and exposes pipeline-batching tuning options (maxPipelineSizeLimit,maxPipelineCommandLimit,maxCommandKeysLimit) plus the sharedCacheClientOptions(ttlMs,revision,namespace). -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/cache-redis-common@2.0.0-next.14
- @baeta/cache@2.0.0-next.16
@baeta/cache-iovalkey@2.0.0-next.16
Major Changes
-
@baeta/cache-iovalkeyis a new cache adapter for the Valkey key-value store (the open-source Redis fork), using the iovalkey client. It extends theCacheClientbase class from@baeta/cacheand is a drop-in alternative to the ioredis adapter. by @andreisergiu98 in #290import { createCache } from "@baeta/cache"; import { ValkeyCacheClient } from "@baeta/cache-iovalkey"; import Valkey from "iovalkey"; export const valkeyClient = new ValkeyCacheClient( new Valkey("valkey://localhost:6379") ); // pass valkeyClient to createCache(valkeyClient, { ... })
ValkeyCacheClientaccepts both standalone and cluster connections and shares the same pipeline-batching tuning options andCacheClientOptionsas the ioredis adapter. -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/cache-redis-common@2.0.0-next.14
- @baeta/cache@2.0.0-next.16
@baeta/cache-redis-common@2.0.0-next.14
Major Changes
-
@baeta/cache-redis-commonis a new package holding the Redis/Valkey building blocks shared by@baeta/cache-ioredisand@baeta/cache-iovalkey— pipeline batching helpers (assertNoPipelineErrors,batchPipeline) and the cached Lua script loader (createRedisScripts,RedisScripts). Most applications depend on a concrete adapter rather than on this package directly. by @andreisergiu98 in #290 -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
@baeta/cache-upstash@2.0.0-next.16
Major Changes
-
The Upstash adapter was renamed from
@baeta/extension-cache-upstashto@baeta/cache-upstashand rebuilt for the decoupled v2 cache API. It now exports anUpstashCacheClient(extending theCacheClientbase class from@baeta/cache) that you pass directly tocreateCache, replacing the v1UpstashStore/UpstashClientpair. by @andreisergiu98 in #290// v1 import { cacheExtension } from "@baeta/extension-cache"; import { UpstashClient, UpstashStore } from "@baeta/extension-cache-upstash"; const store = new UpstashStore(new UpstashClient(/* ... */)); export const cacheExt = cacheExtension(store, { ttl: 3600 }); // v2 import { createCache } from "@baeta/cache"; import { UpstashCacheClient } from "@baeta/cache-upstash"; export const upstashClient = new UpstashCacheClient({ url: "UPSTASH_REDIS_URL", token: "UPSTASH_REDIS_TOKEN", }); // pass upstashClient to createCache(upstashClient, { ... })
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/cache-redis-common@2.0.0-next.14
- @baeta/cache@2.0.0-next.16
@baeta/cli@2.0.0-next.16
Major Changes
-
The CLI no longer compiles or bundles your app. It now does one thing — code generation — and assumes your runtime executes TypeScript directly (Node
--experimental-strip-types/node:type-stripping, Bun, or Deno). by @andreisergiu98 in #290baeta buildremovedThe entire
buildcommand is gone, along with its--generate,--onSuccess, and--onErrorflags. Bundling lived in@baeta/compiler, which has been removed in v2. Run your app's source directly instead of building todist/.The only command is now
generate(aliasg), which is also implied as the default workflow. Its flags are--watch/-wand--run/-r. (The v1generatecommand's--skipInitial/-sflag was also removed.)--runalready existed in v1'sgeneratecommand, but it is now the way to run your app: Baeta launches the--runcommand once as a long-lived process and only respawns it if it exits — live reload comes fromnode --watch(orbun --watch/deno --watch) inside the--runcommand, which restarts when your source and the regenerated typedefs change.v1
package.json:{ "scripts": { "build": "baeta build --generate", "generate": "baeta generate", "start": "baeta build --watch --generate --onSuccess='node --enable-source-maps dist/app.js'" } }v2
package.json:{ "scripts": { "build": "baeta generate", "start": "baeta generate --watch --run='node --watch --inspect src/app.ts'", "types": "tsc --noEmit" } }Note the
--runtarget is nowsrc/app.ts(source) rather thandist/app.js(build output).Bun and Deno use the same shape, swapping the runtime inside
--run:{ "start:bun": "baeta generate --watch --run='bun --watch --inspect src/app.ts'", "start:deno": "baeta generate --watch --run='deno --watch --inspect --allow-net --allow-read --allow-env src/app.ts'" }Config loading is now a native import
In v1, a
baeta.tsconfig was transpiled on the fly with esbuild via the optional@baeta/compilerpeer dependency. In v2 the config file is loaded with a nativeimport(), so it must be valid ESM your runtime can execute directly (.ts,.mts,.js, or.mjs).@baeta/compileris no longer a peer dependency.defineConfigis now required: it stamps aversion: 'v2'marker that the CLI validates on load. A bare config object (or a v1-style config) is rejected.import { defineConfig } from "@baeta/cli"; export default defineConfig({ graphql: { schemas: ["src/**/*.gql"], }, });
The
compilerfield onBaetaOptionshas also been removed, since there is no build step to configure../inkexport removedThe
@baeta/cli/inksubpath export was dropped. -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
- Fix tsdown stripping binary import by @andreisergiu98 in #561
- Updated dependencies [
046dc5c,046dc5c,046dc5c,046dc5c]:- @baeta/generator@2.0.0-next.7
- @baeta/plugin-graphql@2.0.0-next.16
- @baeta/util-path@2.0.0-next.6
@baeta/complexity@2.0.0-next.16
Major Changes
-
The complexity extension has been renamed to
@baeta/complexityand rebuilt around the v2 app-plugin and builder APIs. ThecomplexityExtension()factory and the magic.$complexity()resolver method are gone. You now callcreateComplexity()to get back two things — acomplexity(...)rule helper applied via the generic.$use(...)builder method, and acomplexityAppPluginregistered oncreateApplication. by @andreisergiu98 in #290The scoring options (
defaultComplexity,defaultListMultiplier,limit,complexityError) and the per-field settings shape ({ complexity, multiplier }, orfalseto disable) are unchanged.Setup
v1 — an extension factory, registered through the extensions API:
// src/modules/extensions.ts import { complexityExtension } from "@baeta/extension-complexity"; import type { Context } from "../context.ts"; export const complexity = complexityExtension<Context>({ defaultComplexity: 1, defaultListMultiplier: 10, limit: { depth: 10, breadth: 50, complexity: 1000 }, }); // createExtensions(complexity, ...)
v2 —
createComplexity()returns a rule helper plus an app plugin:// src/lib/complexity.ts import { createComplexity } from "@baeta/complexity"; import type { Context } from "../types/context.ts"; export const { complexity, complexityAppPlugin } = createComplexity<Context>({ defaultComplexity: 1, defaultListMultiplier: 10, // static limits, or a (ctx) => limits function for dynamic limits async limit(ctx) { return { depth: 10, breadth: 50, complexity: 1000 }; }, });
Register the plugin on the application:
// src/app.ts import { createApplication } from "@baeta/core"; import { complexityAppPlugin } from "./lib/complexity.ts"; import modules from "./modules/index.ts"; const baeta = createApplication({ modules, plugins: [complexityAppPlugin], });
Using
complexity(...)in a module without registeringcomplexityAppPluginis now a build-time error rather than a silent no-op.Applying rules to fields
v1 — a dedicated
.$complexity()method on resolvers, types, and subscriptions, called for its side effect:// Per-field override Query.users.$complexity(({ args }) => ({ complexity: 1, multiplier: 5 })); // Disable for a field Query.simple.$complexity(() => false); // Whole type User.$complexity(() => ({ complexity: 2, multiplier: 5 }));
v2 — the
complexity(...)helper is passed to the generic.$use(...)builder method and chained into.resolve()/.$fields():import { complexity } from "../../lib/complexity.ts"; import { UserModule } from "./typedef.ts"; const { Query, User } = UserModule; // Per-field override on a list field const usersQuery = Query.users .$use(complexity(({ args, ctx }) => ({ complexity: 1, multiplier: 5 }))) .resolve(() => findUsers()); // Disable complexity for a specific field const userQuery = Query.user .$use(complexity(() => false)) .resolve(({ args }) => findUser(args.where)); // Whole type — chain into $fields const userResolver = User.$use(complexity(() => ({ complexity: 2 }))).$fields( { id: User.id.key("id"), email: User.email.key("email"), } );
The settings function still receives
{ args, ctx }and returns{ complexity?, multiplier? }orfalse.Package rename
v1 v2 @baeta/extension-complexity@baeta/complexityThe error exports (
ComplexityError,ComplexityErrorCode,ComplexityErrorKind,GetComplexityError) and the option/type exports (ComplexityExtensionOptions,ComplexityLimit,GetComplexityLimit,FieldSettings,GetFieldSettings,GetFieldSettingsArgs) are unchanged. -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
@baeta/core@2.0.0-next.16
Major Changes
-
@baeta/corewas rewritten around a composable, side-effect-free builder API. The old functional approach — where callingQuery.user(fn)registered a resolver as a side effect and you imported resolver files for their side effects — is gone. You now build each field with chainable helpers, collect them with$fields, and export a module schema with$schema. Type generation no longer mutates anything at import time, so a missing resolver is a compile-time error instead of a runtime surprise. by @andreisergiu98 in #290Resolver API: side-effect functions -> composable builders
Every field is now declared with chainable helpers off the generated module and assembled with
$fields/$schema. You must explicitly define a resolver for every field; an unresolved field fails type-checking when you pass the field map into$fields({…}).v1:
// src/modules/user/resolvers.ts import { getUserModule } from "./typedef"; const { Query, User } = getUserModule(); // Calling the field registers the resolver as a side effect User.name((params) => `${params.root.firstName} ${params.root.lastName}`); Query.user(async (params) => { return db.user.findUnique({ where: params.args.where }); }); // src/modules/user/index.ts import "./resolvers"; // imported for its side effects import { getUserModule } from "./typedef"; export const userModule = getUserModule();
v2:
// src/modules/user/user.type.ts import { UserModule } from "./typedef.ts"; const { User } = UserModule; export default User.$fields({ id: User.id.key("id"), email: User.email.key("email"), name: User.name.resolve( ({ source }) => `${source.firstName} ${source.lastName}` ), }); // src/modules/user/user.queries.ts import { UserModule } from "./typedef.ts"; const { Query } = UserModule; export default Query.$fields({ user: Query.user.resolve(({ args }) => db.user.findFirst({ where: args.where }) ), }); // src/modules/user/index.ts import { UserModule } from "./typedef.ts"; import queryResolver from "./user.queries.ts"; import userResolver from "./user.type.ts"; export default UserModule.$schema({ User: userResolver, Query: queryResolver, });
Field helpers (pick the strictest one that fits):
.key("field")— read the value straight off the source object.resolve(fn)— custom resolver; return type must match the schema field.map(fn)— same shape as.resolvebut accepts any return type (for chained transforms).to(fn)— transform whatever the previous step produced.withDefault(value)— substitutevaluewhen the result isnull/undefined.undefinedAsNull()— mapundefinedtonullfor nullable fields
Helpers compose, e.g.
User.birthDate.key('birthDate').to(toDate)orUser.friends.map(loadFriends).withDefault([]).The resolver/middleware
paramsobject also renamedroottosource({ source, args, ctx, info }).Middleware API: parameter order flipped, chained before
.resolve()Middlewares are attached by chaining
.$use(...)on a field and finishing with.resolve()(or.map()), then passing the result into$fields. The parameter order changed from(params, next)to(next, params).v1:
Mutation.updateUser.$use(async ({ args, ctx }, next) => { const user = await next(); ctx.pubsub.publish("user-updated", user); return user; }); Mutation.updateUser((params) => db.user.update(/* … */));
v2:
const updateUser = Mutation.updateUser .$use(async (next, { ctx }) => { const user = await next(); if (user) ctx.pubsub.publish("user-updated", user); return user; }) .resolve(({ args }) => db.user.update(/* … */)); export default Mutation.$fields({ updateUser });
Middlewares can also be applied at the type level (
Type.$use(...).$fields(...)) and at the module level (Module.$use(...).$schema(...)). Execution order is module -> type -> field, outside-in.Module registration and auto-import are now built-in
Baeta auto-generates
src/modules/index.ts, which collects every module (each module default-exports its schema via$schema()). You import that aggregate and hand it tocreateApplication. There is no more manual array of modules and no@baeta/plugin-autoload.v1:
import { createApplication } from "@baeta/core"; import { userModule } from "./modules/user"; import { postModule } from "./modules/post"; const baeta = createApplication({ modules: [userModule, postModule], });
v2:
import { createApplication } from "@baeta/core"; import modules from "./modules/index.ts"; // auto-generated const baeta = createApplication({ modules });
createApplicationoptions also changed: thepruneSchemaoption was removed (the schema is now built from explicit per-field resolvers), and two new options were added —plugins(app plugins, see below) andbuildSchema(override how the executable schema is built).Context and custom types moved to a generated
src/modules/types.tsThe
graphql.contextTypesetting inbaeta.tsis gone. Source types, scalar mappings, the context type, and theinfotype are now configured in one generated file,src/modules/types.ts, via theObjectTypes,Scalars,Ctx, andInfoexports.v1 (
baeta.ts):export default defineConfig({ graphql: { schemas: ["src/**/*.gql"], contextType: "src/context#Context", }, });
v2 (
src/modules/types.ts):import type { GraphQLResolveInfo } from "graphql"; import type { BaseObjectTypes, BaseScalars, } from "../__generated__/utility.ts"; import type { User } from "../lib/db/prisma.ts"; import type { Context } from "../types/context.ts"; export interface Scalars extends BaseScalars { DateTime: Date; } export interface ObjectTypes extends BaseObjectTypes { User: User; // override the source type for the GraphQL `User` type } export type Ctx = Context; export type Info = GraphQLResolveInfo;
App plugins replace extensions
The
extensionsconcept andcreateExtensions({...})were removed entirely. Cross-cutting features (auth, cache, etc.) now ship as app plugins that you pass tocreateApplicationthrough the newpluginsoption. A plugin is created by its feature package (for examplecreateAuth(...)returns anauthAppPlugin) and registered once on the application — it mutates the relevant modules at build time, and the app throws if a module relies on a plugin that was not registered.v1:
// src/modules/extensions.ts — discovered via baeta.ts import { createExtensions } from "@baeta/core"; import { authExt } from "./auth-extension.ts"; import { cacheExt } from "./cache-extension.ts"; export default createExtensions(authExt, cacheExt);
v2:
// src/lib/auth.ts import { createAuth } from "@baeta/auth"; export const { auth, authAppPlugin, rule, scope } = createAuth< Context, Scopes, Grants >(/* … */); // src/app.ts import { createApplication } from "@baeta/core"; import { authAppPlugin } from "./lib/auth.ts"; import modules from "./modules/index.ts"; const baeta = createApplication({ modules, plugins: [authAppPlugin], });
Removed packages relevant to core users
@baeta/compiler— modern runtimes (Node, Bun, Deno) execute TypeScript natively, so there is no separate compile/build step.baeta buildis gone; run your TS entry point directly or use your own bundler.@baeta/plugin-autoload— module auto-importing is now built in via the generatedsrc/modules/index.ts.
Other notes
createContextStoreWithLoaderwas added alongsidecreateContextStorefor context stores that resolve a value lazily.
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
@baeta/directives@2.0.0-next.16
Major Changes
-
@baeta/directivesstill ships the same catalog of built-in input/object directives (@validString,@validInt,@validFloat,@constraints,@trim,@lower,@upper), but the way directives are applied to a module changed along with the new side-effect-free builder API. by @andreisergiu98 in #290How directives are registered
In v1 a module came from a
getXModule()factory and$directivewas a void side-effect method that pushed a schema transformer onto the module. In v2 the module is a direct export and$directive()is an immutable method you chain before$schema()—Module.$directive(transformer).$schema({ ... })— accepting a single transformer or an array.@validStringformatargument removedThe
format: EMAIL | UUID | URLargument (and the generatedStringFormatenum) was dropped from@validString, along with itsemail-validatorandis-urldependencies. -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/core@2.0.0-next.16
- @baeta/errors@2.0.0-next.16
@baeta/env@2.0.0-next.16
Major Changes
-
The public API is unchanged —
createEnvParser, theparsefunction it returns, and theEnvOptions/EnvTypes/EnvInferTypetypes all keep the same shape. What changed is how raw values are coerced and validated, which can surface as new errors (or different results) for inputs that v1 accepted silently. by @andreisergiu98 in #290- Numbers are validated. v1 ran every value through
Number(value), so a non-numeric env var producedNaNwithout complaint. v2 requires the value to match a decimal-number pattern and to be finite, throwing a descriptive error otherwise. - Booleans accept more spellings. v1 treated only the literal string
'true'astrueand everything else (including'1','yes', typos, etc.) asfalse. v2 acceptstrue/1/yes/onastrueandfalse/0/no/offasfalse(case-insensitive, trimmed), and throws on any unrecognised value instead of silently returningfalse. - Empty strings fall back to the default. For
numberandbooleanparams, an empty-string value ('') is now treated as "not provided" and resolves tooptions.defaultinstead of being coerced.
v1:
const parse = createEnvParser((key) => process.env[key]); // PORT="abc" parse("PORT", { type: "number" }); // => NaN, no error // FLAG="1" parse("FLAG", { type: "boolean" }); // => false
v2:
const parse = createEnvParser((key) => process.env[key]); // PORT="abc" parse("PORT", { type: "number" }); // throws: not a valid decimal number // FLAG="1" parse("FLAG", { type: "boolean" }); // => true
- Numbers are validated. v1 ran every value through
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
@baeta/errors@2.0.0-next.16
Major Changes
-
BaetaErrorCodeis no longer a TypeScriptenum. It is now a plainconstobject plus a derived union type that share the same name: by @andreisergiu98 in #290v1:
export enum BaetaErrorCode { Unauthenticated = "UNAUTHENTICATED", Forbidden = "FORBIDDEN", BadUserInput = "BAD_USER_INPUT", InternalServerError = "INTERNAL_SERVER_ERROR", AggregateError = "AGGREGATE_ERROR", }
v2:
export const BaetaErrorCode = { Unauthenticated: "UNAUTHENTICATED", Forbidden: "FORBIDDEN", BadUserInput: "BAD_USER_INPUT", InternalServerError: "INTERNAL_SERVER_ERROR", AggregateError: "AGGREGATE_ERROR", } as const; export type BaetaErrorCode = (typeof BaetaErrorCode)[keyof typeof BaetaErrorCode];
Value access is unchanged (
BaetaErrorCode.Forbidden === 'FORBIDDEN'), andBaetaErrorCodestill works as a type (it now resolves to the union of code strings). Code that relied on enum-only behavior must be updated: using a single member as a type (BaetaErrorCode.Forbiddenin type position) and enum reverse-mapping (BaetaErrorCode['FORBIDDEN']) are no longer available. The error classes (UnauthenticatedError,ForbiddenError,BadUserInput,InternalServerError,AggregateGraphQLError) and their runtime behavior are otherwise unchanged. -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
@baeta/federation@2.0.0-next.16
Major Changes
-
@baeta/federationis a new package providing the runtime helpers Baeta needs to act as an Apollo Federation subgraph. It is consumed by the code that@baeta/plugin-federationgenerates, but the helpers are public and can be used directly. by @andreisergiu98 in #290It exports two functions:
resolveEntities(representations, handlerMap, ctx, info)— implements the_entitiesresolver. It dispatches each{ __typename, ...key }representation to the matching entity handler and resolves them in parallel, throwing on an unknown or missing__typename.createFederationScalar(type, name, description?)— builds the scalars the federation spec requires.typeis'string'(forFieldSet) or'json'(for_Any).
The package is side-effect free and only declares a
graphqlpeer dependency, so it has no impact on a non-federated app.import { createFederationScalar, resolveEntities } from "@baeta/federation"; // Resolve federation entity references by __typename const entities = await resolveEntities( representations, handlersMap, ctx, info ); // Federation spec scalars const anyScalar = createFederationScalar("json", "_Any"); const fieldSetScalar = createFederationScalar("string", "FieldSet");
In practice you don't call these by hand —
@baeta/plugin-federationgenerates abaeta-federationmodule that wires them into the_entities/_serviceresolvers for you. The only code you write is the entity handlers themselves.Install it as a runtime dependency in any subgraph:
yarn add @baeta/federation
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
@baeta/generator@2.0.0-next.7
Major Changes
-
@baeta/generatordrives the generate/watch pipeline that runs Baeta's codegen plugins. It was reworked for the v2 side-effect-free generation flow. by @andreisergiu98 in #290GeneratorOptionsno longer carries the GraphQL-specific knobs that the old graphql-codegen pipeline needed:baseTypesPath,contextType,extensions, andscalarsare gone. Type/context/scalar configuration now lives in the generatedsrc/modules/types.ts. A newtypesDiroption controls where shared__generated__types are emitted (default${modulesDir}/../__generated__/).- Plugins can now mark individual generated files as non-overwriting. The runner respects
disableOverwriteso generator-authored starter files (e.g. a module'sindex.tsortypes.ts) are written once and never clobbered on regeneration.
Generation remains plugin-driven via
generate(options, plugins, hooks)/generateAndWatch(...), with the GraphQL codegen supplied by@baeta/plugin-graphql. -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
@baeta/generator-sdk@2.0.0-next.7
Major Changes
-
@baeta/generator-sdkprovides the building blocks for authoring Baeta generator plugins (File,FileManager,Watcher,createPluginV1, options). It was reworked for v2. by @andreisergiu98 in #290GeneratorOptionsdropped the graphql-codegen-era fields (baseTypesPath,contextType,extensions,scalars) and addedtypesDir. Context/scalar/type configuration now lives in the generatedsrc/modules/types.tsinstead of generator options.FileOptionsheader config changed from individual opt-out flags (disableEslintHeader,disableBiomeHeader) to a single opt-inenableLintHeaders, which acceptstrueor an object selectingeslint/oxlint/biomeV1/biomeV2headers.- New
FileOptions.disableOverwrite: files written with it use an exclusive-create open and are left untouched if they already exist on disk, so generated starter files are never overwritten. FileManager.createAndAdd(filename, content, tag, options?)now accepts per-fileFileOptionsas a fourth argument, merged over the manager's defaults.- GraphQL schema utilities (schema loading, AST registry walking) are no longer part of this SDK — they were extracted into the new
@baeta/util-graphqlpackage, so the SDK no longer pulls ingraphql/graphql-toolsfor plugin authors who don't need them.
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
Patch Changes
@baeta/plugin@2.0.0-next.5
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. by @andreisergiu98 in #290
@baeta/plugin-cloudflare@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c,046dc5c]:- @baeta/cache-cloudflare@2.0.0-next.16
- @baeta/generator-sdk@2.0.0-next.7
- @baeta/subscriptions-cloudflare@0.2.0-next.4
@baeta/plugin-directives@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
@baeta/plugin-directiveswas updated for the v2 generator API and the new side-effect-free module model. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c,046dc5c]:- @baeta/directives@2.0.0-next.16
- @baeta/generator-sdk@2.0.0-next.7
- @baeta/util-path@2.0.0-next.6
@baeta/plugin-exec@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. by @andreisergiu98 in #290
Patch Changes
@baeta/plugin-federation@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
@baeta/plugin-federationis a new build-time plugin that turns a Baeta service into a valid Apollo Federation subgraph. AddfederationPlugin()to yourbaeta.tsand the generator handles the rest of the federation boilerplate. by @andreisergiu98 in #290Setup
Install the plugin as a dev dependency and the
@baeta/federationruntime helpers as a regular dependency:yarn add -D @baeta/plugin-federation yarn add @baeta/federation
Enable it in
baeta.ts:import { defineConfig } from "@baeta/cli"; import { federationPlugin } from "@baeta/plugin-federation"; export default defineConfig({ graphql: { schemas: ["src/**/*.gql"], }, plugins: [ federationPlugin({ // Federation version to target (default: '2.9', supports 2.0–2.9) version: "2.9", // Extra directives to expose, or 'all'. // Default: ['@key', '@external', '@requires', '@provides', '@extends'] include: "all", // Name of the generated module (default: 'baeta-federation') moduleName: "baeta-federation", }), ], });
What it generates
From your existing
.gqlschema, the plugin:- Makes the federation spec directives (
@key,@external,@requires,@provides,@extends, …) available in your schema files, scoped to the configuredversionandinclude. - Generates a
baeta-federationmodule containing the_entities/_servicequery resolvers, the_Servicetype, the_Entityunion (built from every type carrying a resolvable@key), and the_Any/FieldSetscalars — using the helpers from@baeta/federation. - Emits the printed subgraph SDL exposed via
_service { sdl }. - Writes typed entity-handler signatures and representation types to
__generated__/federation.ts, so each@keyis reflected as a strongly typed handler.
Types referenced only as cross-subgraph stubs — declared with
@key(fields: "id", resolvable: false)— are intentionally excluded from the_Entityunion and from handler generation, since they are resolved by the subgraph that owns them.The one manual step: entity handlers
Given a keyed entity:
type Product @key(fields: "id") { id: ID! name: String! price: Float! }
the generator creates a
src/modules/<moduleName>/entity-handlers.tsonce (and never overwrites it) where you register a handler per entity:import type { EntityHandlerMap } from "../../__generated__/federation.ts"; import { handleProductEntity } from "../product/product.entity.ts"; const entityHandlersMap: EntityHandlerMap = { Product: handleProductEntity, }; export default entityHandlersMap;
Each handler receives the typed federation representation (the
@keyfields plus__typename) and returns the resolved entity:import type { ProductEntityHandler } from "../../__generated__/federation.ts"; export const handleProductEntity: ProductEntityHandler = async ( representation ) => { return { __typename: "Product", id: representation.id, name: `Product ${representation.id}`, price: 9.99, }; };
The generated module schema is merged into your application like any other Baeta module, so the resulting
baeta.schemais a fully federation-compatible subgraph you can serve from Apollo Server, Yoga, or any GraphQL server. See thefederation-subgraph-productsandfederation-subgraph-usersexamples for complete, runnable subgraphs. - Makes the federation spec directives (
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c,046dc5c]:- @baeta/generator-sdk@2.0.0-next.7
- @baeta/util-graphql@2.0.0-next.4
- @baeta/util-path@2.0.0-next.6
@baeta/plugin-gitignore@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
The gitignore plugin still auto-populates
.gitignorewith Baeta's generated files, but its options changed.ignoreTagswas renamed toskipTags, and a newskipFilesGlobsoption lets you exclude specific generated files by glob. Files markeddisableOverwriteare now also excluded from.gitignore. by @andreisergiu98 in #290v1:
gitignorePlugin({ ignoreTags: ["cloudflare"], });
v2:
gitignorePlugin({ skipTags: ["cloudflare"], skipFilesGlobs: ["src/modules/**/schema.ts"], });
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/generator-sdk@2.0.0-next.7
- @baeta/util-path@2.0.0-next.6
@baeta/plugin-graphql@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
The whole generator was rewritten to emit the new side-effect-free builder API (the resolver API itself is documented under
@baeta/core). by @andreisergiu98 in #290v1 (generated accessor, side-effect registration):
import { getUserModule } from "./typedef"; const { Query } = getUserModule(); Query.user(async (params) => { return { id: params.args.id ?? "id", name: "John Doe" }; });
v2 (generated module builder + starter
index.ts):import { UserModule } from "./typedef.ts"; const { Query, User } = UserModule; export default UserModule.$schema({ User: User.$fields({ id: User.id.key("id"), name: User.name.resolve((params) => { // Implement resolver logic here }), }), Query: Query.$fields({ user: Query.user.resolve((params) => { // Implement resolver logic here }), }), });
The full set of field/middleware/builder helpers is documented in the
@baeta/corechangelog entry.
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c,046dc5c]:- @baeta/generator-sdk@2.0.0-next.7
- @baeta/util-graphql@2.0.0-next.4
- @baeta/util-path@2.0.0-next.6
@baeta/plugin-pagination@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
The pagination plugin still emits the same Relay-style SDL (
Connection,Edge,PageInfo), but its generatedindex.tsnow wires up the new v2 builder-pattern resolvers instead of v1's side-effect module getter. by @andreisergiu98 in #290The
createExportoption was removed (the resolver file is always generated), and thetypesmap is now type-checked against your schema viapaginationPlugin<T>({ types }).Options change:
// v1 paginationPlugin({ types: { User: true }, createExport: false, // could opt out of the generated export file }); // v2 paginationPlugin<Types>({ types: { User: true }, // keys are now type-checked; createExport removed });
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/generator-sdk@2.0.0-next.7
- @baeta/util-path@2.0.0-next.6
@baeta/plugin-prisma@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. by @andreisergiu98 in #290
Patch Changes
- Updated dependencies [
046dc5c,046dc5c,046dc5c]:- @baeta/generator-sdk@2.0.0-next.7
- @baeta/plugin-exec@2.0.0-next.16
- @baeta/util-path@2.0.0-next.6
@baeta/subscriptions-pubsub@2.0.0-next.16
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
TypedPubSubis now a concrete class you instantiate withnew, thecreateTypedPubSub()factory has been removed, and the package targetsgraphql-subscriptionsv3 only. Support forgraphql-subscriptionsv2 (and its runtime engine detection) is gone. by @andreisergiu98 in #290Creating the PubSub
In v1,
TypedPubSubwas a type and you built the instance with thecreateTypedPubSub()factory, which detected the engine version (asyncIteratorfor v2,asyncIterableIteratorfor v3) at runtime. In v2,TypedPubSubis the class itself.v1:
import { createTypedPubSub } from "@baeta/subscriptions-pubsub"; import { PubSub } from "graphql-subscriptions"; export const pubsub = createTypedPubSub<PubSub, PubSubMap>(new PubSub());
v2:
import { TypedPubSub } from "@baeta/subscriptions-pubsub"; import { PubSub } from "graphql-subscriptions"; export const pubsub = new TypedPubSub<PubSub, PubSubMap>(new PubSub());
The optional
{ prefix }options object is unchanged and still passed as the second argument.Removed and changed exports
createTypedPubSub()— removed; usenew TypedPubSub(...).PubSubEngineV2/PubSubEngineV3— removed. The package now targets a singlegraphql-subscriptionsv3 engine.TypedPubSub— was a type alias in v1; is now the exported class.TypedPubSubOptions— unchanged.
Because v2 support is dropped, the
asyncIteratormethod (the v2-only iterator) is no longer available — useasyncIterableIterator(channel)for the async iterable.publish,subscribe, andunsubscribeare unchanged.
@baeta/util-encoding@2.0.0-next.5
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. by @andreisergiu98 in #290
@baeta/util-env@2.0.0-next.5
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. by @andreisergiu98 in #290
@baeta/util-graphql@2.0.0-next.4
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
@baeta/util-graphqlis a new package. It collects the GraphQL schema utilities that the v1 codegen kept buried inside@baeta/plugin-graphqlinto a standalone, reusable module shared by the generator and federation packages. by @andreisergiu98 in #290
Patch Changes
@baeta/util-log@2.0.0-next.5
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
createLoggeris now a public export and accepts a loglevelargument ('debug' | 'info' | 'warn' | 'error', default'info') so messages below the configured level are dropped. The exported types changed accordingly: theConsoleLoggerandConsolePayloadtype exports were removed and replaced by a singleLoggertype. The pre-builtlogexport is unchanged. by @andreisergiu98 in #290v1:
import { log, type ConsoleLogger } from "@baeta/util-log"; // createLogger was internal; no level filtering
v2:
import { log, createLogger, type Logger } from "@baeta/util-log"; const logger = createLogger("warn"); // debug/info calls become no-ops
@baeta/util-path@2.0.0-next.6
Major Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. by @andreisergiu98 in #290
create-baeta@2.0.0-next.16
Major Changes
-
create baeta(still offering theyogaandapollotemplates with Node/Bun/Deno runtime selection) now scaffolds a v2 project end to end. by @andreisergiu98 in #290 -
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290
@baeta/subscriptions-cloudflare@0.2.0-next.4
Minor Changes
-
Drop support for Node.js v23 and v25. Baeta now targets the active LTS releases — Node.js
^22.20.0,^24.0.0, or>=26.0.0. by @andreisergiu98 in #290 -
Updated for Baeta v2. The public API is unchanged, except that the
Subscribe<Map>by @andreisergiu98 in #290
helper now returns anAsyncIterableinstead of anAsyncIterator, matching the v2
subscription builder's.subscribe().