AXe Skills HubSearch /

← All skills

typescript-fullstack

AXe First-party 

Reference: full SKILL.md

Below is the complete skill definition this hub loads when the skill is triggered — what the agent sees as its instructions, verbatim and unabridged.

TypeScript Full-Stack

Role

You are an elite TypeScript full-stack architect. You design type-safe applications using

Next.js 14+ App Router, server components, server actions, tRPC, Prisma, and Zod, ensuring

end-to-end type safety from database to UI.

Part 1: Next.js 14+ App Router Structure

app/
  layout.tsx           # Root layout (server component)
  page.tsx             # Home page
  loading.tsx          # Streaming fallback
  error.tsx            # Error boundary
  not-found.tsx        # 404 page
  (auth)/
    login/page.tsx
    register/page.tsx
  (dashboard)/
    layout.tsx         # Dashboard layout with sidebar
    page.tsx
    settings/page.tsx
  api/
    trpc/[trpc]/route.ts
lib/
  trpc/
    client.ts
    server.ts
    router.ts
  db.ts                # Prisma client
  auth.ts              # Auth utilities
components/
  ui/                  # Shared UI components
  forms/               # Form components

Root Layout

// app/layout.tsx
import type { Metadata } from "next";
import { TRPCProvider } from "@/lib/trpc/client";

export const metadata: Metadata = {
  title: { default: "App", template: "%s | App" },
  description: "Production app",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <TRPCProvider>{children}</TRPCProvider>
      </body>
    </html>
  );
}

Part 2: Server Components vs Client Components

Server Component (Default — No "use client")

// app/(dashboard)/page.tsx — Server Component
import { db } from "@/lib/db";
import { UserList } from "@/components/UserList";

export default async function DashboardPage() {
  // Direct database access — no API call needed
  const users = await db.user.findMany({
    orderBy: { createdAt: "desc" },
    take: 20,
  });

  return (
    <div>
      <h1>Dashboard</h1>
      <UserList users={users} />
    </div>
  );
}

Client Component (Interactive)

// components/UserList.tsx
"use client";

import { useState } from "react";
import type { User } from "@prisma/client";

interface Props {
  users: User[];
}

export function UserList({ users: initialUsers }: Props) {
  const [search, setSearch] = useState("");

  const filtered = initialUsers.filter((u) =>
    u.name.toLowerCase().includes(search.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder="Search users..."
      />
      <ul>
        {filtered.map((user) => (
          <li key={user.id}>{user.name} — {user.email}</li>
        ))}
      </ul>
    </div>
  );
}

Part 3: Server Actions

// app/actions/user.ts
"use server";

import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
import { z } from "zod";

const CreateUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  role: z.enum(["admin", "user", "viewer"]).default("user"),
});

export async function createUser(formData: FormData) {
  const raw = Object.fromEntries(formData);
  const parsed = CreateUserSchema.safeParse(raw);

  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors };
  }

  try {
    const user = await db.user.create({ data: parsed.data });
    revalidatePath("/dashboard");
    return { data: user };
  } catch (e) {
    return { error: { _form: ["Failed to create user"] } };
  }
}

// Usage in component:
// <form action={createUser}>
//   <input name="name" />
//   <input name="email" />
//   <button type="submit">Create</button>
// </form>

useActionState Hook

"use client";

import { useActionState } from "react";
import { createUser } from "@/app/actions/user";

export function CreateUserForm() {
  const [state, action, isPending] = useActionState(createUser, null);

  return (
    <form action={action}>
      <input name="name" required />
      {state?.error?.name && <p className="text-red-500">{state.error.name}</p>}
      <input name="email" type="email" required />
      {state?.error?.email && <p className="text-red-500">{state.error.email}</p>}
      <button type="submit" disabled={isPending}>
        {isPending ? "Creating..." : "Create User"}
      </button>
    </form>
  );
}

Part 4: tRPC Setup

Server Router

// lib/trpc/router.ts
import { initTRPC, TRPCError } from "@trpc/server";
import { z } from "zod";
import { db } from "@/lib/db";

const t = initTRPC.context<{ userId?: string }>().create();

const authed = t.middleware(({ ctx, next }) => {
  if (!ctx.userId) throw new TRPCError({ code: "UNAUTHORIZED" });
  return next({ ctx: { userId: ctx.userId } });
});

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(authed);

export const appRouter = router({
  user: router({
    list: publicProcedure
      .input(z.object({
        page: z.number().default(1),
        limit: z.number().max(100).default(20),
      }))
      .query(async ({ input }) => {
        const [items, total] = await Promise.all([
          db.user.findMany({ skip: (input.page - 1) * input.limit, take: input.limit }),
          db.user.count(),
        ]);
        return { items, total, page: input.page, pages: Math.ceil(total / input.limit) };
      }),

    create: protectedProcedure
      .input(z.object({
        name: z.string().min(2),
        email: z.string().email(),
      }))
      .mutation(async ({ input }) => {
        return db.user.create({ data: input });
      }),
  }),
});

export type AppRouter = typeof appRouter;

Client Hook

// lib/trpc/client.ts
"use client";

import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "./router";

export const trpc = createTRPCReact<AppRouter>();

Usage in Component

"use client";

import { trpc } from "@/lib/trpc/client";

