Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grantmaster.dev/llms.txt

Use this file to discover all available pages before exploring further.

Shared Package Architecture

Purpose

@grantmaster/shared is a pure TypeScript package that lives in the packages/shared/ workspace. Its sole purpose is to share code that must be identical across two runtime environments:
  • Frontend (src/) — the React web application
  • Cloud Functions (functions/) — Firebase Cloud Functions (Node.js)
Because it is consumed by both environments it has a strict rule: no Firebase imports. Firebase SDKs differ between the browser client and the Admin SDK used in Functions. Any Firebase-specific code lives in the consumer package, not here. The only runtime dependency is zod (for schema definitions that are validated in both environments).

Workspace Setup

GrantMaster uses npm workspaces. The root package.json declares:
{
  "workspaces": ["packages/*", "functions"]
}
This means packages/shared/ and functions/ are both workspace members. The frontend app at src/ is the workspace root itself. Both consumers reference the package by name:
// functions/package.json
{
  "dependencies": {
    "@grantmaster/shared": "*"
  }
}
The * version resolves to the local workspace copy. No publishing to npm is required.

Package Structure

packages/shared/
├── package.json          # Package manifest with named exports
└── src/
    ├── index.ts          # Barrel re-export of all sub-modules
    ├── types/
    │   ├── index.ts
    │   ├── enums.ts      # Domain enums and constants
    │   ├── common.ts     # Base document interfaces
    │   ├── entities.ts   # Minimal entity shapes for Functions
    │   └── intelligence.ts # Cross-org analytics types
    ├── schemas/
    │   ├── index.ts
    │   └── common.schema.ts # Zod schemas for shared validation
    ├── errors/
    │   ├── index.ts
    │   ├── AppError.ts   # Error hierarchy base classes
    │   └── domain.ts     # Domain-specific error classes
    ├── events/
    │   └── index.ts      # SystemEventType enum and typed payloads
    └── config/
        ├── index.ts
        └── entitlements.ts # Feature flags and subscription limits

Named Exports (package.json)

{
  "exports": {
    ".":          "./src/index.ts",
    "./events":   "./src/events.ts",
    "./schemas":  "./src/schemas/index.ts",
    "./errors":   "./src/errors/index.ts",
    "./events":   "./src/events/index.ts",
    "./config":   "./src/config/index.ts"
  }
}
Consumers can import from the root or from a named sub-path:
import { SystemRole } from '@grantmaster/shared';
import { SystemRole } from '@grantmaster/shared/types';

What Gets Shared

Types (@grantmaster/shared/types)

enums.ts — Domain Enumerations and Constants

ExportDescription
SystemRoleSUPER_ADMIN, ADMIN, MANAGER, MEMBER, AUDITOR
Permission200+ fine-grained permission strings
SubscriptionTierPOTENTIAL, PROFESSIONAL, ULTIMATE
ProjectPhaseProject lifecycle phases
GrantorTypeGovernment, Foundation, Corporate, Individual, Other
ORG_SIZE_THRESHOLDSObject with SMALL, MEDIUM, LARGE numeric thresholds
DATA_CATEGORIESConstants for data classification
SystemRole and Permission are the most heavily used — they appear in Firestore security rules, Cloud Function auth checks, and frontend RBAC alike.

common.ts — Base Document Interfaces

interface BaseDocument {
  id: string;
  organizationId: string;
  createdAt: TimestampLike;
  updatedAt: TimestampLike;
}

interface AuditedDocument extends BaseDocument {
  createdBy: string;
  createdByName: string;
  updatedBy: string;
  updatedByName: string;
}
TimestampLike is a structural type that matches both firebase/firestore.Timestamp and plain { seconds: number; nanoseconds: number } objects, so it works without importing Firebase. All Firestore documents that follow the platform convention extend one of these two interfaces.

entities.ts — Minimal Entity Shapes

Contains trimmed-down interfaces for entities that Cloud Functions need to read but do not need the full frontend type for:
  • ProjectBase — id, organizationId, name, phase, budget
  • EmployeeBase — id, organizationId, displayName, email, systemRole
These exist to avoid shipping large frontend-only fields (computed properties, UI state) into the Functions bundle.

