We use cookies to enhance your experience on the site
CodeWorlds

Final project: Typed Jurassic Park management system

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.

Project goal

You will create a typed Jurassic Park management system consisting of four areas:

  1. Dinosaur type system - enums, interfaces and unions describing all aspects of dinosaurs
  2. Sector management system - interfaces and types for park infrastructure
  3. Security system - types for events, alerts and protocols
  4. Central management system - combining all types into a cohesive system

Functional requirements

1. Dinosaur type system (Enums, interfaces, aliases)

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.

2. Sector management system (Interfaces and complex types)

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.

3. Security system (Union types and aliases)

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.

4. Central management system (Combining types)

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}

Implementation tips

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 arbitrary

Interfaces 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};

Good luck!

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.

Go to CodeWorlds