We use cookies to enhance your experience on the site
CodeWorlds

Implementing an Application Using Learned Techniques

Welcome to the final phase of our Jurassic Park adventure! In this exercise we'll combine all the techniques we've learned so far to create a complete application for monitoring and managing the Jurassic Park ecosystem. This is our "grand finale" before moving on to structuring larger projects.

Project Goal

We'll create the "JurassicTracker" application β€” a system for monitoring, tracking, and managing dinosaurs in the park. Our application will use:

  1. Modular code (ES Modules)
  2. Asynchronous operations (Promises, async/await)
  3. Error handling (try/catch, custom error classes)
  4. Background services (setInterval)
  5. Application state management

Project Structure

Before we start implementing, let's plan the structure:

1jurassic-tracker/
2β”œβ”€β”€ index.js               # Main application file
3β”œβ”€β”€ modules/
4β”‚   β”œβ”€β”€ api.js             # API communication module
5β”‚   β”œβ”€β”€ dinosaurs.js       # Dinosaur management module
6β”‚   β”œβ”€β”€ enclosures.js      # Enclosure management module
7β”‚   β”œβ”€β”€ security.js        # Security systems module
8β”‚   β”œβ”€β”€ monitoring.js      # Park monitoring module
9β”‚   β”œβ”€β”€ utils.js           # Utility tools
10β”‚   └── errors/            # Error class directory
11β”‚       β”œβ”€β”€ index.js       # Exports all errors
12β”‚       β”œβ”€β”€ APIError.js    # API-related errors
13β”‚       β”œβ”€β”€ DinoError.js   # Dinosaur-related errors
14β”‚       └── SecurityError.js # Security-related errors
15└── data/
16    └── config.js          # Application configuration

Module Implementations

Let's implement each of the modules. Remember that in practice these files would live in separate files according to the above structure.

1. Errors Module (errors/index.js)

First let's define our custom error classes:

1// errors/APIError.js
2export class APIError extends Error {
3  constructor(message, statusCode, endpoint) {
4    super(message);
5    this.name = 'APIError';
6    this.statusCode = statusCode;
7    this.endpoint = endpoint;
8    this.timestamp = new Date();
9  }
10
11  isServerError() {
12    return this.statusCode >= 500;
13  }
14
15  isClientError() {
16    return this.statusCode >= 400 && this.statusCode < 500;
17  }
18}
19
20// errors/DinoError.js
21export class DinoError extends Error {
22  constructor(message, dinoId, species) {
23    super(message);
24    this.name = 'DinoError';
25    this.dinoId = dinoId;
26    this.species = species;
27    this.timestamp = new Date();
28  }
29}
30
31export class DinoNotFoundError extends DinoError {
32  constructor(dinoId) {
33    super(`Dinosaur with ID ${dinoId} was not found`, dinoId, null);
34    this.name = 'DinoNotFoundError';
35  }
36}
37
38export class DinoBehaviorError extends DinoError {
39  constructor(message, dinoId, species, behaviorType, dangerLevel) {
40    super(message, dinoId, species);
41    this.name = 'DinoBehaviorError';
42    this.behaviorType = behaviorType;
43    this.dangerLevel = dangerLevel; // 1-10
44  }
45
46  requiresIntervention() {
47    return this.dangerLevel >= 7;
48  }
49}
50
51// errors/SecurityError.js
52export class SecurityError extends Error {
53  constructor(message, sector, systemType) {
54    super(message);
55    this.name = 'SecurityError';
56    this.sector = sector;
57    this.systemType = systemType;
58    this.timestamp = new Date();
59  }
60}
61
62export class EnclosureBreachError extends SecurityError {
63  constructor(sector, dinoSpecies, count) {
64    super(`Enclosure breach in sector ${sector}: ${count} ${dinoSpecies} specimens`, sector, 'enclosure');
65    this.name = 'EnclosureBreachError';
66    this.dinoSpecies = dinoSpecies;
67    this.count = count;
68  }
69}
70
71// errors/index.js
72export * from './APIError.js';
73export * from './DinoError.js';
74export * from './SecurityError.js';

2. API Module (api.js)

