Files
linsa-linsa-io/claude.md
Nikita 8cd4b943a5 .
2025-12-21 13:37:19 -08:00

5.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Monorepo Architecture

This is a pnpm workspace monorepo with two packages:

  • packages/worker: Cloudflare Worker (backend)
  • packages/web: TanStack Start app (frontend)

Key Architectural Pattern: Worker RPC via Service Bindings

The web package communicates with the worker package through two mechanisms:

  1. HTTP API (traditional): REST endpoints exposed by Hono in packages/worker/src/index.ts
  2. RPC calls (service bindings): Type-safe method calls via WorkerRpc class in packages/worker/src/rpc.ts

Critical understanding: The worker exports TWO things from src/index.ts:

  • export default app - Hono HTTP handler (default export)
  • export { WorkerRpc } from './rpc' - Named export for RPC entrypoint

The web package's wrangler.jsonc configures a service binding:

"services": [{
  "binding": "WORKER_RPC",
  "service": "fullstack-monorepo-template-worker",
  "entrypoint": "WorkerRpc"  // References the named export
}]

Type Safety Across Packages

The web package imports types directly from the worker package:

// packages/web/env.d.ts
import type { WorkerRpc } from '../worker/src/rpc';

This creates a direct TypeScript dependency between packages. The monorepo structure enables this cross-package type sharing.

TanStack Start Server Context

In TanStack Start server functions/loaders, access the RPC binding via:

import { getServerContext } from '@tanstack/react-start/server';

const { WORKER_RPC } = getServerContext().cloudflare.env;
const result = await WORKER_RPC.sayHello('World');

Commands

Development

# Run both services in separate terminals
pnpm dev:worker    # Worker on localhost:8787
pnpm dev:web       # Web on localhost:3000

# Or run just one
pnpm --filter @linsa/worker dev
pnpm --filter @linsa/web dev

Testing

# Run all tests
pnpm test

# Run tests for specific package
pnpm --filter @linsa/worker test
pnpm --filter @linsa/web test

# Run tests in watch mode (within package directory)
cd packages/worker && pnpm test --watch

Linting & Formatting

pnpm lint           # Lint all packages + check formatting
pnpm lint:fix       # Fix linting issues in all packages
pnpm format         # Format all code
pnpm format:check   # Check formatting without changes

Deployment

# Deploy both packages
pnpm deploy

# Deploy individually
pnpm deploy:worker
pnpm deploy:web

# Login to Cloudflare first (one-time)
cd packages/worker && pnpm wrangler login

Working with Workspace Packages

# Add dependency to specific package
pnpm --filter @linsa/worker add <package-name>
pnpm --filter @linsa/web add <package-name>

# Add dev dependency
pnpm --filter @linsa/worker add -D <package-name>

Adding New RPC Methods

When adding RPC methods that the web package will call:

  1. Add method to packages/worker/src/rpc.ts:
export class WorkerRpc extends WorkerEntrypoint {
	async myNewMethod(param: string): Promise<Result> {
		// implementation
	}
}
  1. TypeScript will automatically provide types in the web package because env.d.ts imports the WorkerRpc type

  2. Call from web package in any server function:

const { WORKER_RPC } = getServerContext().cloudflare.env;
const result = await WORKER_RPC.myNewMethod('value');
  1. Optional: Add helper function in packages/web/src/lib/worker-rpc.ts for convenience

Adding Cloudflare Bindings

To add KV, D1, R2, or other bindings to the worker:

  1. Update packages/worker/wrangler.jsonc:
{
	"kv_namespaces": [
		{
			"binding": "MY_KV",
			"id": "your-namespace-id",
		},
	],
}
  1. Update TypeScript types in packages/worker/src/index.ts or create a separate env.ts:
interface Env {
	MY_KV: KVNamespace;
}
  1. Access in RPC methods or HTTP handlers:
// In rpc.ts
export class WorkerRpc extends WorkerEntrypoint<Env> {
	async getData(key: string) {
		return await this.env.MY_KV.get(key);
	}
}

Vitest Configuration for Cloudflare Workers

The worker package uses @cloudflare/vitest-pool-workers for testing. The config pattern is:

// vitest.config.mts
import { defineConfig } from 'vitest/config';

export default defineConfig({
	test: {
		pool: '@cloudflare/vitest-pool-workers',
		poolOptions: {
			workers: {
				wrangler: { configPath: './wrangler.jsonc' },
			},
		},
	},
});

Do not use defineWorkersConfig from @cloudflare/vitest-pool-workers/config - it causes TypeScript module resolution issues.

pnpm Workspace Configuration

The pnpm-workspace.yaml includes onlyBuiltDependencies for native dependencies:

onlyBuiltDependencies:
  - esbuild
  - sharp
  - workerd

This ensures these packages are built correctly in the monorepo context.

Package Naming Convention

Packages use the @linsa/* scope:

  • @linsa/worker
  • @linsa/web

When filtering commands, use these exact names: pnpm --filter @linsa/worker <command>

ESLint Configuration

The project uses ESLint v9 with the new flat config format (eslint.config.js).

Key points:

  • Uses @typescript-eslint/eslint-plugin v8+ (compatible with ESLint 9)
  • Configured for both TypeScript and TSX/JSX files
  • The no-undef rule is disabled for TypeScript files (TypeScript handles this)
  • Ignores: node_modules/, dist/, .wrangler/, build/

Both packages (worker and web) have lint and lint:fix scripts that use the shared root config.