intelligence.ts — Cross-Org Analytics

Types used by the intelligence platform for aggregating data across organisations. These are consumed by Cloud Functions that power the Superadmin analytics features.

Schemas (@grantmaster/shared/schemas)

Zod schemas validated identically on both frontend (form validation) and backend (Cloud Function input validation):
SchemaValidates
uuidSchemaRFC 4122 UUID strings
organizationIdSchemaNon-empty string (Firestore document id format)
FirestoreTimestampLikeDuck-type check for Timestamp-shaped objects
AddressStreet, city, state/province, postal code, country
AttachmentFile attachment metadata (name, url, mimeType, size)
BankAccountBank account details (accountNumber, routingNumber, bankName)
Rule: Do not add Firebase-specific types (e.g. DocumentReference, Timestamp) as schema field types. Use FirestoreTimestampLike or plain strings instead.

Errors (@grantmaster/shared/errors)

A class hierarchy for typed error handling that is consistent across the frontend and Cloud Functions:
AppError (base)
├── UserError          # 4xx — caused by user input or state
│   ├── ValidationError
│   ├── AuthenticationError
│   ├── AuthorizationError
│   └── NotFoundError
└── SystemError        # 5xx — internal failures
    ├── FirestoreError
    ├── StorageError
    ├── NetworkError
    ├── RateLimitError
    ├── BusinessLogicError
    ├── ExternalAPIError
    ├── AIServiceError
    └── EmailServiceError
Domain errors in domain.ts build on the base classes for platform-specific scenarios:
ClassWhen to throw
RateLimitExceededErrorQuota exceeded (API calls, AI generations, agent credits)
TenantIsolationErrorCross-org data access attempt detected
FeatureNotAvailableErrorFeature not available on the organisation’s subscription tier
All AppError subclasses carry an ErrorSeverity (INFO, WARNING, ERROR, CRITICAL) and an ErrorCategory for structured logging.

Events (@grantmaster/shared/events)

The event system is the backbone of the platform’s audit trail and notification pipeline. See docs/engineering/architecture/base-service-and-eventbus.md for the full EventBus rule.

SystemEventType

A string enum with 100+ event types organised by domain:
Domain groupExamples
JournalsJOURNAL_SUBMITTED, JOURNAL_APPROVED, JOURNAL_REJECTED
ExpensesEXPENSE_SUBMITTED, EXPENSE_APPROVED, EXPENSE_FLAGGED
ComplianceCOMPLIANCE_OVERDUE, COMPLIANCE_COMPLETED
BudgetBUDGET_THRESHOLD_80, BUDGET_THRESHOLD_90, BUDGET_EXCEEDED
Project lifecyclePROJECT_CREATED, PROJECT_ARCHIVED, PROJECT_PHASE_CHANGED
EmployeeEMPLOYEE_ONBOARDED, EMPLOYEE_OFFBOARDED, EMPLOYEE_ROLE_CHANGED
GrantsGRANT_APPLICATION_SUBMITTED, GRANT_AWARDED, GRANT_REJECTED
AgentAGENT_RUN_STARTED, AGENT_RUN_COMPLETED, AGENT_RUN_FAILED
CreditsCREDITS_CONSUMED, CREDITS_LOW, CREDITS_EXHAUSTED
Impact/M&EME_INDICATOR_UPDATED, ME_REPORT_SUBMITTED

SystemEvent<T> Interface

interface SystemEvent<T = unknown> {
  type: SystemEventType;
  organizationId: string;
  userId: string;
  severity: EventSeverity;
  timestamp: TimestampLike;
  payload: T;
  metadata?: Record<string, unknown>;
}
Each event type has a corresponding typed payload interface (e.g. JournalEventPayload, ExpenseEventPayload).

Type Guards

isJournalEvent(event: SystemEvent): event is SystemEvent<JournalEventPayload>
isExpenseEvent(event: SystemEvent): event is SystemEvent<ExpenseEventPayload>
// ... one per domain group

requiresPersistence(eventType)