Our API module handles communication with the (hypothetical) Jurassic Park server:

1// modules/api.js
2import { APIError } from '../errors/index.js';
3
4const API_DELAY = 500; // ms - simulate network delay
5const API_ERROR_RATE = 0.1; // 10% chance of API error for realism
6
7// Simulated dinosaur database
8let dinosaursData = [
9  { id: 'trx1', name: 'Rex', species: 'Tyrannosaurus', enclosureId: 'enc1', health: 98, lastFed: new Date(Date.now() - 3600000), behavior: 'calm' },
10  { id: 'rap1', name: 'Blue', species: 'Velociraptor', enclosureId: 'enc2', health: 95, lastFed: new Date(Date.now() - 7200000), behavior: 'agitated' },
11  { id: 'rap2', name: 'Delta', species: 'Velociraptor', enclosureId: 'enc2', health: 92, lastFed: new Date(Date.now() - 7200000), behavior: 'hunting' },
12  { id: 'tri1', name: 'Sarah', species: 'Triceratops', enclosureId: 'enc3', health: 88, lastFed: new Date(Date.now() - 1800000), behavior: 'sleeping' },
13  { id: 'bro1', name: 'Bronty', species: 'Brontosaurus', enclosureId: 'enc4', health: 100, lastFed: new Date(Date.now() - 900000), behavior: 'grazing' },
14];
15
16// Helper function simulating network delay
17const simulateNetworkDelay = () => {
18  return new Promise(resolve => setTimeout(resolve, API_DELAY * (0.5 + Math.random())));
19};
20
21// Helper function simulating API errors
22const simulateApiError = (endpoint) => {
23  if (Math.random() < API_ERROR_RATE) {
24    const errorTypes = [
25      { statusCode: 500, message: 'Internal Server Error' },
26      { statusCode: 503, message: 'Service Unavailable' },
27      { statusCode: 429, message: 'Too Many Requests' },
28      { statusCode: 403, message: 'Forbidden - Insufficient permissions' }
29    ];
30    const errorType = errorTypes[Math.floor(Math.random() * errorTypes.length)];
31    throw new APIError(`API Error: ${errorType.message}`, errorType.statusCode, endpoint);
32  }
33};
34
35// API for fetching dinosaur data
36export async function getDinosaurs() {
37  const endpoint = '/api/dinosaurs';
38  try {
39    await simulateNetworkDelay();
40    simulateApiError(endpoint);
41    return [...dinosaursData]; // Return a copy to avoid mutation
42  } catch (error) {
43    if (error instanceof APIError) throw error;
44    throw new APIError('Error fetching dinosaur data', 500, endpoint);
45  }
46}
47
48export async function getDinosaurById(id) {
49  const endpoint = `/api/dinosaurs/${id}`;
50  try {
51    await simulateNetworkDelay();
52    simulateApiError(endpoint);
53    const dino = dinosaursData.find(d => d.id === id);
54    if (!dino) {
55      throw new APIError(`Dinosaur with ID ${id} not found`, 404, endpoint);
56    }
57    return { ...dino };
58  } catch (error) {
59    if (error instanceof APIError) throw error;
60    throw new APIError('Error fetching dinosaur', 500, endpoint);
61  }
62}

3. Dinosaur Module (dinosaurs.js)

This module manages dinosaur data and behavior:

