Litestore · Developer guide

Structure

Learn the repository layout, the conventions the harness enforces, and where a new module, block or integration belongs.

The repository has twenty top-level directories and no monorepo packages. Everything ships from one project, so a storefront page imports a domain rule directly rather than across a package boundary.

This page names what each directory holds, the conventions the harness enforces across them, and where a new piece of code belongs.

app/
  (web)/              storefront routes — pages only, no components
  admin/              admin routes — pages only, no components
  ai/                 v1/ route handlers + services/ for the admin AI generators
  api/                public API, webhooks, MCP, oRPC, Inngest, health
components/
  common/             shared primitives (button, dialog, calendar, …)
  web/                storefront components, including blocks/renderers/
  admin/              admin components, one folder per section
  data-table/         the TanStack Table wrapper used by every admin list
  shared/             components used by both surfaces
server/
  admin/<module>/     actions.ts · queries.ts · crud.ts · schema.ts · payloads.ts
  web/                storefront read paths and the page resolver
  ai/                 the gated agent tool kit and registry
  customers/          find-or-reconcile for the signed-in customer
  messaging/          conversations and alert delivery
  shared/             settings read model, facets, scarcity, policy windows
  router.ts           the oRPC root router
lib/                  domain logic — cache.ts, blocks/, permissions/, crud/, orders/, …
services/             infrastructure — db.ts, stripe.ts, redis.ts, s3.ts, resend.ts, integrations/
functions/            Inngest functions: crons and domain-event handlers
contracts/            shared primitive and operation schemas (id, email, pagination, sorting)
config/               site, theme, pricing, fonts and activity-registry configuration
emails/               React Email templates
events/               types.ts (the domain event union) and emitter.ts
hooks/                client hooks
utils/                pure helpers — money, decimal, try-catch, search-params
types/                ambient and shared type declarations
prisma/
  schema.prisma       the schema — the only source of truth for it
  migrations/         the history
  seed.ts             the seed, run by bun run db:seed
scripts/
  check-*.ts          the harness: 25 discipline checks and ratchets
test/                 integration setup and factories
docs/                 architecture and migration notes
assets/               icon sources, compiled by bun run icons
public/               static files served as-is
patches/              bun patch files (currently zsa@0.6.0)

Key conventions

The layout above is not a suggestion. Most of it is checked by a script, so a file in the wrong place fails the build rather than becoming a precedent.

Route folders contain routes only

app/ holds Next.js conventions and nothing else:

page.tsx
layout.tsx
loading.tsx
error.tsx
route handlers

Route-specific UI lives in components/admin/<section>/ or components/web/<section>/, and there are no _components/ folders anywhere in app/.

scripts/check-discipline.ts rejects inline list rendering in a route page.tsx for the same reason: a route file that renders a list is a route file that has started to hold components.

Every mutation is a permission-wrapped server action

Actions live in server/<area>/<module>/actions.ts and declare their permission through modulePermission/withAnyPermission from lib/permissions/server.

Persistence goes through the module's CRUD class, which extends BaseCRUD and handles soft delete, activity logging and cache invalidation as one unit — not as three things a caller has to remember separately.

Reads are separate from writes

Queries live beside actions in queries.ts, wrapped in unstable_cache and tagged from CACHE_TAGS.

The tag is what connects the two halves: the write invalidates the same tag the read was cached under.

One module, one folder

A module's actions, queries, CRUD, zod schemas and Prisma payloads sit together, so everything about — say — coupons is in server/admin/coupons/.

Boundaries are enforced automatically

Three scripts fail the build on imports that cross the wrong way:

scripts/check-client-server-boundary.ts
scripts/check-theme-boundaries.ts
scripts/check-client-bundle-server-only.ts

See The harness.

Where to put a new thing

A storefront section is a block kind: a registry entry in lib/blocks/registry.ts plus a renderer in components/web/blocks/renderers/.

An admin entity is a pair of folders, server/admin/<module>/ and components/admin/<module>/ — see Modules.

A reaction to an event is a handler in functions/ — see Events.

A third-party integration goes in services/integrations/, behind the existing interface.

A shared rule or format goes in lib/ if it is a domain rule and utils/ if it is a pure helper — one canonical home, imported everywhere.

The question to ask when adding a file is not "where would this fit?" It is:

Which folder already owns this responsibility, and what is the nearest existing file doing the same job?

Next Steps

On this page