Skip to content

Project structure ​

Castor is a pnpm monorepo. Run every pnpm command from the repo root; the scripts in the root package.json forward to the relevant package.

Top level ​

text
castorjs/
├── package.json              # Workspace root scripts (dev / verify / scaffold / db:* ...)
├── pnpm-workspace.yaml
├── apps/
│   ├── api/                  # @castorjs/api: Fastify backend
│   ├── web/                  # @castorjs/web: React frontend
│   └── mcp/                  # @castorjs/mcp: MCP Server
├── docs/
│   ├── architecture.md       # Architecture notes and design decisions
│   ├── frontend-design-system.md  # Frontend UI system (shadcn/ui)
│   ├── apifox-full.openapi.json   # OpenAPI document
│   ├── spec.schema.json      # JSON Schema of module specs (scaffold --spec)
│   ├── examples/specs/       # Requirement → spec examples
│   ├── roadmap.md            # Planned features (read the matching section before building one)
│   └── templates/            # Code scaffold templates (backend/, frontend/)
├── website/                  # This docs site (VitePress; a standalone npm project, not in the pnpm workspace)
├── AGENTS.md                 # Shared project context for all AI tools
├── CLAUDE.md                 # Claude Code additions
├── .claude/ .agents/ .github/   # AI skills (.agents mirrors .claude/skills); .github holds CI, the docs deployment and issue templates
├── scripts/                  # setup.sh (one-command Docker setup), docker-entrypoint.sh (image entry point)
└── Dockerfile / docker-compose.yml / render.yaml

For what each AI-related file does, see AI-driven workflow.

Backend: apps/api ​

text
apps/api/
├── src/
│   ├── main.ts               # Web process entry point
│   ├── worker.ts             # Entry point of the standalone scheduler process
│   ├── app.ts                # buildApp(): plugins, routes, error handling, static assets, SPA fallback
│   ├── config.ts             # Per-environment config (Zod-validated; refuses to start in production if key variables are missing)
│   ├── router.ts             # Top-level route wiring; register new business domains here
│   ├── common/               # Cross-cutting concerns (main files only)
│   │   ├── auth.ts           # loginRequired / hasMenuPermission / hasAnyMenuPermission / menuPermissionRequired
│   │   ├── rbac.ts           # Pure permission-check functions
│   │   ├── csrf.ts           # Double-submit CSRF check
│   │   ├── errors.ts         # ServiceError and unified error handling
│   │   ├── db-errors.ts      # Database constraint errors → 400 business errors
│   │   ├── http.ts           # intParam / parseIntParam / notFound / queryString / getUploadedFile
│   │   ├── validation.ts     # field.* / routeBody: request body declarations (Zod)
│   │   ├── pagination.ts     # parsePagination (default 20, max 200)
│   │   ├── serialize.ts      # toIso() and other time output helpers
│   │   ├── time-zone.ts      # X-Time-Zone request header, withZoneOffset()
│   │   ├── data-scope.ts     # Row-level data scope (resolveDataScope / dataScopeWhere)
│   │   ├── api-token.ts      # Bearer API token authentication
│   │   ├── tabular.ts        # csv / xlsx read and write
│   │   ├── i18n.ts           # Translates response messages based on Accept-Language
│   │   ├── storage/          # File storage drivers (local / s3)
│   │   └── scheduler/        # Scheduled task runner, cron matcher, SSRF protection
│   ├── i18n/messages.ts      # English / Japanese translations of backend errors
│   ├── demo/                 # Public demo (DEMO_MODE) fixtures and reset
│   ├── db/
│   │   ├── client.ts         # pg connection pool + Drizzle instance
│   │   ├── readonly.ts       # Read-only connection pool for AI Data Query
│   │   ├── migrate.ts        # Migration runner
│   │   ├── migrate-cli.ts    # Entry point of pnpm db:migrate
│   │   └── schema/           # Model layer: Drizzle table definitions, one directory per domain, re-exported from index.ts;
│   │                         #   columns.ts holds the shared createdAt() / updatedAt() columns
│   └── modules/
│       ├── admin/            # System domain: auth / users / roles / departments / menu / logs / dicts /
│       │                     #   files / settings / sessions / two-factor / password-reset / api-tokens /
│       │                     #   webhooks / scheduled-task / notification / announcement / dashboard / assistant
│       └── component-center/ # Component Gallery domain: demo-record / ai-chat / ai-prompt / ai-sql /
│                             #   devtools / traffic-flow
├── drizzle/                  # SQL migration files + meta/_journal.json (generated by drizzle-kit)
├── scripts/                  # Toolchain: scaffold / verify-feature / seed-rbac / seed-demo / setup-once /
│                             #   init-ro-role / generate-openapi / import-apifox / demo-reset; lib/ holds shared helpers
├── test/                     # Vitest, runs against a real PostgreSQL
└── drizzle.config.ts