1// modules/dinosaurs.js
2import { getDinosaurs, getDinosaurById, updateDinosaur, logEvent } from './api.js';
3import { DinoNotFoundError, DinoBehaviorError } from '../errors/index.js';
4
5// Assess dinosaur behavior risk
6function assessBehaviorRisk(dino) {
7  const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
8  const riskBehaviors = {
9    'calm': 1,
10    'grazing': 1,
11    'sleeping': 2,
12    'exploring': 4,
13    'agitated': 6,
14    'hunting': 7,
15    'aggressive': 9
16  };
17
18  // Base risk depends on species
19  let baseRisk = dangerousSpecies.includes(dino.species) ? 5 : 2;
20
21  // Risk modification based on behavior
22  const behaviorRisk = riskBehaviors[dino.behavior] || 3;
23
24  // Risk modification based on time since last feeding
25  const hoursSinceLastFed = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
26  const hungerRisk = Math.min(5, Math.floor(hoursSinceLastFed / 3));
27
28  // Total risk level (1-10)
29  const totalRisk = Math.min(10, baseRisk + behaviorRisk + hungerRisk);
30
31  return {
32    risk: totalRisk,
33    factors: {
34      species: baseRisk,
35      behavior: behaviorRisk,
36      hunger: hungerRisk
37    }
38  };
39}
40
41// Get all dinosaurs
42export async function getAllDinosaurs() {
43  try {
44    const dinosaurs = await getDinosaurs();
45
46    // Enrich data with risk assessment
47    return dinosaurs.map(dino => ({
48      ...dino,
49      riskAssessment: assessBehaviorRisk(dino)
50    }));
51  } catch (error) {
52    console.error('Error fetching dinosaur list:', error);
53    throw error;
54  }
55}
56
57// Get a specific dinosaur by ID
58export async function getDinosaur(id) {
59  try {
60    const dino = await getDinosaurById(id);
61    return {
62      ...dino,
63      riskAssessment: assessBehaviorRisk(dino)
64    };
65  } catch (error) {
66    if (error.statusCode === 404) {
67      throw new DinoNotFoundError(id);
68    }
69    console.error(`Error fetching dinosaur ${id}:`, error);
70    throw error;
71  }
72}
73
74// Feed a dinosaur
75export async function feedDinosaur(id) {
76  try {
77    const dino = await getDinosaur(id);
78
79    // Update dinosaur state
80    const updatedDino = await updateDinosaur(id, {
81      lastFed: new Date(),
82      health: Math.min(100, dino.health + 5),
83      behavior: 'calm' // Dinosaurs are calmer after feeding
84    });
85
86    // Log the event
87    await logEvent('FEEDING', {
88      dinoId: id,
89      species: dino.species,
90      name: dino.name,
91      previousFeedingTime: dino.lastFed
92    });
93
94    return {
95      ...updatedDino,
96      riskAssessment: assessBehaviorRisk(updatedDino)
97    };
98  } catch (error) {
99    console.error(`Error feeding dinosaur ${id}:`, error);
100    throw error;
101  }
102}
103
104// Monitor dinosaur behavior
105export async function monitorBehavior(id) {
106  try {
107    const dino = await getDinosaur(id);
108    const { risk, factors } = assessBehaviorRisk(dino);
109
110    // Check if behavior requires intervention
111    if (risk >= 8) {
112      const error = new DinoBehaviorError(
113        `Dangerous behavior from dinosaur ${dino.name}`,
114        id,
115        dino.species,
116        dino.behavior,
117        risk
118      );
119
120      // Log the high-risk event
121      await logEvent('HIGH_RISK_BEHAVIOR', {
122        dinoId: id,
123        species: dino.species,
124        name: dino.name,
125        behavior: dino.behavior,
126        risk,
127        factors
128      });
129
130      throw error;
131    }
132
133    // For medium risk, just log
134    if (risk >= 5) {
135      await logEvent('MEDIUM_RISK_BEHAVIOR', {
136        dinoId: id,
137        species: dino.species,
138        name: dino.name,
139        behavior: dino.behavior,
140        risk,
141        factors
142      });
143    }
144
145    return { dino, risk, factors };
146  } catch (error) {
147    if (error instanceof DinoBehaviorError) {
148      throw error; // Re-throw our custom error
149    }
150    console.error(`Error monitoring dinosaur ${id}:`, error);
151    throw error;
152  }
153}
154
155// Simulate behavior change (used by the monitoring module)
156export async function simulateBehaviorChange() {
157  try {
158    const allDinos = await getAllDinosaurs();
159    const randomIndex = Math.floor(Math.random() * allDinos.length);
160    const targetDino = allDinos[randomIndex];
161
162    // Possible behaviors depending on species and last feeding
163    const carnivores = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
164
165    let behaviorPool;
166    if (carnivores.includes(targetDino.species)) {
167      // Predators have higher chance of aggressive behavior
168      behaviorPool = ['calm', 'sleeping', 'exploring', 'agitated', 'hunting', 'aggressive'];
169    } else {
170      // Herbivores are generally calmer
171      behaviorPool = ['calm', 'grazing', 'sleeping', 'exploring', 'agitated'];
172    }
173
174    // Hungry dinosaurs are more aggressive
175    const hoursSinceLastFed = (Date.now() - new Date(targetDino.lastFed).getTime()) / 3600000;
176    if (hoursSinceLastFed > 8) {
177      if (carnivores.includes(targetDino.species)) {
178        behaviorPool = ['agitated', 'hunting', 'aggressive'];
179      } else {
180        behaviorPool = ['agitated', 'exploring'];
181      }
182    }
183
184    // Random behavior selection
185    const newBehavior = behaviorPool[Math.floor(Math.random() * behaviorPool.length)];
186
187    // Update dinosaur state
188    const updatedDino = await updateDinosaur(targetDino.id, {
189      behavior: newBehavior
190    });
191
192    await logEvent('BEHAVIOR_CHANGE', {
193      dinoId: targetDino.id,
194      species: targetDino.species,
195      name: targetDino.name,
196      previousBehavior: targetDino.behavior,
197      newBehavior
198    });
199
200    return {
201      ...updatedDino,
202      riskAssessment: assessBehaviorRisk(updatedDino)
203    };
204  } catch (error) {
205    console.error('Error simulating behavior change:', error);
206    throw error;
207  }
208}

