Skip to content

Latest commit

 

History

History
219 lines (159 loc) · 7.53 KB

File metadata and controls

219 lines (159 loc) · 7.53 KB

AGENTS.md

Guidelines for AI coding agents working in this repository.

Project Overview

@studiolambda/query is a lightweight, isomorphic, framework-agnostic async data management library (SWR-style). It has bindings for React 19+ and Solid.js.

Build/Lint/Test Commands

# Install dependencies
npm install

# Run all tests
npm test

# Run tests in watch mode
npm run dev

# Run a single test file
npx vitest run src/query/query.test.ts

# Run tests matching a pattern
npx vitest run -t "can query resources"

# Run with coverage
npm run test:cover

# Lint
npm run lint

# Format code
npm run format

# Check formatting
npm run format:check

# Build (runs format:check and lint first)
npm run build

# Build without checks
npm run build:only

Directory Structure

src/
  query/       # Core library (framework-agnostic)
  react/       # React bindings
    hooks/     # useQuery, useQueryBasic, etc.
    components/# QueryProvider, QueryPrefetch, etc.
  solid/       # Solid.js bindings (partial)

Code Style

Formatting (Oxfmt)

  • Single quotes, no semicolons
  • 2-space indentation (no tabs)
  • 100 character line width
  • Trailing commas: es5
  • Always use parentheses in arrow functions: (x) => x
  • Configuration: .oxfmtrc.json

Imports

  1. External/framework imports first, then internal imports
  2. Use path aliases: query:index, query/react:context, query/react:hooks/useQuery
  3. Use inline type keyword for type imports:
    import { type Options } from 'query:index'
  4. Multi-line imports with trailing comma:
    import { type Caches, type CacheType, type ItemsCacheItem } from 'query:cache'

TypeScript

  • Strict mode enabled with noUnusedLocals, noUnusedParameters, noImplicitReturns
  • Use interface for object shapes, type for unions and function signatures
  • Use readonly on interface properties
  • Generic type parameters with defaults: <T = unknown>
  • Explicit type assertions when needed: as T

Naming Conventions

Element Convention Example
Files (modules) camelCase useQuery.ts, cache.ts
Files (components) PascalCase QueryProvider.tsx
Test files .test.ts/.test.tsx suffix query.test.ts
Functions camelCase createQuery, defaultFetcher
React hooks use prefix useQuery, useQueryActions
Types/Interfaces PascalCase Cache, Configuration
Function types *Function suffix FetcherFunction, MutationFunction
Props interfaces *Props suffix QueryProviderProps

Functions

  • Use function declarations for named/exported functions (not arrow functions)
  • Use arrow functions only for callbacks and inline functions
  • Use async/await for async code
// Correct - regular function for exports
export function createQuery(options?: Configuration): Query {
  // ...
}

// Correct - arrow for callbacks
events.addEventListener(`${event}:${key}`, listener)

Exports

  • Named exports only - never use default exports
  • Use barrel files (index.ts) for re-exports
  • Factory pattern for main API: createQuery() returns object with methods

Error Handling

  • Simple throw new Error('message') for errors
  • Try-catch with event emission pattern for async operations
  • Explicit empty catch catch(() => {}) when intentionally silencing errors

Comments

  • JSDoc-style block comments for function documentation
  • Inline comments for explaining specific logic
  • Document interface properties with JSDoc
/**
 * Subscribes to a given keyed event.
 */
function subscribe<T = unknown>(...) {
  // For the refetching event, we want to immediately return...
}

Testing

  • Test framework: Vitest with happy-dom environment
  • Use describe.concurrent() for parallel test execution
  • Destructure expect from test context: it('...', async ({ expect }) => { ... })
  • React tests use act() and createRoot
import { describe, it, vi } from 'vitest'

describe.concurrent('feature', function () {
  it('does something', async ({ expect }) => {
    // test code
    expect(result).toBe(expected)
  })
})

OxLint

  • Configuration: .oxlintrc.json
  • React plugin enabled with hooks rules
  • Vitest plugin enabled for test files
  • TypeScript plugin enabled
  • react-in-jsx-scope rule disabled (using new JSX transform)
  • Use // oxlint-disable-next-line to disable rules inline

React Compiler

This project uses React Compiler (babel-plugin-react-compiler v1.0.0+) to automatically optimize React components at build time.

Build Configuration

  • Runs via @rolldown/plugin-babel with reactCompilerPreset() exported from @vitejs/plugin-react
  • Applied only to src/react/**/*.tsx files
  • Plugin order in vite.config.ts: react() (JSX transform) then babel() (compiler) — this is the officially recommended order
  • Peer dependencies: @babel/core, @rolldown/plugin-babel, babel-plugin-react-compiler

Rules for React Code

  • Do NOT use useMemo, useCallback, or React.memo — the compiler handles memoization automatically
  • Do NOT mutate props, state, or values returned from hooks — the compiler assumes immutability
  • All React code must strictly follow the Rules of React
  • useEffectEvent is used for stable event handler references that should not be listed as effect dependencies
  • Prefer function declarations for components (not arrow functions assigned to variables)

Commit Conventions

This project uses Conventional Commits and git-cliff for automated changelog generation and version bumping.

  • feat: new functionality (triggers minor bump)
  • fix: bug fix in library code (triggers patch bump)
  • docs: documentation changes
  • refactor, perf, style, test: non-breaking improvements

Important: Use chore (not fix or feat) for CI, build, or tooling changes that don't affect the published library. For example, use chore(ci): fix workflow instead of fix(ci): fix workflow. Using fix or feat for CI-only changes will trigger an unnecessary version bump and release.

CI/CD

The project uses a single GitHub Actions workflow (main.yml) with three jobs:

  1. check — lint, format check, tests
  2. build — verifies the build succeeds (depends on check)
  3. release — version bump, changelog, npm publish, GitHub Release (depends on build, only on push to main)

The build job does not verify the build on PRs — only the release job builds before publishing. The separate build job exists as an early CI gate so build failures are caught before the release job runs. The release job builds again from scratch because GitHub Actions jobs don't share artifacts between runners.

npm publish uses --ignore-scripts to avoid double-building (the release job already ran build:only before publishing, so prepack is unnecessary).

Environment

  • Node.js 25+ (see .nvmrc)
  • npm 11+ (package manager)
  • TypeScript ~5.9.3
  • React 19.2+ (peer dependency)