This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Install dependencies
bun install
# Build all packages
bun run build
# Type check
bun run typecheck
# Run all tests
bun test
# Run a specific test file
bun test tests/useEntity.test.tsx
# Run tests matching a pattern
bun test --grep "pattern"
# Watch mode for development
bun run dev
# Run the example playground
bun run playgroundBindx is a type-safe data binding framework for React, designed to work with Contember CMS. The monorepo contains two main packages:
@contember/bindx(packages/bindx) - Framework-agnostic core@contember/react-bindx(packages/react-bindx) - React bindings and hooks
Schema Definition (packages/bindx/src/schema/)
- Define entity schemas with
defineSchema(),scalar(),hasOne(),hasMany() SchemaRegistrymanages schema metadata and relation lookups
Snapshot Store (packages/bindx/src/store/SnapshotStore.ts)
- Central immutable data store using frozen snapshots
- Entity-level subscriptions for fine-grained React reactivity
- Tracks both current data and server data for dirty detection
- Keyed by
"entityType:id"for entities,"entityType:id:fieldName"for relations
Handles (packages/bindx/src/handles/)
EntityHandle- Stable reference to an entity with cached field/relation handlesFieldHandle- Access to a single scalar field withvalue,setValue(),inputPropsHasOneHandle- Access to has-one relations withconnect(),disconnect(),entityHasManyListHandle- Access to has-many relations withitems,map(),add(),remove()- Handles implement
EntityRef,FieldRef,HasOneRef,HasManyRefinterfaces
Selection Builder (packages/bindx/src/selection/)
- Fluent API for selecting entity fields:
e => e.id().title().author(a => a.name()) - Builds
SelectionMetawhich is converted toQuerySpecfor fetching
Backend Adapters (packages/bindx/src/adapter/)
BackendAdapterinterface for data fetching/persistenceMockAdapter- In-memory store for testingContemberAdapter- Connects to Contember GraphQL API
createBindx() (src/hooks/createBindx.ts)
- Factory that creates typed hooks for a schema:
useEntity,useEntityList,Entity,createComponent
Hooks Pattern
useEntity(entityType, { id }, selectionDefiner)returnsEntityAccessorResult(loading/error/ready states)- Uses
useSyncExternalStoreinternally for store subscriptions BindxProvider/ContemberBindxProviderprovide context with store, dispatcher, adapter
JSX Components (src/jsx/)
Entity,Field,HasOne,HasMany,If,ShowcomponentscreateComponent()for defining reusable components with selection props- Selection is analyzed from JSX to auto-generate GraphQL queries
- Define schema with
defineSchema() - Create hooks with
createBindx(schema) - Use
useEntity()with selection definer - builds query, fetches via adapter - Data stored in
SnapshotStoreas immutable snapshots - Handles provide stable access to data with change tracking
- Mutations dispatch actions through
ActionDispatcher PersistenceManager/MutationCollectorgenerate Contember mutations
Tests use Bun's test runner with @testing-library/react and happy-dom for DOM simulation. Test preload is configured in bunfig.toml.
- If you are debugging something, it is better to write a test instead of some temp debug file
- Strict mode enabled with
noUncheckedIndexedAccess - Module resolution:
bundler - Project references for packages in
tsconfig.json
- Single Responsibility: One module/class = one clear purpose
- Separation of Concerns: Split presentation, business logic, and data layers
- Dependency Inversion: Depend on interfaces, not implementations
- Composition over Inheritance: Prefer composition and small, focused types
- By Feature/Domain: Group by feature, not by type (
users/,orders/notcontrollers/,services/) - Layer Separation: Clear boundaries between UI, domain logic, and data access
- File Size: Keep files under ~300 lines
- Module Boundaries: Related code together, unrelated code apart
- No
any: Useunknownor proper types instead - No Type Casting: Avoid
as- use type guards and narrowing - Explicit Return Types: Always define return types for functions
- Interface over Type: Prefer
interfacefor object shapes (extensible) - Discriminated Unions: For variants/state machines
- Generic Constraints: Make generics meaningful (
T extends Usernot justT) - Strict Mode: Enable all strict TypeScript flags
- Keep Small: Under 20-30 lines
- Single Task: One function = one thing
- Max 3 Parameters: Use config object for more
- Pure When Possible: Avoid side effects
- Return Early: Use guard clauses
- Descriptive:
getUserByIdnotget - Consistent Terminology: One word per concept
- Booleans: Use
is,has,can,should - Functions: Verbs (
calculate,fetch) - Types/Interfaces: Nouns (
User,Config) - No Abbreviations: Write fully (except HTTP, URL, etc.)
- DRY: Extract common logic
- KISS: Simple over clever
- YAGNI: Don't add unused features
- Fail Fast: Validate early
- Self-Documenting: Clear names over comments
- Small Components: Under ~200 lines
- Props Interface: Always explicit types
- Composition: Break down complex UIs
- Custom Hooks: Extract complex logic
- No Prop Drilling: Use composition or context
- Specific Errors: Custom error types
- Never Empty Catch: Always handle or log
- Type Errors: Make error types explicit
- Inject Dependencies: Constructor/function parameters
- Pure Functions: Easier to test
- Clear Interfaces: Mock through interfaces
- Small Units: Easier to test in isolation
- ❌ God Classes (do everything classes)
- ❌ Tight Coupling (modules knowing too much)
- ❌ Type Assertions (
as) - ❌
anytype - ❌ Deep Nesting (> 3 levels)
- ❌ Copy-Paste Code
- project is work in progress, do not be afraid to refactor anything, or make BC breaks
- do not create back compatible code, do not keep deprecated stuff
- yet, design everything in the manner of best practices with best possible architecture for given task