4. Enclosure Module (enclosures.js)

This module manages enclosures and their security status:

1// modules/enclosures.js
2import { getEnclosures, getEnclosureById, updateEnclosure, logEvent } from './api.js';
3import { SecurityError, EnclosureBreachError } from '../errors/index.js';
4import { getAllDinosaurs } from './dinosaurs.js';
5
6// Assess enclosure security status
7function assessSecurityStatus(enclosure, dinosaurs) {
8  // Check how many dinosaurs are in the enclosure
9  const enclosureDinosaurs = dinosaurs.filter(dino => dino.enclosureId === enclosure.id);
10
11  // Check for carnivores
12  const carnivores = enclosureDinosaurs.filter(dino =>
13    ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'].includes(dino.species)
14  );
15
16  // Check total risk
17  const totalRisk = enclosureDinosaurs.reduce((sum, dino) =>
18    sum + (dino.riskAssessment ? dino.riskAssessment.risk : 3), 0);
19
20  // Power status
21  const powerStatus = enclosure.powerStatus === 'active' ? 10 :
22                       enclosure.powerStatus === 'backup' ? 5 : 0;
23
24  // Time since last inspection (in days)
25  const daysSinceInspection = (Date.now() - new Date(enclosure.lastInspection).getTime()) / (24 * 3600000);
26  const inspectionScore = daysSinceInspection < 1 ? 10 :
27                          daysSinceInspection < 3 ? 7 :
28                          daysSinceInspection < 7 ? 4 : 1;
29
30  // Calculate overall security score (0-100)
31  const securityScore = Math.max(0, Math.min(100,
32    enclosure.securityLevel * 10 - totalRisk + powerStatus + inspectionScore
33  ));
34
35  return {
36    id: enclosure.id,
37    name: enclosure.name,
38    sector: enclosure.sector,
39    dinosaurCount: enclosureDinosaurs.length,
40    carnivoreCount: carnivores.length,
41    securityLevel: enclosure.securityLevel,
42    powerStatus: enclosure.powerStatus,
43    securityScore,
44    status: securityScore >= 80 ? 'secure' :
45            securityScore >= 60 ? 'normal' :
46            securityScore >= 40 ? 'warning' : 'danger',
47    dinosaurs: enclosureDinosaurs.map(d => ({
48      id: d.id,
49      name: d.name,
50      species: d.species,
51      risk: d.riskAssessment ? d.riskAssessment.risk : 3
52    }))
53  };
54}
55
56// Get all enclosures with security assessment
57export async function getAllEnclosures() {
58  try {
59    const [enclosures, dinosaurs] = await Promise.all([
60      getEnclosures(),
61      getAllDinosaurs()
62    ]);
63
64    // Enrich data with security assessment
65    return enclosures.map(enclosure =>
66      assessSecurityStatus(enclosure, dinosaurs)
67    );
68  } catch (error) {
69    console.error('Error fetching enclosure list:', error);
70    throw error;
71  }
72}
73
74// Inspect an enclosure
75export async function inspectEnclosure(id) {
76  try {
77    const enclosure = await getEnclosureById(id);
78
79    // Update last inspection date
80    const updatedEnclosure = await updateEnclosure(id, {
81      lastInspection: new Date()
82    });
83
84    // Log the event
85    await logEvent('ENCLOSURE_INSPECTION', {
86      enclosureId: id,
87      name: enclosure.name,
88      sector: enclosure.sector,
89      previousInspection: enclosure.lastInspection
90    });
91
92    // Fetch dinosaurs to update security assessment
93    const dinosaurs = await getAllDinosaurs();
94
95    return assessSecurityStatus(updatedEnclosure, dinosaurs);
96  } catch (error) {
97    console.error(`Error inspecting enclosure ${id}:`, error);
98    throw error;
99  }
100}
101
102// Simulate power failure
103export async function simulatePowerFailure() {
104  try {
105    const enclosures = await getEnclosures();
106
107    // Select a random enclosure
108    const randomIndex = Math.floor(Math.random() * enclosures.length);
109    const targetEnclosure = enclosures[randomIndex];
110
111    if (targetEnclosure.powerStatus !== 'active') return;
112
113    // 70% chance of backup power, 30% chance of total failure
114    const newPowerStatus = Math.random() < 0.7 ? 'backup' : 'failure';
115
116    // Update enclosure state
117    const updatedEnclosure = await updateEnclosure(targetEnclosure.id, {
118      powerStatus: newPowerStatus
119    });
120
121    await logEvent('POWER_FAILURE', {
122      enclosureId: targetEnclosure.id,
123      name: targetEnclosure.name,
124      sector: targetEnclosure.sector,
125      powerStatus: newPowerStatus
126    });
127
128    // Check if failure could lead to an enclosure breach
129    if (newPowerStatus === 'failure') {
130      const dinosaurs = await getAllDinosaurs();
131      const enclosureDinos = dinosaurs.filter(dino => dino.enclosureId === targetEnclosure.id);
132
133      const dangerousSpecies = ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus'];
134      const hasDangerousDinos = enclosureDinos.some(dino =>
135        dangerousSpecies.includes(dino.species)
136      );
137
138      if (hasDangerousDinos && targetEnclosure.securityLevel >= 8) {
139        const breachingSpecies = enclosureDinos.find(dino =>
140          dangerousSpecies.includes(dino.species)
141        ).species;
142
143        const breachingCount = enclosureDinos.filter(dino =>
144          dino.species === breachingSpecies
145        ).length;
146
147        throw new EnclosureBreachError(
148          targetEnclosure.sector,
149          breachingSpecies,
150          breachingCount
151        );
152      }
153    }
154
155    const dinosaurs = await getAllDinosaurs();
156    return assessSecurityStatus(updatedEnclosure, dinosaurs);
157  } catch (error) {
158    if (error instanceof EnclosureBreachError) {
159      await logEvent('ENCLOSURE_BREACH', {
160        sector: error.sector,
161        species: error.dinoSpecies,
162        count: error.count,
163        timestamp: error.timestamp
164      });
165      throw error;
166    }
167
168    console.error('Error simulating power failure:', error);
169    throw error;
170  }
171}
172
173// Repair power system
174export async function repairPowerSystem(id) {
175  try {
176    const enclosure = await getEnclosureById(id);
177
178    if (enclosure.powerStatus === 'active') {
179      throw new SecurityError(
180        'Power system is already active',
181        enclosure.sector,
182        'power'
183      );
184    }
185
186    // Update power status
187    const updatedEnclosure = await updateEnclosure(id, {
188      powerStatus: 'active'
189    });
190
191    await logEvent('POWER_RESTORED', {
192      enclosureId: id,
193      name: enclosure.name,
194      sector: enclosure.sector,
195      previousStatus: enclosure.powerStatus
196    });
197
198    const dinosaurs = await getAllDinosaurs();
199
200    return assessSecurityStatus(updatedEnclosure, dinosaurs);
201  } catch (error) {
202    console.error(`Error repairing power system for enclosure ${id}:`, error);
203    throw error;
204  }
205}

