Shared Types
Overview
The @pos/shared-types package is the single source of truth for TypeScript interfaces used across the entire system. It is consumed by the HQ server, store server, web client, and all other packages.
Package Structure
packages/shared-types/
src/
index.ts # Re-exports everything
product.ts # Product, Department, TaxRate, TaxGroup, Supplier, etc.
sale.ts # Sale, SaleItem, SalePayment, SaleStatus
user.ts # User, Permission, Role, RolePermission, UserStoreRole
store.ts # Store, StoreConfig, StoreInventory, StoreDashboardEntry
sync.ts # SyncOutboxEntry, SyncLog, SyncEnvelope, SyncEntityType
api.ts # ApiResponse, PaginatedResponse, LoginRequest/Response, SyncDashboard types
inventory.ts # Inventory types
transfer.ts # Transfer types
register.ts # Register session types
purchase-order.ts # PurchaseOrder, PurchaseOrderItem
audit.ts # Audit log types
worksheet.ts # Worksheet, WorksheetItem, WorksheetStore
sales-rep.ts # SalesRep type
module.ts # ModuleDefinition
tsconfig.json
package.jsonWhat Goes in Shared Types
Put in shared-types:
- Interfaces that represent database entities (Product, Sale, User, etc.)
- API request/response shapes (LoginRequest, ApiResponse, etc.)
- Enum-like union types (SaleStatus, SyncDirection, WorksheetStatus)
- Types used by more than one package or app
Keep inline (do NOT put in shared-types):
- Zod schemas (validation is server-specific)
- Drizzle table definitions (ORM-specific)
- Vue component props/emits types
- Internal implementation types used by only one module
Key Types
ApiResponse<T>
The standard response wrapper used by all API endpoints:
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}PaginatedResponse<T>
Extension for paginated list endpoints:
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
items: T[];
total: number;
page: number;
pageSize: number;
}Entity Interfaces
Every database entity has a corresponding TypeScript interface. These interfaces match the database column types with the numeric conventions applied:
export interface Product {
id: string; // UUID
sku: string;
barcode: string | null;
name: string;
description: string | null;
departmentId: string | null;
costPrice: number; // cents (integer)
sellPrice: number; // cents (integer)
taxGroupId: string | null;
isActive: boolean;
trackStock: boolean;
updatedAt: string; // ISO timestamp (serialized from Date)
syncVersion: number;
}Note that updatedAt is string (not Date) because dates are JSON-serialized as ISO strings over the API.
Sync Types
export type SyncEntityType =
| 'product' | 'department' | 'tax_rate' | 'supplier'
| 'user' | 'role' | 'permission' | 'role_permission' | 'user_store_role'
| 'sale' | 'inventory_adjustment' | 'transfer' | 'register_session'
| 'monthly_closing' | 'product_supplier' | 'sales_rep'
| 'store_price' | 'purchase_order' | 'purchase_order_item';
export interface SyncEnvelope {
id: string;
storeId: string;
entityType: SyncEntityType;
entityId: string;
payload: unknown;
version: number;
timestamp: string;
}How TypeScript Catches API Drift
Because the HQ server, store server, web client, and backend adapter all import from the same @pos/shared-types package, TypeScript's type system catches mismatches at build time.
Example: Adding a Field
If you add a new required field to the Product interface:
export interface Product {
// ... existing fields ...
minOrderQuantity: number; // NEW
}The following will fail at typecheck:
- HQ server routes that return products without the new field
- Store server routes that return products without the new field
- Web client views that destructure products without handling the new field
- Backend adapter tests that mock products without the new field
This forces you to update all consumers in a single commit, preventing API drift.
Build-Time Verification
The deploy script runs tsc --noEmit for all packages and apps before building. If any type mismatch exists, the deploy fails before any Docker image is built:
# deploy.sh pipeline
pnpm --filter shared-types exec tsc --noEmit # Shared types must compile
pnpm --filter hq-server exec tsc --noEmit # HQ server must match types
pnpm --filter store-server exec tsc --noEmit # Store server must match types
cd apps/web-client && npx vue-tsc --noEmit # Web client must match typesConsuming Shared Types
In Server Code
import type { Product, ApiResponse } from '@pos/shared-types';
app.get('/', async (): Promise<ApiResponse<Product[]>> => {
const products = await db.select().from(productsTable);
return { success: true, data: products };
});In Vue Components
import type { Product } from '@pos/shared-types';
const products = ref<Product[]>([]);In Backend Adapter
import type { ApiResponse } from '@pos/shared-types';
const result = await adapter.execute<Product[]>('get_products');
// result is typed as ApiResponse<Product[]>Adding a New Type
- Create or update the type file in
packages/shared-types/src/ - Export it from
packages/shared-types/src/index.ts - Build the package:
pnpm --filter shared-types build - Use it in consuming packages (they will pick it up via the workspace link)
The monorepo workspace configuration ensures all apps reference the local version of @pos/shared-types (not a published npm version).