examples/new/.opencode/skills/axolotl-federation/SKILL.md
Axolotl micro-federation architecture - config, schema merging, mergeAxolotls, cross-module dependencies, best practices, and troubleshooting
npx skillsauth add aexol-studio/axolotl axolotl-federationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
schema.graphql, models.ts, axolotl.ts, and resolvers/axolotl build merges schemas into a single supergraphmodels.ts for local type safetyAll modules use graphqlYogaWithContextAdapter<AppContext>() for typed context. Only root passes a context builder.
// every module's axolotl.ts — type-only, no context builder
import { graphqlYogaWithContextAdapter } from '@aexol/axolotl-graphql-yoga';
import type { AppContext } from '@/src/context.js';
const yogaAdapter = graphqlYogaWithContextAdapter<AppContext>();
export const { createResolvers } = Axolotl(yogaAdapter)<Models, unknown>();
// root src/axolotl.ts — WITH context builder (runs once at startup)
const yogaAdapter = graphqlYogaWithContextAdapter<AppContext>(async (initial) => {
// verifyAuth here — runs per-request
return { ...initial, authUser };
});
export const { createResolvers, adapter } = Axolotl(yogaAdapter)<Models, Scalars>();
Query, Mutation, Subscription fields combined across modulesextend type — Use type DeclarationsNever use extend type. Axolotl merges by name, not SDL extension.
# ✅ CORRECT — plain type in each module
# src/modules/users/schema.graphql
type AuthorizedUserQuery {
me: User @resolver
}
# src/modules/posts/schema.graphql
type AuthorizedUserQuery {
posts: [Post!]! @resolver
}
# → merged: AuthorizedUserQuery has both `me` and `posts`
# ❌ WRONG
extend type AuthorizedUserQuery {
posts: [Post!]! @resolver
}
Applies to ALL shared types: Query, Mutation, AuthorizedUserQuery, AuthorizedUserMutation, and any shared domain types.
Modules can declare the same type name — shared fields must match exactly, unique fields are merged:
# users/schema.graphql # posts/schema.graphql
type User { type User {
_id: String! _id: String! # must match
email: String! }
} type Post { owner: User! }
# → merged User has _id + email
mergeAxolotls Behaviorimport { mergeAxolotls } from '@aexol/axolotl-core';
export default mergeAxolotls(authResolvers, usersResolvers /*, postsResolvers */);
Query.user / Mutation.user — never duplicate in domain modulesAuthorizedUserQuery / AuthorizedUserMutationcontext.authUser (set by context builder):export default createResolvers({
AuthorizedUserQuery: {
posts: async ([, , context]) => {
return prisma.post.findMany({ where: { authorId: context.authUser!._id } });
},
},
});
axolotl buildRun cd backend && npx @aexol/axolotl build after any schema/type/field change or federation config change.
Declare scalar Secret in each module schema that uses it. Map once in root axolotl.ts:
Axolotl(yogaAdapter)<Models<{ Secret: number }>, Scalars>();
See axolotl-server skill for createScalars implementation.
tools
Baseline architecture for Axolotl mobile starter (Expo Router + reusable blocks)
tools
Expo Router conventions for route groups, native headers, and starter navigation
development
i18n baseline and dev-translate setup for Expo mobile starter
development
Starter data layer pattern with React Query + Zeus for Expo app