Skip to content

Architecture

One npm package, not a monorepo. examples/* are pnpm workspace members that consume the package; website is this documentation site (Astro with Starlight), also a workspace member.

Folder Entry Runs in May import
src/schema protobase/schema anywhere nothing from other src folders
src/query protobase/query server schema
src/ui protobase/ui browser schema
src/server protobase/server server schema, query
src/cli bin/protobase.mjs Node; src/cli/serve also Bun schema, query, server
src/fields/<type>/ none schema.ts anywhere, sql.ts server, ui.tsx browser schema, per the rules below
examples/* none any protobase/* entry points only

Rules, enforced by pnpm check:boundaries (.dependency-cruiser.cjs):

  • src/ui/** and fields/*/ui.tsx never import query, server, cli or fields/*/sql.ts.
  • src/schema/** and fields/*/schema.ts never import query, server, ui or cli.
  • src/query/** never imports ui, server or cli.
  • Folders import each other only through index.ts, never deep paths (except src/fields/*).
  • No circular dependencies.

Entry points export source directly; the package has no build step. The builds are for deployment: the serve runtime, src/cli/serve/main.ts bundled with its dependencies into dist/protobase-serve.js by protobase build-serve, and a project’s bundle, its config module plus a production build of src/ui/app, by protobase build (see CLI).

  • Unit tests: *.test.ts next to the code, run by Vitest in a node environment.
  • Stories: *.stories.tsx next to the component in src/ui; Storybook config lives in .storybook/.

src/schema/model.ts is the contract between folders. Change its types deliberately; every folder builds against them.

Filters are Google AIP-160 text. Parsing, printing, order_by and the generic checker come from the standalone aip-parsers library (published on npm, entry points aip-parsers/filter and aip-parsers/order-by). src/schema/filter/ is the Protobase profile on top of it:

  • profile.ts registers Protobase’s functions (in, search, similar, regex, isNull, now) and turns a ResourceModel into the library’s schema.
  • parse.ts / lower.ts turn the generic tree into FilterExpr; lift.ts / print.ts go back.
  • check.ts runs the library checker, then check-model.ts for value formats (decimal, bigint, uuid, date, …), traversal and search fields. search(...) and bare words need .search((r) => [...]) on the resource.
  • where.ts builds typed filters in code.

ResourceModel.search is authoritative for server-side search; ViewModel.list.search only seeds the UI’s search box.

website/ is this site. pnpm docs:dev serves it on port 4321; pnpm docs:build writes website/dist/ and fails on a broken internal link or anchor, so CI runs it on every pull request, followed by pnpm docs:check, which opens every page in Chromium and fails on a console error or a failed request. Pages are Markdown or MDX in website/src/content/docs/, and the sidebar is listed in website/astro.config.mjs.

Every push to the production branch builds the site and deploys it to the Cloudflare Pages project protobase-docs, served at docs.protobase.net (.github/workflows/docs.yml). The workflow needs two repository secrets: CLOUDFLARE_API_TOKEN, a token with Cloudflare Pages edit permission, and CLOUDFLARE_ACCOUNT_ID.