5. Utility Functions (utils.js)

1// modules/utils.js
2export function formatDate(date) {
3  return new Intl.DateTimeFormat('en-US', {
4    day: '2-digit', month: '2-digit', year: 'numeric',
5    hour: '2-digit', minute: '2-digit'
6  }).format(new Date(date));
7}
8
9export function formatTimeAgo(date) {
10  const diff = Date.now() - new Date(date).getTime();
11  const minutes = Math.floor(diff / 60000);
12  const hours = Math.floor(diff / 3600000);
13
14  if (minutes < 1) return 'just now';
15  if (minutes < 60) return `${minutes} min ago`;
16  if (hours < 24) return `${hours} h ago`;
17  return `${Math.floor(hours / 24)} days ago`;
18}
19
20export async function retry(fn, maxAttempts = 3, delay = 1000) {
21  let lastError;
22  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
23    try {
24      return await fn();
25    } catch (error) {
26      lastError = error;
27      if (attempt < maxAttempts) {
28        console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
29        await new Promise(resolve => setTimeout(resolve, delay));
30      }
31    }
32  }
33  throw lastError;
34}
35
36export function memoize(fn) {
37  const cache = new Map();
38  return async function(...args) {
39    const key = JSON.stringify(args);
40    if (cache.has(key)) {
41      return cache.get(key);
42    }
43    const result = await fn(...args);
44    cache.set(key, result);
45    return result;
46  };
47}

