Skip to content

Commit 00065bc

Browse files
Enalmadaclaude
andcommitted
docs(skill): document TSS-6 — one createServerFn per file (load-bearing on v1.167+)
The TanStack Start v1.167+ `tanstack-start-core:import-protection` Vite plugin walks the import graph and rejects any module reachable from a client route that imports `@tanstack/react-start/server` (even via dynamic await import). Housing multiple createServerFn surfaces alongside server-only helpers in a single file (the prior shape of `base-service.ts`) reliably trips this gate the moment ANY client module imports any one of those handlers. The per-handler-file split this PR just landed is now documented as TSS-6 with a load-bearing tag, the failure-mode rationale, and pointers at the canonical files (find-first.ts, find-many.ts, create-entity.ts, update-entity.ts, delete-entity.ts + the slimmed base-service.ts). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 33faa05 commit 00065bc

1 file changed

Lines changed: 11 additions & 0 deletions

File tree

.claude/skills/tanstack-start/SKILL.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ Every `createServerFn` file MUST follow this pattern:
2525

2626
Reference example: see [src/functions/user-role.ts](src/functions/user-role.ts).
2727

28+
## One createServerFn per file (REQUIRED — TSS-6)
29+
30+
Each `createServerFn(...)` lives in its own per-handler file. **Do not** collect multiple createServerFn definitions in a shared helper file (e.g. `base-service.ts`) that other modules import.
31+
32+
**Why this is load-bearing**: `@tanstack/react-start` v1.167+ ships a `tanstack-start-core:import-protection` Vite plugin that walks the import graph and rejects any module reachable from a client route that imports `@tanstack/react-start/server` — even via dynamic `await import(...)`. If a shared file houses both the createServerFn definitions AND server-only helpers (e.g. `getUser`, `loadEntityConfig`), then a single client-reachable importer (`~/utils/query/queries.ts`, `~/utils/query/mutations.ts`, any route loader) drags the whole server-only chain into the client compile pass and the dev server / build fails.
33+
34+
Per-handler files keep `base-service.ts`-style modules off the client-reachable graph: the per-handler file only re-exports its `createServerFn`, and the framework's compile-time handler extraction strips the body (with its dynamic imports of the helpers) before the client bundle is emitted.
35+
36+
Pattern in this repo: [src/functions/find-first.ts](src/functions/find-first.ts), [src/functions/find-many.ts](src/functions/find-many.ts), [src/functions/create-entity.ts](src/functions/create-entity.ts), [src/functions/update-entity.ts](src/functions/update-entity.ts), [src/functions/delete-entity.ts](src/functions/delete-entity.ts) each export exactly one createServerFn; [src/functions/base-service.ts](src/functions/base-service.ts) houses only the shared entity registry, validators, error formatter, and the `getUser` / `loadEntityConfig` helpers (no createServerFn).
37+
2838
## TSS Rules (mechanical checks)
2939

3040
| Rule | Description |
@@ -34,6 +44,7 @@ Reference example: see [src/functions/user-role.ts](src/functions/user-role.ts).
3444
| TSS-2 | No top-level server-only imports in createServerFn files (load-bearing — violations crash the client bundle) |
3545
| TSS-3 | Pure-valibot validators must not co-locate with `pgTable` declarations if shared across client + server |
3646
| TSS-5 | Validators use the canonical `.issues.map((i) => i.message).join("; ")` form |
47+
| TSS-6 | One `createServerFn` per file; no shared file housing multiple createServerFn surfaces + server-only helpers (load-bearing on v1.167+ import-protection) |
3748

3849
**TSS-2 is the load-bearing rule** — violations crash the client bundle with `Buffer is not defined` or pull Drizzle / `postgres-js` into the browser. A mechanical CI gate (`bun run check-tss-2`) enforces it.
3950

0 commit comments

Comments
 (0)