6. Shared Code Lives in shared/ Folders with Path Aliases, Not Nx or Publishable Packages
Status
Accepted — 2026-02-18 (project inception)
Context
The workspace has cross-package code: models, constants, utility functions, and services used by the frontend, Cloud Functions, and the build tools. The conventional “correct” options at this scale are an Nx monorepo with generated libraries or publishable npm packages in a Lerna-style setup. Both impose meaningful tooling overhead — library project scaffolding, package versioning, build graphs, publish pipelines — and both assume the code will eventually be consumed by something outside this repository.
Decision
Shared code lives in top-level folders (shared/models, shared/constants, shared/utils, shared/services), exposed through TypeScript path aliases in tsconfig.base.json:
@jjk/models→shared/models/index.ts@jjk/constants→shared/constants/index.ts@jjk/utils→shared/utils/index.ts@jjk/services→shared/services/index.ts@jjk/shared/*→shared/*@app/core/*,@app/shared/*,@app/content→ frontend-internal paths
npm workspaces declare frontend, tools/build, tools/blog-scheduler, and shared as the workspace members. There is no Angular library project, no publishable package, no build graph.
Consequences
- Adding shared code is moving a file and exporting it from a barrel. No generator, no build step, no version bump.
- Imports are stable across the repo.
@jjk/modelsmeans the same thing from a component, a Cloud Function, or a build-pipeline stage. - There is no enforcement of dependency direction between folders.
shared/is by convention leaf code — it must not import fromfrontend/orfunctions/— and that convention is held by reviewer discipline, not tooling. Drift is possible. (See amendment 2026-04-19.) - The code is not publishable. If something in
shared/ever needs to be consumed by a separate repository, it would need to be extracted into a real package at that point. - Refactoring a shared export is a repo-wide find-and-replace, not a package version bump. Fast in a single-consumer codebase; would be painful with external consumers.
- Onboarding a future maintainer who expects Nx is a mismatch; the lack of library tooling is a deliberate cost savings, not an oversight.
Amendment — 2026-04-19
The original Consequences noted that the leaf-code direction was held by reviewer discipline only. That gap is now closed. tools/lint/check-shared-boundaries.ts walks every .ts file under shared/ and fails the build if any module inside shared/ imports from frontend/, functions/, or tools/. shared/ being imported by those packages is the whole point and is unaffected; what the check forbids is shared/ reaching back into its consumers (via @app/* aliases, relative paths that escape shared/, or bare specifiers that collide with workspace directories). The check runs as part of npm run build (and therefore in every CI deploy workflow) and is also runnable locally as npm run lint:boundaries.
See diagrams/dependency-graph.md for the current consumer-to-leaf shape.