6. Monitoring Module (monitoring.js)

1// modules/monitoring.js
2import { getDinosaurs } from './api.js';
3import { DinoBehaviorError, EnclosureBreachError } from '../errors/index.js';
4import { formatTimeAgo } from './utils.js';
5
6class ParkMonitor {
7  constructor() {
8    this.intervals = [];
9    this.alertHandlers = [];
10    this.isRunning = false;
11  }
12
13  onAlert(handler) {
14    this.alertHandlers.push(handler);
15    return () => {
16      this.alertHandlers = this.alertHandlers.filter(h => h !== handler);
17    };
18  }
19
20  _fireAlert(alert) {
21    this.alertHandlers.forEach(handler => handler(alert));
22  }
23
24  async checkDinosaurStatuses() {
25    try {
26      const dinosaurs = await getDinosaurs();
27
28      for (const dino of dinosaurs) {
29        const hoursSinceFeeding = (Date.now() - new Date(dino.lastFed).getTime()) / 3600000;
30
31        if (hoursSinceFeeding > 8) {
32          this._fireAlert({
33            type: 'feeding',
34            severity: hoursSinceFeeding > 12 ? 'critical' : 'warning',
35            message: `${dino.name} hasn't been fed for ${formatTimeAgo(dino.lastFed)}`,
36            dinoId: dino.id
37          });
38        }
39
40        if (dino.behavior === 'escaped') {
41          this._fireAlert({
42            type: 'security',
43            severity: 'critical',
44            message: `BREACH: ${dino.name} (${dino.species}) has escaped!`,
45            dinoId: dino.id
46          });
47        }
48      }
49    } catch (error) {
50      console.error('Monitoring check failed:', error.message);
51    }
52  }
53
54  start(intervalMs = 30000) {
55    if (this.isRunning) return;
56    this.isRunning = true;
57    console.log('Park monitoring started');
58
59    const id = setInterval(() => {
60      this.checkDinosaurStatuses();
61    }, intervalMs);
62
63    this.intervals.push(id);
64    this.checkDinosaurStatuses(); // Run immediately
65  }
66
67  stop() {
68    this.intervals.forEach(id => clearInterval(id));
69    this.intervals = [];
70    this.isRunning = false;
71    console.log('Park monitoring stopped');
72  }
73}
74
75export const monitor = new ParkMonitor();

7. Configuration (config.js)

Application configuration file:

