TypeScript Configuration
CPR uses TypeScript 5.x with strict mode enabled. Nuxt 4 provides full TypeScript support with auto-generated type definitions, auto-imports, and path alias resolution.
Configuration
TypeScript is configured in two places:
nuxt.config.ts
// nuxt.config.ts
typescript: {
strict: true,
shim: false,
},strict: true-- Enables all strict type-checking options (strictNullChecks,strictFunctionTypes,strictBindCallApply,noImplicitAny, etc.). This catches more bugs at compile time and is strongly recommended for a medical application where data integrity matters.shim: false-- Disables the legacyshim.d.tsfile for.vueimports. Nuxt 4 generates proper type definitions through its.nuxt/directory, making the shim unnecessary.
tsconfig.json
The root tsconfig.json references Nuxt's generated TypeScript configs:
{
"files": [],
"references": [
{ "path": "./.nuxt/tsconfig.app.json" },
{ "path": "./.nuxt/tsconfig.server.json" },
{ "path": "./.nuxt/tsconfig.shared.json" },
{ "path": "./.nuxt/tsconfig.node.json" }
]
}These generated configs provide:
- Path aliases (
~/maps toapp/) - Auto-import type definitions (so
ref,computed,useRouteare typed without imports) - Vue SFC type support
- Module resolution for
.vuefiles
Type Definition Patterns
Domain Types
CPR defines domain types in app/types/. Each domain has its own type file:
// app/types/patient.type.ts
export interface Patient {
id: number;
pin: string;
last_name: string;
first_name: string;
middle_name: string;
birthdate: string;
sex: string;
civil_status: string;
email: string;
address: string;
photo_url: string;
categories: PatientCategory[];
status: string;
created_at: string;
updated_at: string;
}
export interface PatientCategory {
id: string;
name: string;
}// app/types/branch.type.ts
export interface Branch {
id: string;
name: string;
address: string;
code?: string;
email?: string;
is_active?: boolean;
phone?: string;
}// app/types/user.type.ts
export interface AuthUser {
id: number;
name: string;
username: string;
email: string;
phone?: string;
role?: string[];
permissions?: string[];
default_branch?: Branch;
}API Response Types
Standard API response wrappers are defined in app/types/apiReponse.type.ts:
// Single resource response
export interface ApiResponse<T> {
data: T;
message: string;
}
// Paginated response (inline pagination)
export interface PaginatedReponse<T> {
data: paginatedData<T>;
message: string;
}
// Paginated response (Laravel Resource style)
export interface PaginatedResourceResponse<T> {
data: T[];
meta: MetaResponse;
links: LinkResponse;
message: string;
}
// Generic API request parameters
export interface GetApiParams<T> {
search?: string;
filters?: T;
page?: number;
perPage?: number;
sortField?: string;
sortDirection?: 'asc' | 'desc';
}Component Props
Vue components use TypeScript interfaces for prop definitions:
// From UiButton.vue
interface Props {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
type?: 'button' | 'submit' | 'reset';
disabled?: boolean;
loading?: boolean;
leftIcon?: Component;
rightIcon?: Component;
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
type: 'button',
disabled: false,
loading: false,
leftIcon: undefined,
rightIcon: undefined,
});// From UiInput.vue
interface Props {
modelValue?: string | number | null;
label?: string;
id?: string;
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'search' | 'date' | 'time';
placeholder?: string;
disabled?: boolean;
leftIcon?: Component;
rightIcon?: Component;
size?: 'sm' | 'md' | 'lg';
fullWidth?: boolean;
error?: string;
required?: boolean;
}Emit Types
Emit declarations use typed generics:
// From UiModal.vue
const emit = defineEmits<{
'update:modelValue': [value: boolean];
close: [];
}>();
// From UiInput.vue
const emit = defineEmits<{
'update:modelValue': [value: string | number | null];
}>();
// From UiTable.vue
defineEmits<{
'row-click': [row: Record<string, unknown>];
}>();Composable Return Types
Composables use TypeScript's type inference. The return type is inferred from the returned object:
// The return type is inferred automatically
export const useCreateInsurance = () => {
const loading = ref(false); // Ref<boolean>
const error = ref(''); // Ref<string>
const success = ref(false); // Ref<boolean>
const createInsurance = async (data: InsuranceFormData) => {
// ...
};
return { loading, error, success, createInsurance };
};Generic Composables
Some composables use generics for reusability:
// useApiFetch is generic over the response type
export const useApiFetch = async <T>(
endpoint: string,
options: ApiFetchOptions = {}
): Promise<T> => {
// ...
return $fetch<T>(url, { ...options, headers });
};
// Usage with type parameter
const patients = await useApiFetch<PaginatedResourceResponse<Patient>>('/patients');// useFormValidation is generic over the form shape
export const useFormValidation = <T extends Record<string, unknown>>(
rules: ValidationRules<T>
) => {
// ...
};Nuxt Auto-Imports for TypeScript
Nuxt auto-imports are fully typed. The .nuxt/ directory contains generated type definitions so that ref, computed, watch, useRoute, and other auto-imports have correct types without explicit import statements:
<script setup lang="ts">
// No imports needed -- these are auto-imported with full types
const count = ref(0); // Ref<number>
const doubled = computed(() => count.value * 2); // ComputedRef<number>
const route = useRoute(); // RouteLocationNormalizedLoaded
</script>Custom composables in app/composables/ are also auto-imported with types:
<script setup lang="ts">
// Auto-imported from app/composables/useValidationRules.ts
const validate = required('Patient Name'); // (value: unknown) => string | null
</script>Strict Mode Benefits
With strict: true, TypeScript catches common issues:
Null Checks
// Error: Object is possibly 'null'
const name = authStore.state.user.name;
// Correct: Check for null first
const name = authStore.state.user?.name ?? 'Unknown';No Implicit Any
// Error: Parameter 'e' implicitly has an 'any' type
const handleError = (e) => { ... };
// Correct: Explicit type
const handleError = (e: FetchError) => { ... };Exhaustive Checks
// TypeScript ensures all cases are handled
type StatusColor = 'gray' | 'yellow' | 'green';
const getLabel = (color: StatusColor): string => {
switch (color) {
case 'gray': return 'Discharged';
case 'yellow': return 'Pending';
case 'green': return 'Active';
// If a new color is added, TypeScript will error here
}
};Adding New Types
When adding a new domain to CPR:
- Create a type file:
app/types/newDomain.type.ts - Export the interfaces
- Import in composables and services:
import type { MyType } from '~/types/newDomain.type' - Use
import typefor type-only imports (tree-shaking benefit)
// app/types/newDomain.type.ts
export interface NewDomainResource {
id: string;
name: string;
// ...
}
export interface NewDomainFormData {
name: string;
// ...
}