Congratulations - you've reached the final project of the TypeScript fundamentals module! Over the past lessons you've learned powerful typing tools: from basic types, through arrays, tuples and enums, all the way to interfaces, union types and aliases. Now the time has come to combine this knowledge into one comprehensive project.
Imagine that the Jurassic Park programming team has finally decided to rewrite their systems from plain JavaScript to TypeScript. After too many incidents caused by type errors - from incorrectly passed power parameters, through mixed up dinosaur species, to security system failures due to
undefined - management made a decision: "Never again!". Your task is to create the foundation of a new, fully typed park management system.In this project you will build a complete type and interface system for Jurassic Park, which will ensure type safety at every level of the application.
You will create a typed Jurassic Park management system consisting of four areas:
The first area is a precise data model describing the dinosaurs in the park. We use enums for constant values, interfaces for object structure, and type aliases for complex combinations.
1// Enums - constant values in the system
2enum Diet {
3 Carnivore = "carnivore",
4 Herbivore = "herbivore",
5 Omnivore = "omnivore",
6 Piscivore = "piscivore"
7}
8
9enum Era {
10 Triassic = "trias",
11 Jurassic = "jura",
12 Cretaceous = "kreda"
13}
14
15enum ThreatLevel {
16 Minimal = 1,
17 Low = 2,
18 Moderate = 3,
19 High = 4,
20 Extreme = 5
21}
22
23enum DinoStatus {
24 Healthy = "zdrowy",
25 Sick = "chory",
26 Sleeping = "sleeping",
27 Feeding = "je",
28 Agitated = "pobudzony",
29 Escaped = "escaped"
30}
31
32// Type alias - dinosaur species
33type Species =
34 | "Tyrannosaurus Rex"
35 | "Velociraptor"
36 | "Triceratops"
37 | "Brachiosaurus"
38 | "Stegosaurus"
39 | "Pteranodon"
40 | "Dilophosaurus"
41 | "Gallimimus"
42 | "Parasaurolophus"
43 | "Ankylosaurus";
44
45// Interface - dinosaur health data
46interface DinoHealth {
47 weight: number; // in kilograms
48 height: number; // in meters
49 heartRate: number; // beats per minute
50 temperature: number; // degrees Celsius
51 lastFed: Date;
52 lastCheckup: Date;
53 conditions: string[]; // list of health conditions
54}
55
56// Interface - full dinosaur profile
57interface Dinosaur {
58 id: string;
59 name: string;
60 species: Species; // Type alias
61 diet: Diet; // Enum
62 era: Era; // Enum
63 threatLevel: ThreatLevel; // Enum
64 status: DinoStatus; // Enum
65 health: DinoHealth; // Nested interface
66 sectorId: string;
67 trackingChipActive: boolean;
68 dateAdded: Date;
69}
70
71// Union type - dinosaur search filter
72type DinoSearchCriteria =
73 | { by: "species"; value: Species }
74 | { by: "diet"; value: Diet }
75 | { by: "threatLevel"; minLevel: ThreatLevel }
76 | { by: "status"; value: DinoStatus }
77 | { by: "sector"; sectorId: string };
78
79// Function with types
80function searchDinosaurs(
81 dinosaurs: Dinosaur[],
82 criteria: DinoSearchCriteria
83): Dinosaur[] {
84 switch (criteria.by) {
85 case "species":
86 return dinosaurs.filter(d => d.species === criteria.value);
87 case "diet":
88 return dinosaurs.filter(d => d.diet === criteria.value);
89 case "threatLevel":
90 return dinosaurs.filter(d => d.threatLevel >= criteria.minLevel);
91 case "status":
92 return dinosaurs.filter(d => d.status === criteria.value);
93 case "sector":
94 return dinosaurs.filter(d => d.sectorId === criteria.sectorId);
95 }
96}Notice how TypeScript enforces completeness of handling in
switch - if you add a new variant to DinoSearchCriteria, the compiler will remind you to handle the new case.The second area is the park infrastructure model - sectors, fences, power systems and monitoring.
1// Union type - fence type
2type FenceType = "electric" | "concrete" | "reinforced" | "moat";
3
4// Fence interface
5interface Fence {
6 id: string;
7 type: FenceType;
8 voltage: number; // 0 for non-electric
9 integrity: number; // 0-100%
10 lastMaintenance: Date;
11 requiresRepair: boolean;
12}
13
14// Enum - sector status
15enum SectorStatus {
16 Operational = "operacyjny",
17 Maintenance = "konserwacja",
18 Lockdown = "blokada",
19 Evacuated = "ewakuowany",
20 Offline = "offline"
21}
22
23// Interface - power system
24interface PowerSystem {
25 mainPowerLevel: number; // 0-100%
26 backupPowerLevel: number; // 0-100%
27 isBackupActive: boolean;
28 solarPanelsOnline: number; // number of active panels
29 generatorStatus: "running" | "standby" | "failed";
30}
31
32// Sector interface with nested types
33interface Sector {
34 id: string;
35 name: string;
36 status: SectorStatus;
37 dinosaurs: Dinosaur[];
38 fences: Fence[];
39 power: PowerSystem;
40 capacity: number; // maximum number of dinosaurs
41 securityLevel: ThreatLevel;
42 staff: StaffMember[];
43}
44
45// Type alias - staff role
46type StaffRole =
47 | "weterynarz"
48 | "guard"
49 | "technik"
50 | "naukowiec"
51 | "kierownik"
52 | "przewodnik";
53
54// Staff member interface
55interface StaffMember {
56 id: string;
57 name: string;
58 role: StaffRole;
59 assignedSector: string;
60 clearanceLevel: ThreatLevel; // Reusing enum in a different context
61 onDuty: boolean;
62 specializations: string[];
63}
64
65// Type - sector status summary
66type SectorSummary = {
67 sectorId: string;
68 sectorName: string;
69 status: SectorStatus;
70 dinoCount: number;
71 avgFenceIntegrity: number;
72 powerStatus: "stabilne" | "niskie" | "krytyczne";
73 staffOnDuty: number;
74 alerts: number;
75};
76
77// Function generating the summary
78function getSectorSummary(sector: Sector): SectorSummary {
79 const avgFence = sector.fences.reduce(
80 (sum, f) => sum + f.integrity, 0
81 ) / sector.fences.length;
82
83 const powerStatus: SectorSummary["powerStatus"] =
84 sector.power.mainPowerLevel > 60 ? "stabilne" :
85 sector.power.mainPowerLevel > 30 ? "niskie" : "krytyczne";
86
87 return {
88 sectorId: sector.id,
89 sectorName: sector.name,
90 status: sector.status,
91 dinoCount: sector.dinosaurs.length,
92 avgFenceIntegrity: Math.round(avgFence),
93 powerStatus,
94 staffOnDuty: sector.staff.filter(s => s.onDuty).length,
95 alerts: 0
96 };
97}Interfaces build hierarchically -
Sector contains arrays of Dinosaur[], Fence[] and StaffMember[], creating a rich data model.The third area is the security event and alert system, which makes heavy use of union types to precisely model different types of incidents.
1// Union types for security events - each event has a different structure
2type SecurityEvent =
3 | {
4 type: "fence_breach";
5 sectorId: string;
6 fenceId: string;
7 dinosaurId: string;
8 severity: "warning" | "critical";
9 }
10 | {
11 type: "power_outage";
12 sectorId: string;
13 affectedSystems: string[];
14 backupActivated: boolean;
15 }
16 | {
17 type: "dinosaur_escape";
18 dinosaurId: string;
19 species: Species;
20 lastKnownSector: string;
21 threatLevel: ThreatLevel;
22 }
23 | {
24 type: "sensor_failure";
25 sectorId: string;
26 sensorIds: string[];
27 possibleCause: "damage" | "power" | "sabotage" | "unknown";
28 }
29 | {
30 type: "unauthorized_access";
31 location: string;
32 personId: string | null;
33 timestamp: Date;
34 };
35
36// Enum - alert priority
37enum AlertPriority {
38 Info = "INFO",
39 Warning = "UWAGA",
40 Danger = "DANGER",
41 Critical = "KRYTYCZNY",
42 Evacuation = "EWAKUACJA"
43}
44
45// Alert interface
46interface SecurityAlert {
47 id: string;
48 event: SecurityEvent;
49 priority: AlertPriority;
50 createdAt: Date;
51 acknowledgedBy: string | null; // Staff member ID or null
52 resolved: boolean;
53 notes: string[];
54}
55
56// Type - threat response protocol
57type ResponseProtocol = {
58 alertId: string;
59 actions: ProtocolAction[];
60 estimatedTime: number; // in minutes
61 requiredClearance: ThreatLevel;
62};
63
64// Union type - protocol action
65type ProtocolAction =
66 | { action: "lockdown"; sectors: string[] }
67 | { action: "evacuate"; sectors: string[]; priority: "staff" | "visitors" | "all" }
68 | { action: "deploy_team"; teamType: StaffRole; targetSector: string }
69 | { action: "activate_backup"; systems: string[] }
70 | { action: "track_dinosaur"; dinosaurId: string; useHelicopter: boolean };
71
72// Event handling function with type narrowing
73function handleSecurityEvent(event: SecurityEvent): ResponseProtocol {
74 const actions: ProtocolAction[] = [];
75 let estimatedTime = 10;
76 let requiredClearance = ThreatLevel.Moderate;
77
78 switch (event.type) {
79 case "fence_breach":
80 actions.push({ action: "lockdown", sectors: [event.sectorId] });
81 actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
82 if (event.severity === "critical") {
83 actions.push({ action: "evacuate", sectors: [event.sectorId], priority: "visitors" });
84 requiredClearance = ThreatLevel.High;
85 }
86 break;
87
88 case "dinosaur_escape":
89 actions.push({ action: "track_dinosaur", dinosaurId: event.dinosaurId, useHelicopter: event.threatLevel >= ThreatLevel.High });
90 actions.push({ action: "lockdown", sectors: [event.lastKnownSector] });
91 actions.push({ action: "deploy_team", teamType: "guard", targetSector: event.lastKnownSector });
92 estimatedTime = 60;
93 requiredClearance = ThreatLevel.Extreme;
94 break;
95
96 case "power_outage":
97 actions.push({ action: "activate_backup", systems: event.affectedSystems });
98 actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
99 if (!event.backupActivated) {
100 actions.push({ action: "lockdown", sectors: [event.sectorId] });
101 requiredClearance = ThreatLevel.High;
102 }
103 break;
104
105 case "sensor_failure":
106 actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
107 if (event.possibleCause === "sabotage") {
108 actions.push({ action: "lockdown", sectors: [event.sectorId] });
109 requiredClearance = ThreatLevel.Extreme;
110 }
111 break;
112
113 case "unauthorized_access":
114 actions.push({ action: "deploy_team", teamType: "guard", targetSector: event.location });
115 break;
116 }
117
118 return {
119 alertId: `PROTOCOL-${Date.now()}`,
120 actions,
121 estimatedTime,
122 requiredClearance
123 };
124}Type narrowing in
switch allows TypeScript to automatically narrow the event type - in the case "fence_breach" block TypeScript knows that event has fenceId and severity fields.The last area is the central system that combines all the above types into a cohesive park management interface.
1// Interface for the entire park
2interface JurassicPark {
3 name: string;
4 sectors: Sector[];
5 totalDinosaurs: number;
6 activeAlerts: SecurityAlert[];
7 operationalStatus: "open" | "restricted" | "closed" | "emergency";
8}
9
10// Type - daily report
11type DailyReport = {
12 date: string;
13 parkStatus: JurassicPark["operationalStatus"]; // Reusing type from interface
14 sectorSummaries: SectorSummary[];
15 incidentCount: number;
16 resolvedIncidents: number;
17 dinosaurStats: {
18 total: number;
19 byDiet: Record<Diet, number>; // Record with enum as key
20 byStatus: Record<DinoStatus, number>;
21 escaped: number;
22 };
23 staffStats: {
24 totalOnDuty: number;
25 byRole: Record<StaffRole, number>; // Record with type alias as key
26 };
27 recommendations: string[];
28};
29
30// Function generating the report
31function generateDailyReport(park: JurassicPark): DailyReport {
32 // Initialize counters with enums
33 const byDiet: Record<Diet, number> = {
34 [Diet.Carnivore]: 0,
35 [Diet.Herbivore]: 0,
36 [Diet.Omnivore]: 0,
37 [Diet.Piscivore]: 0
38 };
39
40 const byStatus: Record<DinoStatus, number> = {
41 [DinoStatus.Healthy]: 0,
42 [DinoStatus.Sick]: 0,
43 [DinoStatus.Sleeping]: 0,
44 [DinoStatus.Feeding]: 0,
45 [DinoStatus.Agitated]: 0,
46 [DinoStatus.Escaped]: 0
47 };
48
49 // Counting dinosaurs
50 const allDinos = park.sectors.flatMap(s => s.dinosaurs);
51 allDinos.forEach(dino => {
52 byDiet[dino.diet]++;
53 byStatus[dino.status]++;
54 });
55
56 // Sector summaries
57 const sectorSummaries = park.sectors.map(getSectorSummary);
58
59 // Recommendations based on data
60 const recommendations: string[] = [];
61
62 if (byStatus[DinoStatus.Escaped] > 0) {
63 recommendations.push(`CRITICAL: ${byStatus[DinoStatus.Escaped]} dinosaur(s) escaped!`);
64 }
65
66 if (byStatus[DinoStatus.Agitated] > allDinos.length * 0.3) {
67 recommendations.push("WARNING: Over 30% of dinosaurs are agitated - investigate the cause");
68 }
69
70 const lowPowerSectors = sectorSummaries.filter(s => s.powerStatus === "krytyczne");
71 if (lowPowerSectors.length > 0) {
72 recommendations.push(
73 `Critical power level in sectors: ${lowPowerSectors.map(s => s.sectorName).join(", ")}`
74 );
75 }
76
77 return {
78 date: new Date().toISOString().split("T")[0],
79 parkStatus: park.operationalStatus,
80 sectorSummaries,
81 incidentCount: park.activeAlerts.length,
82 resolvedIncidents: park.activeAlerts.filter(a => a.resolved).length,
83 dinosaurStats: {
84 total: allDinos.length,
85 byDiet,
86 byStatus,
87 escaped: byStatus[DinoStatus.Escaped]
88 },
89 staffStats: {
90 totalOnDuty: park.sectors.flatMap(s => s.staff).filter(s => s.onDuty).length,
91 byRole: {
92 weterynarz: 0, guard: 0, technik: 0,
93 naukowiec: 0, kierownik: 0, przewodnik: 0
94 }
95 },
96 recommendations
97 };
98}When designing the type system, remember the key principles:
Use enums for constant sets of values:
1// Good - values are constant and known in advance
2enum Diet { Carnivore = "carnivore", Herbivore = "herbivore" }
3
4// Bad - too dynamic data for an enum
5enum DinosaurName { Rex = "Rex", Blue = "Blue" } // Names can be arbitraryInterfaces for object structures, aliases for unions and combinations:
1// Interface - describes the shape of an object
2interface Dinosaur {
3 name: string;
4 species: Species;
5}
6
7// Alias - combines types, creates variants
8type SearchResult = Dinosaur | null;
9type DinoFilter = (dino: Dinosaur) => boolean;
10type DinoOrError = { success: true; data: Dinosaur } | { success: false; error: string };Discriminated union types for events:
1// The "type" field allows TypeScript to distinguish variants
2type Event =
3 | { type: "A"; fieldA: string }
4 | { type: "B"; fieldB: number };
5
6function handle(event: Event) {
7 if (event.type === "A") {
8 // TypeScript knows fieldA is here
9 console.log(event.fieldA);
10 }
11}Record for creating mappings from enums:
1// Automatic enum → value map
2const dietLabels: Record<Diet, string> = {
3 [Diet.Carnivore]: "Carnivore - feed with meat",
4 [Diet.Herbivore]: "Herbivore - feed with plants",
5 [Diet.Omnivore]: "Omnivore - mixed diet",
6 [Diet.Piscivore]: "Piscivore - feed with fish"
7};This project is your chance to prove that you understand the power of the TypeScript type system. In a real Jurassic Park every mistake can cost lives - and TypeScript is your fence protecting against type errors. As Dr. Alan Grant said: "Dinosaurs and man - two species separated by 65 million years of evolution - suddenly must live side by side again." And TypeScript makes that coexistence safe and predictable.
Create your typed management system in the editor below. Define at least three enums, four interfaces, two union types and one function using these types.