1// data/config.js
2
3export const appConfig = {
4  // General application settings
5  appName: 'Jurassic Tracker',
6  version: '1.0.0',
7
8  // Monitoring settings
9  monitoring: {
10    dinoBehaviorInterval: 60000, // Dinosaur behavior monitoring interval (ms)
11    enclosureSecurityInterval: 90000, // Enclosure security monitoring interval (ms)
12    autoStart: true // Automatically start monitoring on startup
13  },
14
15  // API settings
16  api: {
17    baseUrl: 'https://jurassic-park-api.example.com', // In reality we'd use a real URL
18    timeout: 10000, // API request timeout (ms)
19    retryCount: 3 // Number of retry attempts for failed requests
20  },
21
22  // Dinosaur risk assessment
23  dinorisk: {
24    dangerousSpecies: ['Tyrannosaurus', 'Velociraptor', 'Spinosaurus', 'Allosaurus', 'Dilophosaurus'],
25    behaviorRiskMatrix: {
26      'calm': { base: 1, factor: 0.8 },
27      'grazing': { base: 1, factor: 0.9 },
28      'sleeping': { base: 2, factor: 0.7 },
29      'exploring': { base: 4, factor: 1.0 },
30      'agitated': { base: 6, factor: 1.2 },
31      'hunting': { base: 7, factor: 1.5 },
32      'aggressive': { base: 9, factor: 2.0 }
33    },
34    hungerRiskHours: 3, // Hours after which hunger starts affecting risk
35    hungerRiskFactor: 0.5 // Hunger impact factor on risk
36  },
37
38  // Security levels for notifications
39  security: {
40    alertThresholds: {
41      low: 3,
42      medium: 5,
43      high: 7,
44      critical: 9
45    },
46    autoResolveAlerts: false // Whether to automatically mark alerts as resolved
47  }
48};
49
50export default appConfig;

8. Main Application File (index.js)

1// index.js
2import { getDinosaurs } from './modules/api.js';
3import { monitor } from './modules/monitoring.js';
4import { retry } from './modules/utils.js';
5
6async function initialize() {
7  console.log('=== JurassicTracker Starting ===');
8
9  // Set up alert handling
10  const unsubAlert = monitor.onAlert((alert) => {
11    const icon = alert.severity === 'critical' ? '🚨' : '⚠️';
12    console.log(`${icon} [${alert.type.toUpperCase()}] ${alert.message}`);
13  });
14
15  try {
16    // Fetch initial data with retry logic
17    const dinosaurs = await retry(() => getDinosaurs(), 3, 1000);
18    console.log(`Loaded ${dinosaurs.length} dinosaurs`);
19
20    // Start background monitoring
21    monitor.start(10000); // Check every 10 seconds
22
23    console.log('=== JurassicTracker Ready ===');
24
25    // Cleanup on shutdown
26    const cleanup = () => {
27      monitor.stop();
28      unsubAlert();
29      console.log('JurassicTracker shut down gracefully');
30    };
31
32    process.on('SIGINT', cleanup);
33    process.on('SIGTERM', cleanup);
34
35  } catch (error) {
36    console.error('Fatal initialization error:', error.message);
37    process.exit(1);
38  }
39}
40
41initialize();

Summary of Applied Techniques

In our JurassicTracker application we used all the most important techniques learned in this module:

  1. ES Modules β€” the entire app is organized into modules that import and export functionality
  2. Asynchrony:
    • We use async/await for clean asynchronous operation handling
    • We use Promise.all for parallel operation execution
    • We handle delays and time intervals
  3. Error handling:
    • We created a hierarchy of custom error classes (APIError, DinoError, SecurityError)
    • We use try/catch blocks for local error handling
    • We implement retry logic for operations that can be retried
  4. Background monitoring:
    • We use setInterval for cyclical monitoring tasks
    • We implement a notification system based on the Observer pattern
  5. Application state management:
    • We store state in module variables
    • We use Map for efficient data storage
    • We implement memoization for optimization

This application shows how to combine all the learned techniques into a functional, modular, and fault-tolerant system.

In the next exercise we'll delve into structuring code in even larger JavaScript projects, extending the techniques and patterns we've learned.

Go to CodeWorlds→