Each feature module consists of one table definition file and one module directory:

text
src/db/schema/<domain>/<name>.ts                 # Table definition + toDict serializer
src/modules/<domain>/<name>/schema.ts            # Zod request schemas, import/export field maps
src/modules/<domain>/<name>/repository.ts        # Database reads and writes
src/modules/<domain>/<name>/service.ts           # Business logic
src/modules/<domain>/<name>/routes.ts            # Routes and permission checks

Table definitions live together under db/schema/ because drizzle-kit needs a single schema entry point. The other four layers sit next to each other per feature, so adding a feature only means creating files in one directory. For the layering rules, see Backend.

Frontend: apps/web ​

text
apps/web/
├── components.json           # shadcn CLI config
├── scripts/
│   ├── shadcn-add.sh         # Runs npx shadcn@latest add through a local relay
│   ├── i18n-scan.mjs         # Scanner for untranslated text
│   └── api-types.mjs         # Generates src/shared/api/openapi.d.ts from the OpenAPI document
├── test/                     # Vitest (i18n, appearance, tabs bar, shared components, ...)
└── src/
    ├── App.tsx               # Dynamic routing (resolves menu pages through lib/page-modules.ts)
    ├── index.css             # Tailwind v4 entry + design tokens (light / dark / accent colors)
    ├── i18n/index.ts         # i18next setup
    ├── locales/              # Shared UI translations; menus/ holds menu name translations
    ├── context/              # AuthContext / ThemeContext / TagsViewContext
    ├── components/
    │   ├── ui/               # shadcn/ui primitives (source lives in the repo)
    │   ├── ai-elements/      # AI chat building blocks (conversation, message, prompt input, ...)
    │   └── app/              # App shell: AppLayout / AppSidebar / TopBar / TopNav / TagsView /
    │                         #   AppearanceMenu / CommandMenu / LanguageSwitcher / ...
    ├── lib/                  # page-modules (import.meta.glob page lookup) / cn / toast / format / motion /
    │                         #   chart-theme / menu-icons / appearance / ...
    ├── modules/
    │   ├── auth/pages/{login,reset_password}/  # Sign-in and password reset pages
    │   ├── admin/{pages,api,components}/       # System pages, API clients and page components
    │   └── component_center/
    │       ├── pages/{patterns,components,dataviz,ai,editor,devtools}/
    │       ├── showcase/     # Layout kit of the components/ pages (ShowcasePage / Example / PropsTable)
    │       └── api/
    └── shared/
        ├── api/request.ts    # Axios instance (baseURL '/api'; sends the CSRF header, Accept-Language and X-Time-Zone automatically)
        ├── api/openapi.d.ts  # API types generated from the OpenAPI document
        ├── hooks/            # useCrudList / useDebouncedValue / useIsMobile / useDictOptions / useAppInfo
        ├── utils/file.ts     # downloadBlobFile
        └── components/       # Shared business components: PageHeader / DataTable / FormDialog / ...

A menu's component value <module>/<page_path> maps to the file modules/<module>/pages/<page_path>/index.tsx (for example admin/users → modules/admin/pages/users/index.tsx, component_center/patterns/kanban_page → modules/component_center/pages/patterns/kanban_page/index.tsx); a page anywhere else isn't found by dynamic routing. See Frontend.

MCP Server: apps/mcp ​

apps/mcp/src/index.ts wraps the toolchain as MCP tools for MCP clients such as Claude Desktop. See AI-driven workflow.

Naming conventions ​

WhatRuleExample
Project and package namesLowercase, hyphenatedcastorjs, @castorjs/api
Backend directory and file namesLowercase, hyphenatedcomponent-center, scheduled-task
Database table namesUnderscoresscheduled_tasks
Frontend directories, menu component fieldUnderscorescomponent_center/patterns/card_list_page
API pathsHyphenated, plural/api/admin/customer-orders

The session cookie name castor_session is an exception and uses an underscore.

Released under the MIT License.