Skip to content

Shared Package

packages/shared (@ronl/shared) carries the TypeScript types and constant data shared across the workspace โ€” and, deliberately, no logic at all. It has no runtime dependencies and must be built before a consuming package can be built or type-checked.

It is consumed by the backend, the frontend and the PA-demo. The demo's imports are type-only and erased before the bundler sees them, so a shared-only change cannot alter its compiled output โ€” but packages/shared/** still appears in the demo's CI path filter, so that a breaking type change fails there rather than surfacing later at an unrelated pull request.

This is not the only workspace package

@ronl/pa-cockpit is the other one: the Public Affairs cockpit, imported by both the caseworker frontend and the public demo. Unlike shared it ships React components and carries a host contract.


Build

npm run build --workspace=@ronl/shared

Output is written to packages/shared/dist/. Both @ronl/backend and @ronl/frontend reference @ronl/shared: "*" in their package.json, resolving to the dist/index.js and dist/index.d.ts files.


Contents

The shared package exports the TypeScript types used across the system. Key interfaces include:

ApiResponse<T> โ€” standard response envelope used by all backend endpoints:

interface ApiResponse<T = unknown> {
  success: boolean;
  data?: T;
  error?: { code: string; message: string; };
  timestamp: string;
}

TenantConfig โ€” municipality tenant configuration shape:

interface TenantConfig {
  id: string;
  name: string;
  displayName: string;
  municipalityCode: string;
  theme: TenantTheme;
  features: TenantFeatures;
  contact: TenantContact;
  enabled: boolean;
}

JwtClaims โ€” decoded JWT payload type for req.user:

interface JwtClaims {
  sub: string;
  municipality: string;
  roles: string[];
  loa: string;
  preferred_username: string;
  mandate?: string;
  bsn?: string;
}


Kept declarations-only

@ronl/shared holds types and constant data, and nothing that executes. That is a checked property rather than a habit: npm run check-shared enforces it from the audit job, on every pull request.

Why it needs a check at all. The per-file 80% branch floor is enforced in the five workspaces that have a test runner. This package is not one of them, so a function placed here is not under-tested โ€” it is outside the measurement entirely, and nothing signals that: no run fails and no number moves. v2026.09.4 moved a branching label helper back out of this package for exactly that reason, found by hand rather than by any check.

Why the TypeScript compiler API rather than a pattern over text. A regex cannot tell (x: string) => void inside an interface โ€” a FunctionType, which is precisely what this package is for โ€” from the same syntax assigned to a const, which is an ArrowFunction and is not. It would also miss a function expression assigned to a const. The parser knows the difference, and a check with false positives gets disabled. Flagged: function declarations and expressions, arrow functions, class declarations, method implementations, and the branching statements โ€” if, switch, a ternary, the four loop forms, and try/catch. Not flagged: FunctionType and MethodSignature, and every type-level construct.

Why the audit job rather than a workspace suite. audit carries no paths filter and is the required check, so every pull request reaches it. A suite that only ran when packages/shared/** changed would not catch the pull request that adds the first function to a package that filter does not yet watch. Giving this package its own test runner was the other option, and it is worse today: with no executable lines to measure, the runner would report a green check over an empty set โ€” the same false comfort the coverage floor exists to remove.

A passing run reads:

check-shared-declarations: 11 file(s) in packages/shared/src/ โ€” declarations and constant data only.

A failure names the file, the line, and where the logic should live instead. If the package ever genuinely needs runtime logic, the documented path is to give it a Vitest runner with the same per-file floor and delete the script โ€” a deliberate change, not a workaround.


Development workflow

When you modify a type in packages/shared/src/, both the backend and frontend need to pick up the change:

# Rebuild shared
npm run build --workspace=@ronl/shared

# tsx watch (backend) and Vite HMR (frontend) pick up the rebuilt types automatically
# If they don't, restart npm run dev

The pre-push Husky hook runs npm run type-check across all workspaces, which catches type mismatches between shared types and their consumers before the push reaches CI.