Returns true for event types that must be written to Firestore for the audit trail. These include: audit events, compliance events, approval workflow events, budget threshold events, employee lifecycle events, project lifecycle events, agent events, credit events, and M&E events. Read operations and trivial updates return false.

Config (@grantmaster/shared/config)

Subscription tier entitlements and feature flags used by both the frontend (to gate UI) and Cloud Functions (to enforce limits server-side).

Feature Enum

40+ feature identifiers, e.g. AI_GRANTS_DISCOVERY, ADVANCED_REPORTING, CUSTOM_ROLES, MULTI_ORG, AGENT_SYSTEM.

TierLimits and TIER_LIMITS

interface TierLimits {
  maxUsers: number;
  maxProjects: number;
  maxStorageGB: number;
  maxApiCallsPerMonth: number;
  maxAIGenerationsPerMonth: number;
  maxAgentCreditsPerMonth: number;
  maxAgentRunsPerMonth: number;
}
TierMax usersMax projectsStorage
POTENTIAL3105 GB
PROFESSIONAL155050 GB
ULTIMATE25unlimitedunlimited

Helper Functions

FunctionDescription
getMinimumTierForFeature(feature)Lowest tier that includes this feature
tierIncludesFeature(tier, feature)Boolean check
getFeaturesForTier(tier)All features available at a tier
getLimitsForTier(tier)TierLimits object for a tier

How to Add New Shared Code

Decision checklist

Add code to @grantmaster/shared only if it meets all of the following criteria:
  • It is consumed by at least two of: frontend, Cloud Functions
  • It contains no Firebase imports (no firebase/app, firebase/firestore, firebase-admin/*)
  • It contains no browser APIs (window, document, localStorage)
  • It contains no Node.js built-ins that are not available in both environments
If any criterion fails, put the code in the consumer package instead.

Steps

  1. Choose the right sub-module based on what you are adding:
    What you are addingSub-module
    A domain enum or constantowning shared or feature contract module
    A base document interfaceowning schema or shared contract module
    A minimal entity shape for Functionsowning shared contract module
    A Zod validation schemasrc/schemas/common.schema.ts
    A new error classsrc/errors/AppError.ts or src/errors/domain.ts
    A new event typesrc/events/index.ts
    A feature flag or tier limitsrc/config/entitlements.ts
  2. Write the code in the chosen file. Follow the existing style — use export on all public symbols.
  3. Re-export from the sub-module’s index.ts if the file has one.
  4. Verify the root barrel (src/index.ts) re-exports the sub-module. It should already, since it re-exports ./types, ./schemas, ./errors, ./events, and ./config wholesale.
  5. Run the TypeScript compiler across both consumers to catch any issues:
    # From repo root
    npm run build           # checks frontend
    npm run build --workspace=functions  # checks Cloud Functions
    
  6. Do not add new runtime dependencies without team discussion. The package intentionally has only one runtime dependency (zod). Adding Firebase, Lodash, or similar libraries defeats the purpose of the package being environment-agnostic.

Adding a New Event Type

Events require more steps than other additions because each type needs a payload interface and (potentially) persistence registration:
  1. Add the new value to SystemEventType in src/events/index.ts.
  2. Define a typed payload interface (e.g. MyDomainEventPayload).
  3. Extend the relevant type guard or add a new one.
  4. Update requiresPersistence() if the event must be written to Firestore.
  5. In the consuming service, emit via EventBus.emit(SystemEventType.MY_EVENT, payload) following the EventBus rule in docs/engineering/architecture/base-service-and-eventbus.md.

Adding a New Error Class

  1. Decide whether it is a UserError (client mistake) or SystemError (internal failure).
  2. Extend the appropriate base class in src/errors/AppError.ts (for general errors) or src/errors/domain.ts (for platform-specific errors).
  3. Set a meaningful severity and category in the constructor.
  4. Export from src/errors/index.ts.

Dependency Rule Summary

Allowed in @grantmaster/sharedNot allowed
Pure TypeScriptFirebase client SDK
Zod schemasFirebase Admin SDK
Enums and constants@tanstack/react-query
Structural type definitionsReact or JSX
Error class hierarchiesBrowser globals (window, fetch)
Event type enumerationsNode built-ins (fs, path, crypto)