This document provides guidance for AI coding assistants (like Claude, Cursor, etc.) working with this TypeScript monorepo.
DO NOT RUN these commands directly - they will hang:
pnpm new- Package generatorpnpm delete- Package deletionpnpm add-entry- Add custom entry pointspnpm changeset- Create changesets
Instead: Ask the user to run these commands and wait for them to complete. Provide clear instructions on what options to select.
Safe to run (non-interactive):
pnpm install,pnpm build,pnpm test,pnpm verifypnpm dev,pnpm lint,pnpm formatpnpm version(applies changesets)pnpm generate:configs(regenerates TypeScript configs)
This is a TypeScript monorepo built with:
- PNPM Workspaces for package management
- Turbo for task orchestration and caching
- tsdown for building publishable packages
- Vitest for testing
- Changesets for version management
@restatedev/world: Package using typed client to implement World by talking to Restate virtual objects/services@restatedev/backend: Collection of Restate virtual objects and services@restatedev/common: Common types package (private, not publishable)@restatedev/workflow: Example package built usingworkflowpackage
-
Public Libraries (
packages/libs/*/withprivate: false)- Built with tsdown to
dist/ - Published to npm
- Support ESM + CJS + TypeScript declarations
- Can have multiple entry points (subpath exports)
- Built with tsdown to
-
Private Libraries (
packages/libs/*/withprivate: true)- Source-only (no build step)
- Used internally, bundled into public packages
- Main points to
./src/index.ts
-
Test Packages (
packages/tests/*/)- Always private
- Use Vitest for testing
- Can test libraries from source (dev) or built output (CI)
-
Example Packages (
packages/examples/*/)- Always private
- Demonstrate library usage
- Can run from source (dev) or built output (production)
- Dev Mode: No builds required! Type checking only with
tsc --noEmit --watch - Production Mode: Full builds with tsdown (ESM + CJS + declarations)
- Turbo: Automatically handles dependency ordering and caching
- Path Mappings: Auto-generated in root
tsconfig.jsonfor IDE support
.templates/- Plop templates for generating packagesturbo.json- Task configuration with dependency graphpnpm-workspace.yaml- Workspace configuration with catalogstsconfig.base.json- Shared TypeScript configvitest.config.ts- Shared test configurationapi-extractor.base.json- API validation configuration
Build:
pnpm build- Build library packages onlypnpm build:all- Build everything including examples
Test:
pnpm test- Run all testspnpm test:watch- Run tests in watch modevitest run path/to/file.test.ts- Run a single test file
Dev:
pnpm dev- Type-check all library packagespnpm examples:dev- Run workflow examplepnpm examples:start <name>- Start specific example in production mode
Inspect:
pnpm inspect- Inspect workflows
Verify:
pnpm verify- Run all checks (format, lint, types, build, test, exports, API) - ALWAYS run before commit
Lint/Format:
pnpm lint- Run ESLintpnpm format- Format code with Prettierpnpm check:format- Check formatting without fixing
Clean:
pnpm clean- Remove build artifactspnpm clean:cache- Clear Turbo cache
The pnpm new command is interactive and will hang if run by an AI agent. Instead:
-
Ask the user to run it:
"Please run `pnpm new` to create a new package. I'll help you configure it after." -
Or inform the user what needs to be created and let them decide:
"To add a new library package, you'll need to run `pnpm new` and select: - Package type: lib - Package name: my-package - Private: no (for publishable) or yes (for internal) Would you like me to wait while you run this, or would you prefer to do it later?"
Never attempt to run interactive commands like:
pnpm new- Package generator (interactive prompts)pnpm delete- Package deletion (interactive selection)pnpm add-entry- Entry point addition (interactive prompts)pnpm changeset- Changeset creation (interactive prompts)
For monorepo dependencies:
cd packages/libs/my-package
pnpm add "@restatedev/other-package@workspace:*"For publishable packages depending on other publishable packages:
Add to tsdown.config.ts:
export default defineConfig({
// ...
external: ["@restatedev/other-package"],
});This prevents bundling the dependency into your package.
All package scripts use Turbo with --filter={.}... pattern:
{
"scripts": {
"build": "turbo run _build --filter={.}...",
"_build": "tsc --noEmit && tsdown"
}
}- Public-facing scripts (e.g.,
build) use Turbo with filters - Internal scripts (e.g.,
_build) are the actual commands - The
--filter={.}...pattern means "this package and its dependencies"
The pnpm add-entry command is interactive. Tell the user:
"To add a custom entry point (e.g., @restatedev/pkg/utils), please run:
pnpm add-entry
This will automatically update:
- package.json exports and typesVersions
- tsdown.config.ts entry array
- Root tsconfig.json paths
- API Extractor configs
- Package scripts"
- Strict mode enabled:
strict: true,noUncheckedIndexedAccess: true,noImplicitOverride: true - Module system:
NodeNextmodules and resolution (ESM) - Target: ES2022
- Explicit return types: Required on all exported functions
- Type safety: Avoid
any, preferunknownfor truly unknown types - No implicit any: All parameters and variables must have explicit or inferred types
- Indentation: 2 spaces
- Semicolons: Required
- Quotes: Double quotes
- Line width: 80 characters
- Trailing commas: ES5 style (objects, arrays, but not function params in older contexts)
- TypeScript ESLint with recommended type-checked rules
- Unused variables: Prefix with
_to indicate intentionally unused (e.g.,_error,_data) - Config files: Disable type checking for
*.config.{js,ts,mjs,mts}files
- ESM only: All packages use
"type": "module" - File extensions: Use
.jsextensions in imports for local files (TypeScript convention for ESM)import { hello } from "./utils.js"; // Correct import { hello } from "./utils"; // Incorrect
- Import order: No strict enforcement, but generally: external deps → workspace deps → relative imports
- Variables/Functions: camelCase (e.g.,
getUserData,isActive) - Types/Interfaces/Classes: PascalCase (e.g.,
UserProfile,ApiResponse) - Files: kebab-case (e.g.,
user-profile.ts,api-client.ts) - Constants: camelCase or UPPER_SNAKE_CASE for true constants (e.g.,
MAX_RETRIES)
- Throw
Errorobjects: Always throw proper Error instances or custom error classes - Use
FatalError: For workflow-specific errors that should not be retried - Document errors: Use JSDoc to document error conditions
/** * Fetches user data from the API * @throws {Error} If the network request fails * @throws {FatalError} If the user ID is invalid */ async function fetchUser(id: string) { ... }
All TypeScript files should include a copyright header:
/*
* Copyright (c) TODO: Add copyright holder
*
* This file is part of TODO: Add project name,
* which is released under the MIT license.
*
* You can find a copy of the license in file LICENSE in the root
* directory of this repository or package, or at
* TODO: Add repository URL
*/Templates already include this header. Update the TODOs when customizing for your project.
- Minimal inline comments: Prefer self-documenting code with clear variable and function names
- JSDoc for public APIs: Document all exported functions, classes, and types
- Explain "why" not "what": Comments should explain reasoning, not restate the code
- TODO comments: Use
// TODO:for temporary notes, but address them before committing
Root level:
tsconfig.base.json- Shared compiler optionstsconfig.json- Extends base + path mappings (auto-generated)
Package level:
tsconfig.json- Extends root (inherits path mappings)tsconfig.build.json- Extends base (clean builds, no path mappings)tsconfig.test.json- For test packages
Key points:
- Use
tsconfig.build.jsonfor building (clean, no external references) - Use
tsconfig.jsonfor dev/IDE (includes path mappings) - Path mappings are auto-generated by
pnpm generate:configs
Standard order for consistency:
{
"name": "@scope/package-name",
"version": "0.0.0",
"description": "...",
"author": "...",
"license": "MIT",
"homepage": "...",
"repository": {...},
"bugs": {...},
"private": true,
"type": "module",
"main": "...",
"module": "...",
"types": "...",
"exports": {...},
"files": [...],
"publishConfig": {...},
"scripts": {...},
"dependencies": {...},
"devDependencies": {...}
}# Start type checking for all libs
pnpm dev
# In another terminal, run tests in watch mode
pnpm test:watch
# In another terminal, run the workflow example
pnpm examples:devChanges to lib source files are immediately reflected in tests and examples - no build required!
# Run all checks (same as CI)
pnpm verify
# If checks pass, commit
git add .
git commit -m "Your message"pnpm changeset is interactive
Automatic workflow (recommended):
- Ask user to create changeset: "Please run
pnpm changesetto create a changeset for your changes" - User commits the changeset and merges to main
- GitHub Actions automatically creates tag, release, and publishes
Manual workflow (hotfixes):
- Ask user to create changeset: "Please run
pnpm changeset" - User or agent runs:
pnpm version(not interactive - safe to run) - Commit and tag:
git tag v1.2.3 && git push origin v1.2.3 - Create GitHub release (triggers publish)
Never run pnpm release locally - let CI handle publishing.
If you see import errors:
- Check if
tsconfig.jsonin root has path mappings - Regenerate configs:
pnpm generate:configs - Restart TypeScript server in your IDE
If builds fail:
- Clean artifacts:
pnpm clean - Clear Turbo cache:
pnpm clean:cache - Reinstall dependencies:
rm -rf node_modules pnpm-lock.yaml && pnpm install
If export validation fails:
- Check that all entry points are listed in
package.jsonexports - Verify
typesVersionsis configured for Node 10 compatibility - Ensure built files exist in
dist/
If API validation fails:
- Export any types used in public APIs
- Check that
api-extractor.jsonexists for each entry point - Verify
tsconfig.build.jsonis being used
- Always use generators for creating/modifying packages and entry points
- Run
pnpm verifybefore committing - Use workspace dependencies with
@workspace:*protocol - Add external declarations in tsdown config for publishable dependencies
- Keep private libs simple - they're bundled automatically
- Test from source in dev - use built output for CI/production validation
- Follow the package.json field order for consistency
- Include copyright headers in all TypeScript files
- Use PNPM catalogs for shared/peer dependencies
- Let CI handle publishing - never publish locally
When creating new TypeScript files outside of the generators, remember to:
- Include the copyright header
- Follow existing naming conventions
- Export from index.ts if it's a public API
- Add tests in the appropriate test package
See DEVELOPMENT.md for comprehensive documentation.