Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 97 additions & 3 deletions .agents/skills/tanstack-promptable-fullstack-app-template/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ description: 'Use when scaffolding a new TanStack Start project, adding domain
repository pattern with AI-promptable tools, or nested layout routes duplicate
beforeLoad checks or loaders that should live on a parent route, or TanStack
Router, Start, or AI behavior must be verified against current documentation
instead of training data. Project: TanStack AI-Promptable Full-Stack Template.
instead of training data, or route loaders may import databases or secrets, or
server-only code may leak into the client bundle, or isomorphic loader
boundaries are unclear. Project: TanStack AI-Promptable Full-Stack Template.
Triggers on "fullstack template", "TanStack Start project", "repository
pattern", "interface-first", "new app scaffold", "nested routes", "layout
route", "beforeLoad", "tanstack cli", "tanstack intent", "package skills".'
route", "beforeLoad", "tanstack cli", "tanstack intent", "package skills",
"client bundle leak", "server-only", "isomorphic loader", "process.env in
loader", "import protection".'
---

> This file is generated from `skills/src/*.skill.yaml`. Do not edit manually.
Expand All @@ -22,7 +26,7 @@ description: 'Use when scaffolding a new TanStack Start project, adding domain

1. Read **Core Contract** first — it is the non-negotiable architecture.
2. Run the **Architecture Checklist** before every non-trivial change.
3. Jump to **Schema Boundaries**, **Request Context**, or **Special Patterns** only when that concern applies.
3. Jump to **Server execution boundaries**, **Schema Boundaries**, **Request Context**, or **Special Patterns** only when that concern applies.
4. Use **[AGENTS.md](https://github.com/carlosvin/tanstack-fullstack-ai-template/blob/main/AGENTS.md)** for operational how-to (UI kit, chat wiring, logging, tests, validation commands) — not for inventing alternate architecture.

## Common failure modes (avoid these)
Expand All @@ -34,6 +38,9 @@ description: 'Use when scaffolding a new TanStack Start project, adding domain
- **UI-only auth:** hiding buttons in components but skipping guards in server handlers.
- **Type escape hatches:** `any`, loose `Record<string, unknown>`, or `as` after `Schema.parse` — narrow, guard, or fix types instead.
- **Duplicated parent work:** copying a parent layout’s `beforeLoad`, loader, or expensive read into each child route.
- **Server logic in loaders:** `process.env` secrets, DB drivers, or repository imports inside a route `loader` or route file top-level imports.
- **Wrong server primitive:** `createServerFn` for internal singletons that must never be RPC-callable — use `createServerOnlyFn` instead.
- **Leaky module graph:** server modules without `*.server.ts` or `import '@tanstack/react-start/server-only'` pulled into files consumed by UI.

## Core Contract

Expand All @@ -52,6 +59,7 @@ description: 'Use when scaffolding a new TanStack Start project, adding domain
13. **Bound the agent loop:** every `chat()` call sets `agentLoopStrategy: maxIterations(N)` explicitly (default `N=10`); tune after measuring — do not rely on the framework default.
14. **Metadata for AI and UI:** Use `.describe()` for all narrative explanations (JSON Schema `description`). Use `.meta({ ... })` only for **structured extras** — `unit`, `format`, optional `title`, app-specific hints — not as a substitute for `.describe()`. Prefer deriving prompts and UI copy from schemas + `z.toJSONSchema()` and router introspection over parallel hand-maintained maps.
15. **Parent layouts:** Shared `beforeLoad`, redirects, and expensive reads belong on the **parent** layout route; children read parent loader data via `getRouteApi` / `useLoaderData({ from })` — do not duplicate parent work.
16. **Server execution boundaries:** Route loaders are **isomorphic** — they run on the server during SSR and on the client during SPA navigations. Loaders only **call** exported `createServerFn` from `serverFns.ts` (e.g. `getTasks({ data: deps })`). DB access, secrets, and Node-only SDKs live in `*.server.ts` or behind `createServerOnlyFn`; extend `tanstackStart({ importProtection })` when adding node packages.

## Architecture Checklist

Expand All @@ -67,6 +75,92 @@ Scan before changing code:
- **AI stack is complete:** every repo method → server tool + safe handler; client **`navigate`** / **`invalidateRouter`**; root **`getAIAvailability()`**; chat payload includes **`browserContext`**; **`chat({ agentLoopStrategy: maxIterations(N) })`**.
- **Routes:** **`validateSearch`** + **`loaderDeps`**; duplicate **`beforeLoad`** / shared loaders only on **parent** layouts.
- **Metadata discipline:** `.describe()` for narrative copy; `.meta()` for structured extras; closed vocabularies = `as const` tuple + `z.enum` + `z.infer<>`.
- **Server boundaries:** loaders call `serverFns` only — no `process.env` secrets, DB drivers, or repo imports in route files; `*.server.ts` for Mongo/Node SDKs; `createServerOnlyFn` for non-RPC infra; `importProtection` updated for new node packages.

## Server execution boundaries

TanStack route **loaders are isomorphic** — they run during SSR **and** on client-side navigations. Treat every route module as potentially shipping to the browser.

### Forbidden in route files

- Top-level imports of `getDb`, repositories, `mongodb`, `fs`, or other Node-only modules.
- `process.env` for secrets inside `loader` bodies.
- Inline DB queries or repository calls inside `loader`.

### Required pattern

Define reads/writes in [`src/services/api/serverFns.ts`](src/services/api/serverFns.ts). Route loaders only invoke them:

```typescript
// src/routes/tasks/index.tsx — thin route
export const Route = createFileRoute('/tasks/')({
validateSearch: TasksSearchSchema,
loaderDeps: ({ search }) => search,
loader: ({ deps }) => getTasks({ data: deps }),
})
```

```typescript
// src/services/api/serverFns.ts — server-only handler body
export const getTasks = createServerFn({ method: 'GET' })
.inputValidator(TaskFilterSchema.optional())
.handler(async ({ data: filter }) => {
const repoFilter = filter ? TaskRepoFilterSchema.parse(filter) : undefined
return getReadRepository().getTasks(repoFilter)
})
```

### File naming and tripwires

- **`*.server.ts` / `*.server.tsx`:** DB clients, repositories with drivers, private API keys, Node-only SDKs (e.g. `mongoClient.server.ts`, `getRepository.server.ts`).
- **When rename is awkward:** first line `import '@tanstack/react-start/server-only'`.

### `createServerFn` vs `createServerOnlyFn`

| Primitive | Use when |
|-----------|----------|
| `createServerFn` | Loaders, mutations, and AI tools need to trigger server work over RPC (`GET` / `POST`). |
| `createServerOnlyFn` | Internal singletons (DB client factory) that must **never** be client-callable. |

```typescript
import { createServerOnlyFn } from '@tanstack/react-start'
import { getDb } from '../db/mongoClient.server'

export const getDbConnection = createServerOnlyFn(async () => getDb())
```

Do **not** define new `createServerFn` inline in route files — keep RPC entry points centralized in `serverFns.ts`.

### Import protection (Vite)

When adding node-only packages, extend [`vite.config.ts`](vite.config.ts):

```typescript
tanstackStart({
importProtection: {
behavior: 'error',
client: {
specifiers: ['mongodb', 'jose'],
files: ['**/services/db/**', '**/repository/*.server.ts'],
},
},
})
```

Add `jose` (or other auth/crypto libs) when they are not isolated in `*.server.ts`. Set `ignoreImporters: ['**/*.test.ts']` if unit tests import server modules in jsdom. Verify with `pnpm build`. Docs: `npx @tanstack/cli search-docs "import protection" --library start`.

### If the user asks for DB/secrets in a component or route config

1. **Stop** — explain the isomorphic loader / client-bundle risk.
2. **Refactor** — move logic to `serverFns.ts`, `*.server.ts`, or `createServerOnlyFn`.

### Rationalizations (reject these)

| Excuse | Reality |
|--------|---------|
| “Loader ran on SSR so it’s server-only” | Loaders re-run on client navigations. |
| “Dynamic import in the loader is enough” | Route module static imports still enter the client graph. |
| “One-line `process.env` read won’t matter” | Isomorphic code can expose env reads to the client bundle. |

## Markdown assistant replies (UX contract)

Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ Route Loader → Server Function (serverFns.ts) → Repository → Database / Se
- **Input Validation**: Server functions pass Zod schemas to `.inputValidator(Schema)`.
- **Repository Pattern**: All data access goes through the repository interface.
- **Calling convention**: Callers pass `{ data: inputData }` to server functions (e.g. `getTasks({ data: filter })`).
- **Server execution boundaries:** Route loaders are **isomorphic** (SSR and client navigations) — never import DB clients, repositories, or read secrets in route files. Loaders only call exported functions from `serverFns.ts`. DB and env wiring live in `*.server.ts` modules; see the skill’s **Server execution boundaries** section and `vite.config.ts` `importProtection`.

### 6.1. Data Fetching (Queries)

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ src/
│ │ ├── sentry.ts # Sentry implementation
│ │ ├── noop.ts # No-op implementation
│ │ └── index.ts # Factory
│ └── db/mongoClient.ts # MongoDB singleton
│ └── db/mongoClient.server.ts # MongoDB singleton (server-only)
├── utils/
│ ├── auth.ts # requireAuth(), requireGroup()
│ ├── httpError.ts # HttpError class
Expand Down Expand Up @@ -247,7 +247,7 @@ See [`.env.example`](.env.example) for the full list with documentation.
### Adding a New Entity (End-to-End)

1. **Schema**: Add Zod schemas in `src/services/schemas/schemas.ts` with `.describe()` on every field.
2. **Repository**: Add methods to the `ReadRepository` and/or `WritableRepository` interfaces in `types.ts`. Implement in both `seedRepository.ts` and `mongoRepository.ts`.
2. **Repository**: Add methods to the `ReadRepository` and/or `WritableRepository` interfaces in `types.ts`. Implement in both `seedRepository.ts` and `mongoRepository.server.ts`.
3. **Server Functions**: Add `createServerFn` wrappers in `src/services/api/serverFns.ts`. Chain `.middleware([invalidateMiddleware])` on mutations.
4. **AI Tools**: Expose methods as tools in `src/services/ai/tools.ts` that call your server functions through `createSafeServerTool()`. Update the system prompt.
- Keep `src/services/ai/navigationManifest.ts` aligned with routes (including dynamic segments like `/tasks/$taskId`).
Expand All @@ -257,7 +257,7 @@ See [`.env.example`](.env.example) for the full list with documentation.

### Swapping the Database

Replace `mongoRepository.ts` with your implementation of the `Repository` interface. Update the factory in `getRepository.ts`.
Replace `mongoRepository.server.ts` with your implementation of the `Repository` interface. Update the factory in `getRepository.server.ts`.

### Swapping the AI Provider

Expand Down Expand Up @@ -346,7 +346,7 @@ Then follow the end-to-end workflow:
4. Add server functions in `src/services/api/serverFns.ts` (GET for loaders, POST with `invalidateMiddleware` for mutations)
5. Expose methods as AI tools in `src/services/ai/tools.ts` that call your server functions through `createSafeServerTool()`
6. Create file-based routes under `src/routes/` (data in loaders, state in URL search params)
7. When ready for real data, implement `mongoRepository.ts` and set `MONGODB_URI`
7. When ready for real data, implement `mongoRepository.server.ts` and set `MONGODB_URI`

### Option B: AI-assisted (skill)

Expand Down
7 changes: 7 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
allowBuilds:
'@parcel/watcher': set this to true or false
'@sentry/cli': set this to true or false
'@swc/core': set this to true or false
esbuild: set this to true or false
protobufjs: set this to true or false
sharp: set this to true or false
Loading
Loading