examples/new/.opencode/skills/axolotl-server/SKILL.md
Axolotl server setup, axolotl.ts initialization, custom context, scalars, directives, and adapter configuration
npx skillsauth add aexol-studio/axolotl axolotl-serverInstall 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.
// Root src/axolotl.ts — passes context builder function
const yogaAdapter = graphqlYogaWithContextAdapter<AppContext>(async (initial) => {
// ... auth verification, setCookie/clearCookie closures
return { ...initial, authUser, setCookie, clearCookie };
});
export const { createResolvers, createDirectives, applyMiddleware, adapter } = Axolotl(yogaAdapter)<
Models<{ Secret: number; ID: string }>,
Scalars,
Directives
>();
// Module src/modules/myModule/axolotl.ts — type-only, no builder
const yogaAdapter = graphqlYogaWithContextAdapter<AppContext>();
export const { createResolvers } = Axolotl(yogaAdapter)<ModuleModels>();
graphqlYogaWithContextAdapter<AppContext>(){ ...initial, ...customFields } — spreading initial is mandatoryYogaInitialContextas AppContextas any — use as unknown as T for structural incompatibilityimport type { YogaInitialContext } from 'graphql-yoga';
export type AuthUser = { _id: string; email: string; jti: string };
export interface AppContext extends YogaInitialContext {
authUser?: AuthUser;
setCookie: (token: string) => void;
clearCookie: () => void;
}
// axolotl.ts — verifies auth on every request (try/catch, non-throwing)
const yogaAdapter = graphqlYogaWithContextAdapter<AppContext>(async (initial) => {
const cookieHeader = initial.request.headers.get('cookie');
const tokenHeader = initial.request.headers.get('token');
let authUser: AppContext['authUser'];
try {
authUser = await verifyAuth(cookieHeader, tokenHeader);
} catch {
authUser = undefined;
}
return { ...initial, authUser, setCookie, clearCookie };
});
// setCookie/clearCookie are closures created from the response object — see context.ts
Common mistakes:
type AppContext = { userId: string } — not extending YogaInitialContext(initial) => ({ userId: '123' }) — missing ...initialgraphqlYogaWithContextAdapter<AppContext>({ userId: '123' }) — must pass a functionimport { createScalars } from '@/src/axolotl.js';
import { GraphQLScalarType, Kind } from 'graphql';
const scalars = createScalars({
Secret: new GraphQLScalarType({
name: 'Secret',
serialize: (v) => String(v),
parseValue: (v) => Number(v),
parseLiteral: (ast) => (ast.kind === Kind.INT ? Number(ast.value) : null),
}),
});
adapter({ resolvers, scalars });
import { createDirectives } from '@/src/axolotl.js';
import { MapperKind } from '@graphql-tools/utils';
import { defaultFieldResolver } from 'graphql';
const directives = createDirectives({
auth: (schema, getDirective) => ({
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
if (!getDirective(schema, fieldConfig, 'auth')?.[0]) return fieldConfig;
const { resolve = defaultFieldResolver } = fieldConfig;
return {
...fieldConfig,
resolve: async (source, args, context, info) => {
if (!context.authUser) throw new GraphQLError('Not authenticated');
return resolve(source, args, context, info);
},
};
},
}),
});
adapter({ resolvers, directives });
(schema, getDirective) => { [MapperKind.X]: fieldMapper }mapSchema() internally — don't call it yourself// ✅ correct
app.use('/graphql', yoga as unknown as express.RequestHandler);
// ❌ wrong
app.use('/graphql', yoga as any);
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