"Dotarliśmy do końca naszej podróży," ogłasza Dr. Ellie Sattler, stojąc w głównym laboratorium diagnostycznym InGen. "Przez ten moduł opanowałeś Web Workers, zarządzanie pamięcią, zaawansowane typy TypeScript - od utility types, przez conditional i mapped types, po template literal types i module augmentation. Nauczyłeś się też Test-Driven Development i integracji TypeScript z popularnymi frameworkami."
"Teraz zbudujesz kompletny system diagnostyczny Parku Jurajskiego - projekt, który połączy wszystkie te umiejętności w jeden, spójny system. Potraktuj to jako symulację prawdziwego, produkcyjnego kodu, który mógłby działać w naszym parku."
W tym projekcie stworzysz zaawansowany system diagnostyczny łączący: typowanie na poziomie eksperta z utility types i conditional types, template literal types do generowania API, mapped types do transformacji danych, module augmentation do rozszerzania istniejących bibliotek oraz podejście TDD z testami jednostkowymi.
Zbudujesz system
DiagnosticCenter, który:Zdefiniuj precyzyjne typy danych, wykorzystując zarówno wbudowane, jak i własne utility types. System musi obsługiwać różne kategorie dinozaurów z różnymi zestawami parametrów diagnostycznych.
1// Bazowy typ dinozaura
2interface DinosaurRecord {
3 id: string;
4 species: string;
5 name: string;
6 age: number;
7 weight: number;
8 height: number;
9 enclosureId: string;
10 diet: 'carnivore' | 'herbivore' | 'omnivore';
11 healthStatus: 'healthy' | 'sick' | 'critical' | 'quarantined';
12 lastCheckup: Date;
13 geneticModifications: string[];
14 dangerRating: 1 | 2 | 3 | 4 | 5;
15}
16
17// Własne utility types
18type DeepReadonly<T> = {
19 readonly [K in keyof T]: T[K] extends object
20 ? T[K] extends Date | Array<any>
21 ? T[K]
22 : DeepReadonly<T[K]>
23 : T[K];
24};
25
26type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
27 Pick<T, Exclude<keyof T, Keys>> &
28 { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys];
29
30type Mutable<T> = {
31 -readonly [K in keyof T]: T[K];
32};
33
34// Zablokowany rekord - nie można modyfikować po zamknięciu diagnostyki
35type LockedDiagnosticRecord = DeepReadonly<DinosaurRecord>;
36
37// Aktualizacja - wymagaj co najmniej jednego pola
38type DinosaurUpdate = RequireAtLeastOne<
39 Partial<Pick<DinosaurRecord, 'healthStatus' | 'weight' | 'enclosureId' | 'dangerRating'>>,
40 'healthStatus' | 'weight' | 'enclosureId' | 'dangerRating'
41>;
42
43// Rekord do wyświetlania - zamień Date na string
44type DisplayRecord<T> = {
45 [K in keyof T]: T[K] extends Date ? string : T[K];
46};
47
48type DinosaurDisplayRecord = DisplayRecord<DinosaurRecord>;
49
50// Filtrowanie pól numerycznych
51type NumericFields<T> = {
52 [K in keyof T as T[K] extends number ? K : never]: T[K];
53};
54
55type DinosaurMetrics = NumericFields<DinosaurRecord>;
56// Rezultat: { age: number; weight: number; height: number; dangerRating: 1|2|3|4|5 }Wykorzystaj typy warunkowe do automatycznego określania procedury diagnostycznej na podstawie gatunku i stanu zdrowia dinozaura. System musi "wiedzieć" na poziomie typów, jakie badania przeprowadzić.
1// Typy gatunków
2type CarnivoreSpecies = 'TRex' | 'Velociraptor' | 'Spinosaurus' | 'Dilophosaurus';
3type HerbivoreSpecies = 'Triceratops' | 'Brachiosaurus' | 'Stegosaurus' | 'Parasaurolophus';
4type FlyingSpecies = 'Pteranodon' | 'Dimorphodon';
5type AquaticSpecies = 'Mosasaurus' | 'Plesiosaurus';
6
7type AllSpecies = CarnivoreSpecies | HerbivoreSpecies | FlyingSpecies | AquaticSpecies;
8
9// Conditional type - procedura diagnostyczna zależy od gatunku
10type DiagnosticProcedure<S extends AllSpecies> =
11 S extends CarnivoreSpecies ? {
12 type: 'carnivore-diagnostic';
13 sedationRequired: true;
14 aggressionTest: boolean;
15 teethInspection: boolean;
16 clawMeasurement: boolean;
17 huntingBehaviorAnalysis: boolean;
18 } :
19 S extends HerbivoreSpecies ? {
20 type: 'herbivore-diagnostic';
21 sedationRequired: false;
22 digestiveAnalysis: boolean;
23 hoofInspection: boolean;
24 grazingPatternCheck: boolean;
25 } :
26 S extends FlyingSpecies ? {
27 type: 'flying-diagnostic';
28 sedationRequired: true;
29 wingspanMeasurement: number;
30 flightCapabilityTest: boolean;
31 nestInspection: boolean;
32 } :
33 S extends AquaticSpecies ? {
34 type: 'aquatic-diagnostic';
35 sedationRequired: true;
36 underwaterExam: boolean;
37 gillFunction: boolean;
38 divingDepthTest: number;
39 } :
40 never;
41
42// Wynik diagnostyki - zależny od procedury
43type DiagnosticResult<S extends AllSpecies> = {
44 species: S;
45 procedure: DiagnosticProcedure<S>;
46 timestamp: Date;
47 veterinarian: string;
48 passed: boolean;
49 notes: string;
50};
51
52// Użycie - TypeScript wymusza odpowiednie pola
53const trexDiagnostic: DiagnosticResult<'TRex'> = {
54 species: 'TRex',
55 procedure: {
56 type: 'carnivore-diagnostic',
57 sedationRequired: true,
58 aggressionTest: true,
59 teethInspection: true,
60 clawMeasurement: true,
61 huntingBehaviorAnalysis: false
62 },
63 timestamp: new Date(),
64 veterinarian: 'Dr. Harding',
65 passed: true,
66 notes: 'T-Rex w doskonałej formie. Sedacja przebiegła bez komplikacji.'
67};
68
69// Conditional type z infer - wyciągnij typ procedury
70type ExtractProcedureType<T> =
71 T extends DiagnosticResult<infer S>
72 ? DiagnosticProcedure<S>
73 : never;
74
75// Dystrybucyjny conditional type - filtrowanie gatunków wymagających sedacji
76type RequiresSedation<S extends AllSpecies> =
77 DiagnosticProcedure<S> extends { sedationRequired: true } ? S : never;
78
79type SedatedSpecies = RequiresSedation<AllSpecies>;
80// Rezultat: 'TRex' | 'Velociraptor' | ... (wszystkie wymagające sedacji)Stwórz system API, który na poziomie typów gwarantuje poprawność nazw endpointów, kluczy konfiguracji i komunikatów diagnostycznych.
1// Template literal types dla endpointów API
2type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
3type ApiVersion = 'v1' | 'v2';
4type Resource = 'dinosaurs' | 'enclosures' | 'diagnostics' | 'alerts';
5
6// Automatyczne generowanie ścieżek API
7type ApiEndpoint = `/api/${ApiVersion}/${Resource}`;
8// Rezultat: "/api/v1/dinosaurs" | "/api/v1/enclosures" | ... (12 kombinacji)
9
10type ApiEndpointWithId = `${ApiEndpoint}/${string}`;
11
12// Template literal types dla kluczy konfiguracji
13type SensorType = 'temperature' | 'heartRate' | 'movement' | 'stress';
14type SensorLocation = 'head' | 'body' | 'tail' | 'legs';
15
16type SensorConfigKey = `sensor_${SensorType}_${SensorLocation}`;
17// Rezultat: "sensor_temperature_head" | "sensor_temperature_body" | ... (16 kombinacji)
18
19type SensorConfig = Record<SensorConfigKey, {
20 enabled: boolean;
21 threshold: number;
22 interval: number;
23}>;
24
25// Template literal types dla komunikatów diagnostycznych
26type DiagnosticStatus = 'started' | 'completed' | 'failed' | 'cancelled';
27type DiagnosticMessage<S extends AllSpecies, D extends DiagnosticStatus> =
28 `[DIAGNOSTIC] Species: ${S} | Status: ${D}`;
29
30// Użycie
31const message: DiagnosticMessage<'TRex', 'completed'> =
32 '[DIAGNOSTIC] Species: TRex | Status: completed';
33
34// Ekstrakcja z template literal type
35type ExtractSpecies<T> = T extends `[DIAGNOSTIC] Species: ${infer S} | Status: ${string}`
36 ? S
37 : never;
38
39type ExtractedSpecies = ExtractSpecies<typeof message>; // "TRex"
40
41// Zaawansowane: konwersja camelCase na kebab-case
42type CamelToKebab<S extends string> =
43 S extends `${infer First}${infer Rest}`
44 ? Rest extends Uncapitalize<Rest>
45 ? `${Lowercase<First>}${CamelToKebab<Rest>}`
46 : `${Lowercase<First>}-${CamelToKebab<Rest>}`
47 : S;
48
49type TestKebab = CamelToKebab<'healthStatus'>; // "health-status"
50type TestKebab2 = CamelToKebab<'dangerRating'>; // "danger-rating"Wykorzystaj mapped types do automatycznego generowania formularzy, walidatorów i transformacji danych diagnostycznych.
1// Mapped type: generuj typ formularza z modelu danych
2type FormFields<T> = {
3 [K in keyof T]: {
4 value: T[K];
5 label: string;
6 required: boolean;
7 validate: (value: T[K]) => boolean;
8 };
9};
10
11type DinosaurForm = FormFields<Pick<DinosaurRecord, 'name' | 'species' | 'weight' | 'healthStatus'>>;
12
13// Mapped type: generuj typ walidatora
14type Validators<T> = {
15 [K in keyof T as `validate${Capitalize<K & string>}`]: (value: T[K]) => boolean;
16};
17
18type DinosaurValidators = Validators<Pick<DinosaurRecord, 'name' | 'age' | 'weight'>>;
19// Rezultat: {
20// validateName: (value: string) => boolean;
21// validateAge: (value: number) => boolean;
22// validateWeight: (value: number) => boolean;
23// }
24
25// Mapped type: generuj gettery i settery
26type Accessors<T> = {
27 [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
28} & {
29 [K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;
30};
31
32type DinosaurAccessors = Accessors<Pick<DinosaurRecord, 'name' | 'healthStatus'>>;
33// Rezultat: {
34// getName: () => string;
35// setName: (value: string) => void;
36// getHealthStatus: () => 'healthy' | 'sick' | 'critical' | 'quarantined';
37// setHealthStatus: (value: ...) => void;
38// }
39
40// Mapped type: zamień wartości na Observable
41type Observable<T> = {
42 [K in keyof T]: {
43 value: T[K];
44 subscribe: (callback: (newValue: T[K], oldValue: T[K]) => void) => void;
45 unsubscribe: (callback: Function) => void;
46 };
47};
48
49type ObservableDinosaurMetrics = Observable<DinosaurMetrics>;
50
51// Implementacja mapped type w runtime
52function createValidators<T extends Record<string, any>>(
53 rules: { [K in keyof T]: (value: T[K]) => boolean }
54): Validators<T> {
55 const validators = {} as any;
56 for (const key in rules) {
57 const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
58 validators[`validate${capitalizedKey}`] = rules[key];
59 }
60 return validators;
61}
62
63// Przykład użycia
64const dinoValidators = createValidators<Pick<DinosaurRecord, 'name' | 'age' | 'weight'>>({
65 name: (value) => value.length >= 2 && value.length <= 50,
66 age: (value) => value > 0 && value < 200,
67 weight: (value) => value > 0 && value < 100000
68});Rozszerz istniejące typy przez module augmentation i napisz system z podejściem TDD - najpierw testy, potem implementacja.
1// Module augmentation - rozszerzanie istniejącego modułu
2declare module './dinosaur-record' {
3 interface DinosaurRecord {
4 lastDiagnosticId?: string;
5 diagnosticHistory: DiagnosticEntry[];
6 riskScore: number;
7 }
8}
9
10interface DiagnosticEntry {
11 id: string;
12 date: Date;
13 type: string;
14 result: 'pass' | 'fail' | 'inconclusive';
15 veterinarian: string;
16}
17
18// TDD - najpierw piszemy testy
19// Test 1: DiagnosticCenter powinien obliczyć ryzyko
20function testCalculateRiskScore() {
21 const center = new DiagnosticCenter();
22
23 // Zdrowy roślinożerca - niskie ryzyko
24 const herbivoreRisk = center.calculateRiskScore({
25 diet: 'herbivore',
26 healthStatus: 'healthy',
27 dangerRating: 1,
28 age: 5
29 } as DinosaurRecord);
30
31 console.assert(herbivoreRisk < 30,
32 `Oczekiwano ryzyko < 30, otrzymano ${herbivoreRisk}`);
33
34 // Chory drapieżnik - wysokie ryzyko
35 const carnivoreRisk = center.calculateRiskScore({
36 diet: 'carnivore',
37 healthStatus: 'sick',
38 dangerRating: 5,
39 age: 12
40 } as DinosaurRecord);
41
42 console.assert(carnivoreRisk > 70,
43 `Oczekiwano ryzyko > 70, otrzymano ${carnivoreRisk}`);
44
45 console.log('✅ testCalculateRiskScore: PASSED');
46}
47
48// Test 2: System powinien filtrować dinozaury wymagające badania
49function testFilterDinosRequiringCheckup() {
50 const center = new DiagnosticCenter();
51
52 const dinosaurs: Partial<DinosaurRecord>[] = [
53 { name: 'Rex', lastCheckup: new Date('2024-01-01'), healthStatus: 'healthy' },
54 { name: 'Blue', lastCheckup: new Date('2025-12-01'), healthStatus: 'healthy' },
55 { name: 'Echo', lastCheckup: new Date('2025-12-15'), healthStatus: 'critical' }
56 ];
57
58 const needCheckup = center.filterRequiringCheckup(
59 dinosaurs as DinosaurRecord[],
60 30 // Dni od ostatniego badania
61 );
62
63 // Rex - badanie ponad 30 dni temu
64 // Echo - status "critical" wymaga natychmiastowego badania
65 console.assert(needCheckup.length === 2,
66 `Oczekiwano 2 dinozaury, otrzymano ${needCheckup.length}`);
67
68 console.log('✅ testFilterDinosRequiringCheckup: PASSED');
69}
70
71// Test 3: Generowanie raportu powinno zwracać prawidłowy format
72function testGenerateReport() {
73 const center = new DiagnosticCenter();
74
75 const report = center.generateReport('TRex', {
76 species: 'TRex',
77 procedure: {
78 type: 'carnivore-diagnostic',
79 sedationRequired: true,
80 aggressionTest: true,
81 teethInspection: true,
82 clawMeasurement: true,
83 huntingBehaviorAnalysis: false
84 },
85 timestamp: new Date(),
86 veterinarian: 'Dr. Harding',
87 passed: true,
88 notes: 'Badanie zakończone pomyślnie'
89 });
90
91 console.assert(report.includes('[DIAGNOSTIC]'),
92 'Raport powinien zawierać nagłówek [DIAGNOSTIC]');
93 console.assert(report.includes('TRex'),
94 'Raport powinien zawierać gatunek');
95 console.assert(report.includes('Dr. Harding'),
96 'Raport powinien zawierać weterynarza');
97
98 console.log('✅ testGenerateReport: PASSED');
99}
100
101// Implementacja (po napisaniu testów)
102class DiagnosticCenter {
103 calculateRiskScore(dino: Pick<DinosaurRecord, 'diet' | 'healthStatus' | 'dangerRating' | 'age'>): number {
104 let score = 0;
105
106 // Dieta wpływa na bazowe ryzyko
107 const dietScores = { carnivore: 30, omnivore: 15, herbivore: 5 };
108 score += dietScores[dino.diet];
109
110 // Stan zdrowia
111 const healthScores = { healthy: 0, sick: 20, critical: 40, quarantined: 30 };
112 score += healthScores[dino.healthStatus];
113
114 // Poziom zagrożenia (1-5) * 6
115 score += dino.dangerRating * 6;
116
117 // Wiek - starsze dinozaury są bardziej nieprzewidywalne
118 if (dino.age > 10) score += 10;
119
120 return Math.min(100, Math.max(0, score));
121 }
122
123 filterRequiringCheckup(dinosaurs: DinosaurRecord[], maxDaysSinceCheckup: number): DinosaurRecord[] {
124 const now = new Date();
125 return dinosaurs.filter(dino => {
126 // Krytyczny stan - zawsze wymaga badania
127 if (dino.healthStatus === 'critical') return true;
128
129 // Sprawdź czas od ostatniego badania
130 const daysSinceCheckup = Math.floor(
131 (now.getTime() - dino.lastCheckup.getTime()) / (1000 * 60 * 60 * 24)
132 );
133 return daysSinceCheckup > maxDaysSinceCheckup;
134 });
135 }
136
137 generateReport<S extends AllSpecies>(
138 species: S,
139 result: DiagnosticResult<S>
140 ): string {
141 return [
142 `[DIAGNOSTIC] Species: ${species} | Status: ${result.passed ? 'completed' : 'failed'}`,
143 `Weterynarz: ${result.veterinarian}`,
144 `Data: ${result.timestamp.toISOString()}`,
145 `Wynik: ${result.passed ? 'POZYTYWNY' : 'NEGATYWNY'}`,
146 `Notatki: ${result.notes}`
147 ].join('\n');
148 }
149}
150
151// Uruchomienie testów
152testCalculateRiskScore();
153testFilterDinosRequiringCheckup();
154testGenerateReport();Zacznij od zdefiniowania bazowych typów i interfejsów - to fundament Twojego systemu diagnostycznego. Następnie zbuduj conditional types dla procedur diagnostycznych, template literal types dla API i mapped types dla transformacji. Na końcu napisz testy i implementację
DiagnosticCenter.Sugerowana kolejność implementacji:
1// 1. Typy bazowe: DinosaurRecord, AllSpecies
2// 2. Utility types: DeepReadonly, RequireAtLeastOne, NumericFields
3// 3. Conditional types: DiagnosticProcedure<S>, DiagnosticResult<S>
4// 4. Template literal types: ApiEndpoint, SensorConfigKey
5// 5. Mapped types: FormFields, Validators, Accessors
6// 6. Module augmentation: rozszerzenie DinosaurRecord
7// 7. Testy jednostkowe (TDD)
8// 8. Implementacja DiagnosticCenter
9// 9. Integracja i uruchomienie testówPamiętaj o kluczowych zasadach:
infer pozwalają wyciągać typy z zagnieżdżonych strukturas pozwalają na zmianę nazw kluczy (remapping)declare module i merge'uje się z oryginalnym typem"Pamiętaj," mówi Dr. Sattler z uśmiechem, "w Parku Jurajskim nie ma miejsca na błędy typów. Każdy
any to potencjalna luka w zabezpieczeniach, każdy brakujący typ to otwarta brama zagrody. TypeScript daje Ci narzędzia, aby złapać 90% błędów jeszcze zanim kod zostanie uruchomiony. Utility types, conditional types, mapped types, template literal types - to Twój arsenał. Użyj go mądrze.""A testy? Testy to jak systemy alarmowe w zagrodach. Nie budujesz ich po tym, jak T-Rex ucieknie. Budujesz je ZANIM to się stanie. TDD to filozofia, która chroni Twój kod przed katastrofą."
Otwórz edytor i zbuduj swój system diagnostyczny - kompletny, w pełni typowany i przetestowany!