export function UserDirectory() {
  const { data, isLoading } = trpc.user.list.useQuery({ page: 1, limit: 20 });
  const createUser = trpc.user.create.useMutation({
    onSuccess: () => utils.user.list.invalidate(),
  });
  const utils = trpc.useUtils();

  if (isLoading) return <p>Loading...</p>;

  return (
    <div>
      {data?.items.map((user) => <p key={user.id}>{user.name}</p>)}
      <button onClick={() => createUser.mutate({ name: "New", email: "[email protected]" })}>
        Add User
      </button>
    </div>
  );
}

Part 5: Prisma ORM

Schema

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int       @id @default(autoincrement())
  email     String    @unique
  name      String
  role      Role      @default(USER)
  posts     Post[]
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  @@index([email])
  @@index([role])
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  tags      Tag[]
  createdAt DateTime @default(now())

  @@index([authorId])
  @@index([published])
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[]
}

enum Role {
  ADMIN
  USER
  VIEWER
}

Prisma Client Singleton

// lib/db.ts
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const db = globalForPrisma.prisma ?? new PrismaClient({
  log: process.env.NODE_ENV === "development" ? ["query", "warn", "error"] : ["error"],
});

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;

Part 6: Zod Validation Patterns

import { z } from "zod";

// Reusable schemas
const EmailSchema = z.string().email().toLowerCase();
const PasswordSchema = z.string().min(8).regex(/[A-Z]/, "Needs uppercase").regex(/[0-9]/, "Needs number");

// Object schema with refinements
const RegisterSchema = z.object({
  email: EmailSchema,
  password: PasswordSchema,
  confirmPassword: z.string(),
  name: z.string().min(2).max(100).transform((s) => s.trim()),
  acceptTerms: z.literal(true, { errorMap: () => ({ message: "Must accept terms" }) }),
}).refine((d) => d.password === d.confirmPassword, {
  message: "Passwords don't match",
  path: ["confirmPassword"],
});

// Discriminated unions for API responses
const ApiResponse = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.unknown() }),
  z.object({ status: z.literal("error"), message: z.string(), code: z.number() }),
]);

// Parse env vars
const EnvSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
});

export const env = EnvSchema.parse(process.env);

Part 7: Error Boundaries

// app/error.tsx
"use client";

import { useEffect } from "react";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error("Unhandled error:", error);
    // Send to error tracking service
  }, [error]);

  return (
    <div className="flex flex-col items-center justify-center min-h-[50vh]">
      <h2 className="text-2xl font-bold mb-4">Something went wrong</h2>
      <p className="text-gray-600 mb-4">{error.message}</p>
      <button
        onClick={reset}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Try again
      </button>
    </div>
  );
}

Part 8: Custom React Hooks

"use client";

import { useState, useCallback, useRef, useEffect } from "react";

// Debounced value hook
export function useDebounce<T>(value: T, delay: number = 300): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Async operation hook with loading/error states
export function useAsync<T>() {
  const [state, setState] = useState<{
    data: T | null;
    loading: boolean;
    error: Error | null;
  }>({ data: null, loading: false, error: null });

  const execute = useCallback(async (fn: () => Promise<T>) => {
    setState({ data: null, loading: true, error: null });
    try {
      const data = await fn();
      setState({ data, loading: false, error: null });
      return data;
    } catch (error) {
      setState({ data: null, loading: false, error: error as Error });
      throw error;
    }
  }, []);

  return { ...state, execute };
}

// Intersection observer hook for infinite scroll
export function useIntersection(callback: () => void) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) callback(); },
      { threshold: 0.1 }
    );
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, [callback]);

  return ref;
}

AXE MCP Server Integration

Every skill in the AXE Skills Hub runs with access to the AXE MCP Server — giving it the full fleet intelligence toolkit automatically. No setup required; tools are available in any AXE-powered session.

Core Tools Available

CategoryToolsUse Case
Memoryread_memory, write_memory, list_memoryPersist context across sessions
Webweb_search, web_fetchLive data, docs, research
File Opsread_file, write_fileRead/write any local file
Fleetfleet_ssh, axe_pushRun commands on JL2/JL3/JL4, send notifications
AI Modelsquery_team_channel, get_partner_stateCross-agent coordination
Dataqdrant_search, qdrant_storeSemantic memory & vector search
Pipelinehydra_addAdd high-quality outputs to Edge training
Skillshub_list_skills, hub_get_skill, hub_search_skills, hub_get_registry, hub_skill_metadataChain skills together
Secretsget_secretRetrieve API keys securely

Quick Start

# In any AXE session, tools are pre-loaded. Example chaining:

# 1. Search for context
results = qdrant_search("user query here", collection="axe_persistent_memory")

# 2. Fetch live data if needed
content = web_fetch("https://docs.example.com/api")

# 3. Write result to memory for next session
write_memory("shared/last_result.md", output)

# 4. Log quality output to Edge training pipeline
hydra_add(prompt=user_query, response=output, score=0.9, source="skill-name")

Edge Training Integration

High-quality skill outputs are automatically eligible for Edge model training via hydra_add. When a response scores ≥0.85 in evals, pipe it to the Hydra pipeline to compound Edge's knowledge. This is how skills make Edge smarter over time.

# After generating a high-quality response:
hydra_add(
    prompt=user_input,
    response=final_output,
    score=0.9,          # eval score
    source="skill-name" # tracks provenance
)

Metadata

Category
Web
Tier
community
Version
1.0.0
License
MIT
Path
skills/typescript-fullstack/SKILL.md

Use with an agent

Fetch this skill’s definition over the open API — no key required.

curl -s /v1/skills/typescript-fullstack

View source ↗