Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Metama'lumot yozish
- 2.1 SetMetadata
- 2.2 Reflector.createDecorator
- 3. Metama'lumotni o'qish: Reflector
- 3.1 Kanon tajribasi
- 3.2 O'zimiz yozamiz
- 3.3 Qaysi metodni tanlash
- 4. DiscoveryService: ilovani ichidan ko'rish
- 4.1 O'z @Hodisada dekoratorimiz
- 4.2 Scope'li provider'lar: instance — bo'sh qolip
- 4.3 Klass darajasidagi belgi: DiscoveryService.createDecorator
- 5. Ko'p uchraydigan xatolar
- 6. Mashqlar
- 1-mashq (oson): override yoki merge?
- 2-mashq (o'rta): JS'da @Hodisada modeli
- 3-mashq (qiyin): marshrut jadvali
- 7. Loyiha qadami: marshrutlar testi va commit
- 7.1 Test
- 7.2 Test nimani ushlaydi — biz sinadik
- 7.3 Tekshiruv
- 7.4 Commit
- 8. Real ishda
- Xulosa
- Manbalar
NestJS Reflector va DiscoveryService: metadata, o'z dekoratoringiz va marshrut testi
Qisqacha: Nest'ning "sehri" — metama'lumot: dekorator klass yoki metodga yorliq yopishtiradi (
SetMetadata(kalit, qiymat)yoki tipliReflector.createDecorator<T>()), keyin guard, interceptor yoki xizmat uniReflectorbilan o'qiydi. Metod va klassda bir xil kalit bo'lsa, ikki yo'l bor. Birinchisi —getAllAndOverride: birinchi topilganini (odatda metodnikini) beradi. Ikkinchisi —getAllAndMerge: hammasini birlashtiradi.DiscoveryServiceesa ilovadagi hamma provider va controller'larni ro'yxat qilib beradi — o'z@OnXdekoratoringizni yozish yoki marshrutlar jadvalini test bilan tekshirish uchun.
Bu darsda
SetMetadatavaReflector.createDecoratorbilan o'z dekoratoringizni yozasiz.get,getAllAndOverride,getAllAndMergefarqini kanon tajribasida ko'rasiz va kichik nusxasini o'zingiz yasaysiz.DiscoveryServicevaMetadataScannerbilan "dekorator belgilagan metodlarni" ishga tushishda topasiz.vazifalar-apimarshrutlari jadvalini Nest metama'lumotidan o'qib, 405 javoblarini avtomatik tekshiradigan test yozasiz.- Shu holatni commit qilasiz.
Oldin bilishingiz kerak: Lifecycle hook'lar, Dynamic modullar, Legacy dekoratorlar va reflect-metadata, Request lifecycle, Controller'lar va route dekoratorlari.
Bu holatning oxirgi darsi. Oldingi besh darsda konteyner ichini o'rgandik: DI, provider shakllari, scope, dinamik modul, hayot sikli. Endi Nest'ning o'zi foydalanadigan vositalarni olamiz — metama'lumotni o'qish va ilovani "ichidan ko'rish" — va ular bilan kanon bir eski kamchilikni yopadi.
1. Nega bu kerak?
Controller'lar darsida @All() handler'lari Allow sarlavhasini qo'lda yozardi: "GET, HEAD, PUT, PATCH, DELETE". O'sha darsning «Allow sarlavhasini hisoblash» mashqida bu qatorlarni hisoblaydigan funksiya yozgandik va oxirida aytgandik: haqiqiy loyihada bu tekshiruv testda, Nest'ning DiscoveryService i bilan bo'ladi.
Muammo oddiy. Ertaga kimdir @Patch() ni /vazifalar ga qo'shadi va Allow satrini yangilashni unutadi. Mijoz 405 javobida noto'g'ri ro'yxatni oladi. Hech qanday test buni ko'rmaydi — 25-qismdagi Express kodida ham shunday edi.
Express'da marshrutlar jadvali umuman yo'q edi. router.get(...), router.post(...) va .all(metodYoq("GET, HEAD, POST")) — har biri alohida qator. Express o'zi bu ro'yxatni "bilardi", lekin ichki obyektlarda, ommaviy API'siz. Test yozish uchun uni o'qib bo'lmasdi. Shuning uchun Allow satri bilan haqiqiy marshrutlar faqat dasturchining diqqatiga tayanib bir xil turardi.
Nest bu ma'lumotni allaqachon biladi: har @Get(), @Patch() dekoratori metodga yo'l va HTTP metodini metama'lumot sifatida yozgan. Uni o'qisak, haqiqiy marshrutlar jadvali qo'limizda. Bu darsda shu yo'lni ikki qadamda bosamiz: avval bitta metodning belgisini o'qishni (Reflector), keyin butun ilovani aylanib chiqishni (DiscoveryService) o'rganamiz.
O'xshatish: kutubxonadagi kitoblar. Har kitob ichiga kartochka qo'yilgan (dekorator — muqovadagi yorliq). Kutubxonachi (Reflector) kartochkani o'qiydi; inventarizatsiya (DiscoveryService) esa hamma javonni aylanib, kitoblar ro'yxatini tuzadi.
2. Metama'lumot yozish
2.1 SetMetadata
const ROLLAR_KALIT = "rollar";
const Rollar = (...r: string[]) => SetMetadata(ROLLAR_KALIT, r);
@Controller("v")
@Rollar("foydalanuvchi")
class VController {
@Get("b") @Rollar("admin") b() { return "b"; }
}SetMetadata(kalit, qiymat) — tayyor dekorator yasaydi: u klassga yoki metodga kalit → qiymat yozadi. Rollar — uning ustiga qulay qobiq: @Rollar("admin"). Nest'ning o'z dekoratorlari ham shunday ishlaydi: @Get("b") metodga path va method kalitlarini yozadi.
Bu ikki kalit Nest'da konstanta sifatida bor: PATH_METADATA (qiymati — "path") va METHOD_METADATA ("method"). @Controller("v") esa klassga path yozadi. Ya'ni marshrut haqidagi hamma ma'lumot — yo'l va metod — aslida oddiy metama'lumot. Loyiha qadamidagi test aynan shu ikki kalitni o'qib, ilovaning marshrutlar jadvalini quradi.
Metama'lumot qachon yoziladi? Dekorator klass e'lon qilingan lahzada, fayl yuklanganda bir marta ishlaydi. So'rov kelganda hech narsa yozilmaydi — faqat o'qiladi. Reflector.get — oddiy qidiruv, u har so'rovda guard ichida chaqirilsa ham sezilarli vaqt olmaydi.
Tekshirib ko'ring: Bitta metodda
@Rollar("admin")ikki marta yozildi:@Rollar("admin") @Rollar("moliya").reflector.getnima qaytaradi?
Javob
Bitta qiymat — ikkalasi birlashmaydi. SetMetadata bir kalitga yozadi; ikkinchi dekorator birinchisining ustidan yozadi. Dekoratorlar pastdan yuqoriga qo'llanadi (Legacy dekoratorlar va reflect-metadata), shuning uchun oxirgi bo'lib tepadagisi yozadi. Biz NestJS 12.2.0 da tekshirdik — natija [ 'admin' ]. Bir nechta rol kerak bo'lsa — bitta chaqiruvda: @Rollar("admin", "moliya").
2.2 Reflector.createDecorator
SetMetadata ning kamchiligi — kalit satr va qiymat turi unknown. O'qishda turni qo'lda aytish kerak, kalitni noto'g'ri yozish oson. Nest 10.2 versiyasidan beri tipli muqobil bor:
const Ochiq = Reflector.createDecorator<boolean>({
transform: (v) => v ?? true,
});
@Get("c") @Ochiq() c() { return "c"; }createDecorator<boolean>() — dekorator ham, kalit ham bir obyektda. O'qishda reflector.get(Ochiq, handler) — tur avtomatik boolean, qo'lda aytish shart emas. Lekin bitta kamchilik bor: turda undefined yo'q, garchi belgisiz handler'da aynan undefined kelsa ham. Bu kanon topgan haqiqiy tuzoq — uni Guard'lar darsida lint chiqishi bilan ko'ramiz. transform — argumentsiz @Ochiq() yozilsa true qo'yadi. Bunday dekorator keyinroq auth'da kerak bo'ladi: "bu yo'l login'siz ochiq" (Global auth guard, @Public va @Roles).
3. Metama'lumotni o'qish: Reflector
3.1 Kanon tajribasi
Kanon guard ichida uch metod natijasini chiqardi. Controller'da @Rollar("foydalanuvchi"), b metodida @Rollar("admin"), c da @Ochiq() (kanon tajribasi, Vitest muhitida):
1) Reflector — guard ichida (controller: @Rollar("foydalanuvchi")):
VController.a: {"getAllAndOverride":["foydalanuvchi"],"getAllAndMerge":["foydalanuvchi"],"Ochiq (createDecorator, tipli)":false}
VController.b: {"get(handler)":["admin"],"getAllAndOverride":["admin"],"getAllAndMerge":["admin","foydalanuvchi"],"Ochiq (createDecorator, tipli)":false}
VController.c: {"getAllAndOverride":["foydalanuvchi"],"getAllAndMerge":["foydalanuvchi"],"Ochiq (createDecorator, tipli)":true}Guard nishonlarni [c.getHandler(), c.getClass()] tartibida beradi — avval metod, keyin klass (Request lifecycle darsidagi ExecutionContext):
| Metod | a (metodda yo'q) |
b (metodda admin) |
|---|---|---|
get(kalit, handler) |
undefined (JSON'da tushib qoldi) |
["admin"] |
getAllAndOverride |
["foydalanuvchi"] — klassdan |
["admin"] — metod ustun |
getAllAndMerge |
["foydalanuvchi"] |
["admin", "foydalanuvchi"] |
Qachon qaysi biri:
- Override — "eng aniq qoida yutadi": controller hammaga
foydalanuvchidesa, bitta metodadmintalab qilsa — metodniki. Rollar va@Ochiquchun deyarli doim shu. - Merge — "hammasi qo'shiladi": masalan, controller
audittegini qo'ysa, metodmoliyategini — log uchun ikkalasi kerak.
3.2 O'zimiz yozamiz
Reflector ning yuragi — Reflect.getMetadata ustidagi bir necha qator. Kichik nusxasi (TypeScript, dekoratorlar bilan; saytda ishga tushmaydi, tekshiruvimizda tsc va Node'da yurgizilgan):
// @ts-options: {"experimentalDecorators": true}
import "reflect-metadata";
// Nest'ning SetMetadata va Reflector g'oyasi — kichik nusxa
const SetMetadata = (kalit: string, qiymat: unknown) =>
(nishon: object, _k?: string | symbol, d?: PropertyDescriptor) => {
// metod dekoratori — funksiyaga, klass dekoratori — klassga
Reflect.defineMetadata(kalit, qiymat, d ? d.value : nishon);
};
const Rollar = (...r: string[]) => SetMetadata("rollar", r);
const reflector = {
get: (k: string, n: object): unknown => Reflect.getMetadata(k, n),
getAllAndOverride(k: string, nishonlar: object[]): unknown {
for (const n of nishonlar) {
const q: unknown = Reflect.getMetadata(k, n);
if (q !== undefined) return q;
}
return undefined;
},
getAllAndMerge(k: string, nishonlar: object[]): unknown[] {
return nishonlar.flatMap(
(n) =>
(Reflect.getMetadata(k, n) as unknown[] | undefined) ?? [],
);
},
};
@Rollar("foydalanuvchi")
class VController {
a() { return "a"; }
@Rollar("admin")
b() { return "b"; }
}
for (const nom of ["a", "b"] as const) {
const handler = VController.prototype[nom];
const nishonlar = [handler, VController];
console.log(nom, JSON.stringify({
handler: reflector.get("rollar", handler) ?? null,
override: reflector.getAllAndOverride("rollar", nishonlar),
merge: reflector.getAllAndMerge("rollar", nishonlar),
}));
}Konsolda:
a {"handler":null,"override":["foydalanuvchi"],"merge":["foydalanuvchi"]}
b {"handler":["admin"],"override":["admin"],"merge":["admin","foydalanuvchi"]}Natija kanon tajribasidagi bilan bir xil. a qatorida handler — null: metodning o'zida belgi yo'q, shuning uchun override va merge klassdagi foydalanuvchi ni oldi. E'tibor bering: metod dekoratori metama'lumotni d.value ga — funksiyaning o'ziga — yozadi, klass dekoratori esa klassga. Shuning uchun guard getHandler() (funksiya) va getClass() (klass) ni beradi. Haqiqiy Reflector farqi: u Nest konteynerida provider (DI bilan olinadi) va createDecorator kalitlarini ham tushunadi.
Tekshirib ko'ring: Controller'da
@Ochiq(), bitta metodda esa@Ochiq(false)— "bu metod ochiq emas". GuardgetAllAndOverride(Ochiq, [handler, klass])bilan nima oladi va bu to'g'rimi?
Javob
false — metodniki ustun, va bu to'g'ri: eng aniq qoida yutdi. Agar getAllAndMerge ishlatilsa, [false, true] kabi massiv chiqardi va "ochiqmi?" degan savolga bir ma'noli javob bo'lmasdi. Ha/yo'q qiymatlar uchun — override.
3.3 Qaysi metodni tanlash
Uch savol bilan tanlanadi:
- Belgi faqat metodda bo'lishi mumkinmi? Ha —
get(kalit, getHandler())yetadi. Masalan,@JsonTana()faqat metodga qo'yiladi; loyihadagi guard shunday o'qiydi. - Klass va metod ikkalasida bo'lishi mumkin, qiymat — ha/yo'q yoki bitta tanlov?
getAllAndOverride: eng aniq qoida yutadi. Rollar,@Ochiq, kesh muddati. - Ikkala darajadagi qiymatlar birga kerakmi?
getAllAndMerge: teglar, audit belgilari, log maydonlari.
Xato tanlov ikki xil zarar beradi. Override o'rniga merge — "faqat admin" qoidasi "admin yoki foydalanuvchi" ga aylanadi va ruxsat kengayadi. Merge o'rniga override — controller'dagi audit tegi metod tegiga almashadi va logdan yo'qoladi. Birinchisi xavfsizlik xatosi, ikkinchisi — ma'lumot yo'qolishi.
Tekshirib ko'ring: Controller'da
@KeshMuddati(60), bitta metodda@KeshMuddati(5). Kesh interceptor'i qaysi metod bilan o'qishi kerak va natija nima?
Javob
getAllAndOverride — natija 5. Muddat bitta son: "60 va 5" dan iborat massiv ma'nosiz. Metod qoidasi aniqroq, u yutadi. Belgisiz metodlar esa controller'dagi 60 ni oladi.
4. DiscoveryService: ilovani ichidan ko'rish
4.1 O'z @Hodisada dekoratorimiz
Reflector bitta klass yoki metodni o'qiydi. Lekin "ilovadagi qaysi metodlarda @Hodisada bor?" degan savolga kim javob beradi? Buning uchun DiscoveryService — hamma provider va controller'lar ro'yxati — va MetadataScanner — klassning hamma metod nomlari.
Kanon kichik hodisa tizimini yozdi:
const Hodisada = Reflector.createDecorator<string>();
@Injectable()
class Hodisalar implements OnApplicationBootstrap {
readonly tinglovchilar =
new Map<string, ((x: unknown) => unknown)[]>();
constructor(
private readonly discovery: DiscoveryService,
private readonly scanner: MetadataScanner,
private readonly reflector: Reflector,
) {}
onApplicationBootstrap(): void {
for (const w of this.discovery.getProviders()) {
const obj = w.instance as Record<string, unknown> | undefined;
if (!obj || !w.isDependencyTreeStatic()) continue;
const proto = Object.getPrototypeOf(obj) as object;
for (const nom of this.scanner.getAllMethodNames(proto)) {
const fn = obj[nom] as (x: unknown) => unknown;
const hodisa = this.reflector.get(Hodisada, fn);
if (hodisa === undefined) continue;
const r = this.tinglovchilar.get(hodisa) ?? [];
r.push(fn.bind(obj));
this.tinglovchilar.set(hodisa, r);
}
}
}
chiqar(hodisa: string, x: unknown): void {
for (const f of this.tinglovchilar.get(hodisa) ?? []) f(x);
}
}
@Injectable() class Bildirishnoma {
@Hodisada("vazifa.yaratildi") yubor(x: unknown) { /* … */ }
}
@Injectable() class Statistika {
@Hodisada("vazifa.yaratildi") sana() { /* … */ }
@Hodisada("vazifa.ochirildi") ayir() { /* … */ }
}(Kod qisqartirilgan; modulda imports: [DiscoveryModule] — DiscoveryService shu moduldan.)
2) Discovery — onApplicationBootstrap:
topildi: Bildirishnoma.yubor → "vazifa.yaratildi"
topildi: Statistika.sana → "vazifa.yaratildi"
topildi: Statistika.ayir → "vazifa.ochirildi"
2b) chiqar("vazifa.yaratildi"):
Bildirishnoma.yubor({"id":7})
Statistika.sana()Ishga tushishda Hodisalar hamma provider'ni aylanib chiqdi. U @Hodisada belgilangan uchta metodni topdi va ro'yxatga oldi. chiqar("vazifa.yaratildi") — ikkala tinglovchi chaqirildi. Bildirishnoma va Statistika bir-birini ham, Hodisalar ni ham bilmaydi — faqat dekorator yozilgan. Circular dependency darsidagi "halqani hodisa bilan buzish" g'oyasining Nest uslubi.
Uch tafsilot:
onApplicationBootstrap— hamma provider yaratilgan, ro'yxat to'liq (Lifecycle hook'lar).isDependencyTreeStatic()— provider o'zi ham, bog'liqliklari ham REQUEST scope'da emas; nusxa ishga tushishda yaratilgan (Provider scope'lari). TRANSIENT uchun utrueqaytaradi — bu nozik joy, pastda ko'ramiz.fn.bind(obj)— metod keyinroq chaqirilgandathisnusxaga bog'liq qolsin (bind).
@nestjs/event-emitter ning @OnEvent, @nestjs/schedule ning @Cron dekoratorlari aynan shu naqshda ishlaydi — Hodisalar va Rejalashtirish darslarida ularni ishlatamiz.
flowchart LR
D["@Hodisada('vazifa.yaratildi')"] -->|"metadata yozadi"| M["metod funksiyasi"]
B["onApplicationBootstrap"] --> DS["DiscoveryService.getProviders"]
DS --> MS["MetadataScanner: metod nomlari"]
MS --> R["Reflector.get(Hodisada, fn)"]
R --> J["tinglovchilar jadvali"]
C["chiqar(hodisa)"] --> JDiagrammaning chap qismi — kompilyatsiya va yuklanish paytida (dekorator yozadi), o'ng qismi — ishga tushishda (Discovery o'qiydi), pastki — ish vaqtida.
4.2 Scope'li provider'lar: instance — bo'sh qolip
w.instance doim haqiqiy nusxa emas. Biz demo loyihada uch provider'ni Discovery bilan ko'rdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0). Transient — TRANSIENT, ichida id maydoni; Sorovli — REQUEST, ichida x = 1; A — oddiy xizmat, constructor'da Transient ni oladi. Har biri uchun isDependencyTreeStatic() va instance ning JSON ko'rinishi (hook qatorlarini olib tashladik):
Transient static: true instance: {} Transient
Sorovli static: false instance: {} Sorovli
A static: true instance: {"t":{"id":1}} ANima bo'ldi? Uchinchi qator kutilganidek: A haqiqiy nusxa, ichida o'ziga berilgan Transient (id: 1). Birinchi ikkitasida esa instance — bo'sh obyekt {}. Klass to'g'ri, lekin constructor ishlamagan: na id, na x bor. Bu — Nest'ning ichki "qolipi". Haqiqiy nusxalar boshqa joyda: TRANSIENT'niki — har iste'molchida, REQUEST'niki — har so'rovda.
Eng nozik joy — birinchi qator: static: true. Ya'ni isDependencyTreeStatic() filtri TRANSIENT qolipini o'tkazib yuboradi. Agar TRANSIENT xizmatda @Hodisada metodi bo'lsa, Hodisalar uni qolipga bind qiladi — this ichida maydonlar yo'q bo'ladi. Ishonchli filtr: !w.isDependencyTreeStatic() || w.isTransient — ikkalasini ham o'tkazib yuborish. Kanon misolida TRANSIENT provider yo'q, shuning uchun u bu tekshiruvsiz ishladi.
4.3 Klass darajasidagi belgi: DiscoveryService.createDecorator
Ba'zan metod emas, butun klass belgilanadi: "bu xizmat — xabar yuboruvchi kanal". Buning uchun DiscoveryService ning o'z dekorator yasovchisi bor. U getProviders() ga filtr beradi — hamma provider'ni aylanib chiqish shart emas. Demo (biz ishga tushirdik, o'sha versiyalar):
const Kanal = DiscoveryService.createDecorator<string>();
@Injectable() @Kanal("telegram")
class TelegramYuboruvchi {}
@Injectable() @Kanal("email")
class EmailYuboruvchi {}
@Injectable()
class Hisobchi {}
// ilova yig'ilgach:
const topilgan = d.getProviders({ metadataKey: Kanal.KEY });
for (const w of topilgan) {
console.log(w.name, "→", d.getMetadataByDecorator(Kanal, w));
}
console.log("jami provider'lar:", d.getProviders().length,
"| belgilangan:", topilgan.length);TelegramYuboruvchi → telegram
EmailYuboruvchi → email
jami provider'lar: 23 | belgilangan: 2Nima bo'ldi? Filtr faqat ikki belgili klassni qaytardi, Hisobchi tushib qoldi. "Jami 23" ga qarang: bizning modulda uchta xizmat bor, qolgan 20 tasi — Nest'ning ichki provider'lari (Reflector, ModuleRef, DiscoveryService …) va modul klasslarining o'zi. Filtrsiz getProviders() hammasini beradi — shuning uchun o'z kodingizda doim belgi bo'yicha tanlang.
Bir farq: getMetadataByDecorator ning turi string | undefined — undefined ni hisobga oladi. Reflector.createDecorator ning get() turi esa buni qilmaydi; bu tuzoqni Guard'lar darsida haqiqiy lint chiqishi bilan ko'ramiz.
5. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
reflector.get(...) doim undefined |
metama'lumot klassga yozilgan, metoddan o'qilyapti (yoki aksincha) | getHandler() va getClass() ikkalasini bering |
| Metod qoidasi klassnikini bosmaydi | nishonlar tartibi [klass, handler] |
[handler, klass] |
Nest can't resolve dependencies of the Hodisalar (?) |
DiscoveryModule import qilinmagan |
imports: [DiscoveryModule] |
Discovery'da instance bo'sh {}, maydonlari yo'q |
REQUEST yoki TRANSIENT provider'ning qolipi | !w.isDependencyTreeStatic() || w.isTransient — o'tkazib yuboring |
getProviders() 20+ begona provider beradi |
Nest'ning ichki provider'lari ham ro'yxatda | belgi bo'yicha filtr (metadataKey) |
Chaqirilgan metodda this undefined |
metod nusxadan ajratib saqlangan | fn.bind(nusxa) |
Uchinchi qatordagi xato eng ko'p uchraydi. Biz uni demo loyihada ataylab chiqardik: Hodisalar constructor'ida DiscoveryService bor, modulda esa DiscoveryModule yo'q (biz ishga tushirdik: NestJS 12.2.0):
Nest can't resolve dependencies of the Hodisalar (?). Please make sure that the argument DiscoveryService at index [0] is available in the HodisaModule module.
Potential solutions:
- Is HodisaModule a valid NestJS module?
- If DiscoveryService is a provider, is it part of the current HodisaModule?
- If DiscoveryService is exported from a separate @Module, is that module imported within HodisaModule?
@Module({
imports: [ /* the Module containing DiscoveryService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errorsTarjimasi: "Nest Hodisalar ning bog'liqliklarini topa olmadi. 0-o'rindagi DiscoveryService argumenti HodisaModule modulida mavjudligiga ishonch hosil qiling." (?) — topilmagan argumentning o'rni. Uchinchi taklif to'g'ri yo'lni ko'rsatadi: DiscoveryService boshqa moduldan keladi, o'sha modulni (DiscoveryModule) import qiling. Bu — DI va IoC konteyner darsidagi o'sha xato; Reflector bilan esa u chiqmaydi, chunki Reflector har modulda o'z-o'zidan bor.
6. Mashqlar
Mashqlar kurs/mashqlar/30/14-reflector/ da.
1-mashq (oson): override yoki merge?
Controller'da @Rollar("foydalanuvchi"), metodda @Rollar("admin"). To'ldiring:
getAllAndOverride— ]getAllAndMerge—["admin",]- Metodda dekorator yo'q bo'lsa
getAllAndOverride— ] - Ilovadagi hamma provider'lar ro'yxatini beradigan xizmat —
- Klassning metod nomlarini beradigan vosita —
Yechim
- ["admin"] — metod birinchi nishon, u yutadi.
- "foydalanuvchi" — merge ikkalasini qo'shadi, metodniki oldin.
- ["foydalanuvchi"] — metodda yo'q, klassdan olindi.
- DiscoveryService —
DiscoveryModuledan. - MetadataScanner —
getAllMethodNames(proto).
2-mashq (o'rta): JS'da @Hodisada modeli
Dekoratorsiz JS modeli: hodisada(Klass, metodNomi, hodisa) funksiyasi Map ga (klass.prototype[metod] → hodisa) yozsin. kashfEt(nusxalar) hamma nusxalarning prototip metodlarini aylanib (Object.getOwnPropertyNames), belgilanganlarini tinglovchilar ga (bind bilan) yig'sin. chiqar(hodisa, x) ularni chaqirsin. Darsdagi Bildirishnoma va Statistika misolini takrorlang. Ishora: constructor nomini o'tkazib yuboring.
Yechim
const metama = new Map(); // funksiya → hodisa nomi
function hodisada(Klass, metod, hodisa) {
metama.set(Klass.prototype[metod], hodisa);
}
function kashfEt(nusxalar) {
const tinglovchilar = new Map();
for (const obj of nusxalar) {
const proto = Object.getPrototypeOf(obj);
for (const nom of Object.getOwnPropertyNames(proto)) {
if (nom === "constructor") continue;
const hodisa = metama.get(proto[nom]);
if (hodisa === undefined) continue;
const r = tinglovchilar.get(hodisa) ?? [];
tinglovchilar.set(hodisa, [...r, proto[nom].bind(obj)]);
const kim = `${proto.constructor.name}.${nom}`;
console.log(`topildi: ${kim} → ${hodisa}`);
}
}
return (hodisa, x) => {
for (const f of tinglovchilar.get(hodisa) ?? []) f(x);
};
}
class Bildirishnoma {
yubor(x) {
console.log(`Bildirishnoma.yubor(${JSON.stringify(x)})`);
}
}
class Statistika {
soni = 0;
sana() {
this.soni++;
console.log(`Statistika.sana(): ${this.soni}`);
}
ayir() { this.soni--; }
}
hodisada(Bildirishnoma, "yubor", "vazifa.yaratildi");
hodisada(Statistika, "sana", "vazifa.yaratildi");
hodisada(Statistika, "ayir", "vazifa.ochirildi");
const chiqar = kashfEt([new Bildirishnoma(), new Statistika()]);
chiqar("vazifa.yaratildi", { id: 7 });
chiqar("vazifa.yaratildi", { id: 8 });Konsolda:
topildi: Bildirishnoma.yubor → vazifa.yaratildi
topildi: Statistika.sana → vazifa.yaratildi
topildi: Statistika.ayir → vazifa.ochirildi
Bildirishnoma.yubor({"id":7})
Statistika.sana(): 1
Bildirishnoma.yubor({"id":8})
Statistika.sana(): 2Statistika.sana(): 2 — bind tufayli this.soni haqiqiy nusxada o'sdi. bind siz this — undefined va TypeError bo'lardi. Model kalit sifatida funksiyaning o'zini ishlatadi — Reflect.defineMetadata(kalit, qiymat, d.value) ham aynan shunday.
3-mashq (qiyin): marshrut jadvali
Controller'lar ro'yxati beriladi: { asos: "vazifalar", metodlar: [{ yol: "/", metod: "GET" }, { yol: ":id", metod: "ALL" }, …] }. jadval(controllerlar) funksiyasini yozing: har to'liq yo'l uchun "GET, HEAD, POST | 405" ko'rinishidagi satr (tartib: GET, HEAD, POST, PUT, PATCH, DELETE; GET bo'lsa HEAD ham; ALL bo'lsa | 405). Darsdagi testning marshrutJadvali funksiyasining JS shakli. Ishora: /${asos} va yol === "/" ? "" : "/" + yol.
Yechim
const TARTIB = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"];
function jadval(controllerlar) {
const j = new Map();
for (const c of controllerlar) {
for (const { yol, metod } of c.metodlar) {
const toliq = `/${c.asos}${yol === "/" ? "" : `/${yol}`}`;
const y = j.get(toliq) ??
{ metodlar: new Set(), allBor: false };
if (metod === "ALL") y.allBor = true;
else {
y.metodlar.add(metod);
if (metod === "GET") y.metodlar.add("HEAD");
}
j.set(toliq, y);
}
}
return Object.fromEntries([...j].map(([k, v]) => [k,
TARTIB.filter((m) => v.metodlar.has(m)).join(", ") +
(v.allBor ? " | 405" : "")]));
}
console.log(jadval([
{
asos: "vazifalar",
metodlar: [
{ yol: "/", metod: "GET" }, { yol: "/", metod: "POST" },
{ yol: "/", metod: "ALL" }, { yol: ":id", metod: "GET" },
{ yol: ":id", metod: "DELETE" }, { yol: ":id", metod: "ALL" },
],
},
{ asos: "eksportlar", metodlar: [{ yol: "/", metod: "POST" }] },
]));Konsolda:
{
'/vazifalar': 'GET, HEAD, POST | 405',
'/vazifalar/:id': 'GET, HEAD, DELETE | 405',
'/eksportlar': 'POST'
}Haqiqiy testda bu ro'yxat qo'lda yozilmaydi — DiscoveryService va Reflector uni Nest metama'lumotidan oladi. @Get() dekoratorining yo'li metama'lumotda "/" bo'lib saqlanadi — shuning uchun yol === "/" tekshiruvi.
7. Loyiha qadami: marshrutlar testi va commit
7.1 Test
test/marshrutlar.test.ts — yangi fayl. U ilovani tayyor XotiraOmbori bilan ko'taradi (AppModule.forRoot ning test yo'li), DiscoveryModule ni qo'shadi va Nest'ning o'z metama'lumot kalitlari (PATH_METADATA, METHOD_METADATA) bilan marshrutlar jadvalini quradi:
// marshrutlar.test.ts — DiscoveryService + MetadataScanner +
// Reflector bilan ilovaning marshrut jadvali (Nest dekoratorlari
// yozgan metama'lumot) o'qiladi va har @All() (405) handler'ining
// Allow ro'yxati HAQIQIY marshrutlar bilan solishtiriladi. Yangi
// metod qo'shib Allow'ni yangilashni unutsangiz — shu test yiqiladi
// (Express'da .all(metodYoq("…")) satrlari qo'lda edi, tekshiruvsiz)
import { afterAll, beforeAll, expect, test } from "vitest";
import { Module, RequestMethod } from "@nestjs/common";
import {
METHOD_METADATA, PATH_METADATA,
} from "@nestjs/common/constants.js";
import {
DiscoveryModule, DiscoveryService, MetadataScanner, NestFactory,
Reflector,
} from "@nestjs/core";
import type { INestApplication } from "@nestjs/common";
import request from "supertest";
import type { App } from "supertest/types.js";
import { AppModule } from "../src/app.module.ts";
import {
ProblemDetailsFilter,
} from "../src/umumiy/problem.filter.ts";
import { XotiraOmbori } from "../src/vazifalar/xotira-ombor.ts";
import { logYarat } from "../src/umumiy/log.ts";
@Module({
imports: [
AppModule.forRoot({
ombor: new XotiraOmbori(),
log: logYarat("silent"),
sozlamalar: { tz: "Asia/Tashkent" },
}),
DiscoveryModule,
],
})
class SinovModuli {}
let app: INestApplication<App>;
beforeAll(async () => {
app = await NestFactory.create(SinovModuli, { logger: false });
app.useGlobalFilters(new ProblemDetailsFilter(() => undefined));
await app.init();
});
afterAll(() => app.close());
type Yol = { metodlar: Set<string>; allBor: boolean };
const TARTIB = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"];
function marshrutJadvali(): Map<string, Yol> {
const discovery = app.get(DiscoveryService);
const scanner = app.get(MetadataScanner);
const reflector = app.get(Reflector);
const jadval = new Map<string, Yol>();
for (const w of discovery.getControllers()) {
type Klass = new (...a: never[]) => object;
const klass = w.metatype as Klass | null;
if (klass === null || w.instance === undefined) continue;
const asos = String(
reflector.get<string | string[]>(PATH_METADATA, klass),
);
const proto = Object.getPrototypeOf(w.instance) as object;
for (const nom of scanner.getAllMethodNames(proto)) {
const handler =
(proto as Record<string, unknown>)[nom] as () => unknown;
const yol = reflector.get<string | undefined>(
PATH_METADATA, handler,
);
const metod = reflector.get<RequestMethod | undefined>(
METHOD_METADATA, handler,
);
if (yol === undefined || metod === undefined) continue;
const toliq = `/${asos}${yol === "/" ? "" : `/${yol}`}`;
const y = jadval.get(toliq) ??
{ metodlar: new Set<string>(), allBor: false };
if (metod === RequestMethod.ALL) y.allBor = true;
else {
y.metodlar.add(RequestMethod[metod]);
// Express GET marshruti HEAD'ga ham javob beradi
if (metod === RequestMethod.GET) y.metodlar.add("HEAD");
}
jadval.set(toliq, y);
}
}
return jadval;
}
const qator = (v: Yol) =>
TARTIB.filter((m) => v.metodlar.has(m)).join(", ") +
(v.allBor ? " | 405" : "");
test("DiscoveryService: marshrut jadvali (controller + handler " +
"metama'lumoti)", () => {
const jadval = Object.fromEntries(
[...marshrutJadvali()].map(([k, v]) => [k, qator(v)]),
);
expect(jadval).toEqual({
"/vazifalar": "GET, HEAD, POST, PUT | 405",
"/vazifalar/toplam": "POST | 405",
"/vazifalar/:id": "GET, HEAD, PUT, PATCH, DELETE | 405",
"/teglar": "GET, HEAD | 405",
"/eksportlar": "POST",
"/eksportlar/:id": "GET, HEAD",
"/eksportlar/:id/fayl": "GET, HEAD",
});
});
test("har @All() handler'ining Allow sarlavhasi haqiqiy metodlar " +
"bilan bir xil", async () => {
for (const [yol, y] of marshrutJadvali()) {
if (!y.allBor) continue;
const kutilgan = TARTIB
.filter((m) => y.metodlar.has(m)).join(", ");
const yoq = ["DELETE", "POST", "PATCH", "PUT"]
.find((m) => !y.metodlar.has(m)) ?? "OPTIONS";
// sinov ilovasida versiyalash yoqilmagan — yo'l prefikssiz
const r = await request(app.getHttpServer())[
yoq.toLowerCase() as "delete"
](yol.replace(":id", "1"));
expect([yol, r.status, r.headers["allow"]])
.toEqual([yol, 405, kutilgan]);
}
});Ikki test:
- Jadval — ilovadagi hamma marshrut va ularning metodlari. Kimdir yangi yo'l qo'shsa yoki olib tashlasa — test buni ko'rsatadi va jadvalni ataylab yangilashga majbur qiladi. Bu — API shartnomasining "surati" (snapshot).
Allowmosligi — har@All()yo'lga mavjud bo'lmagan metod bilan so'rov yuboriladi va405javobidagiAllowjadvaldagi haqiqiy metodlar bilan solishtiriladi.
Ikki kichik tafsilot. PATH_METADATA va METHOD_METADATA — Nest'ning ichki kalitlari (@nestjs/common/constants.js dan); ular ommaviy hujjatlashtirilmagan, versiya yangilanganda tekshirib turing. Sinov ilovasida enableVersioning yo'q — shuning uchun yo'llar /v1 siz (ilovaYarat emas, NestFactory.create to'g'ridan). qator — telefon uchun alohida yordamchi funksiyaga ajratdik; kanonda bu ifoda test ichida edi.
7.2 Test nimani ushlaydi — biz sinadik
teglar.controller.ts da Allow qiymatini ataylab "unutdik": "GET, HEAD" o'rniga "GET". Natija (biz ishga tushirdik):
FAIL test/marshrutlar.test.ts > har @All() handler'ining Allow sarlavhasi haqiqiy metodlar bilan bir xil
AssertionError: expected [ '/teglar', 405, 'GET' ] to deeply equal [ '/teglar', 405, 'GET, HEAD' ]
- Expected
+ Received
[
"/teglar",
405,
- "GET, HEAD",
+ "GET",
]Xabar aniq: qaysi yo'l (/teglar), qaysi status va farq qayerda. 25-qismdagi Express kodi bunday xatoni hech qachon ushlamasdi. Qiymatni qaytardik — test yana yashil.
Bunday "surat" testining narxi ham bor. Har yangi marshrut qo'shilganda birinchi test yiqiladi va kutilgan jadvalni qo'lda yangilash kerak bo'ladi. Bu — ataylab qilingan to'siq: API shartnomasi o'zgarayotganini dasturchi va review qiluvchi ko'rib turadi. Jadval yangilanishi commit'da alohida qator bo'lib ko'rinadi — "bu yangi yo'l rejalashtirilganmi?" degan savolni berishga sabab.
Ikkinchi test esa qo'lda yangilanmaydi: u haqiqiy marshrutlardan kutilgan Allow ni o'zi hisoblaydi. Shuning uchun u yangi @Patch() qo'shilganda ham ishlayveradi va faqat Allow satri eskirganda yiqiladi.
7.3 Tekshiruv
npm run check
npm run buildKanon tekshiruvi (Node 24.21.0, NestJS 12.2.0, PostgreSQL 18.6):
--- npm run tip
exit 0 (5864 ms)
--- npm run lint
exit 0 (10040 ms)
--- npm run test
↓ test/konteyner.test.ts (1 test | 1 skipped)
Test Files 22 passed | 1 skipped (23)
Tests 117 passed | 1 skipped (118)
exit 0 (9971 ms)
--- npm run build
exit 0 (6087 ms)Testlar soni oltitaga o'sdi (111 → 117): ombor-modul.test dan 4, marshrutlar.test dan 2. Testcontainers 10/10, jonli API 9 so'rov uch saqlash rejimida — o'zgarishsiz. Kanon yana bitta o'lchovni yozdi: yuklangan mashinada jarayon.test 17 s davom etdi (yolg'iz — 3 s) — bola jarayon Vite bilan ko'tariladi.
Biz ham kanon nusxasida yurgizdik: tip 0, lint 0, Vitest 77 o'tdi va 5 fayl SKIP (Redis va test bazalari yo'q; ombor-modul.test ning Postgres qismi o'z PostgreSQL 17 klasterimizda o'tdi), build 0. Jonli ishga tushirish SAQLASH=postgres bilan — ombor fabrikada yaratildi, GET /v1/teglar 200.
7.4 Commit
Bu holat oltita darsda yig'ildi: CONFIG tokeni (DI), ombor-yarat.ts va fabrika (custom provider'lar), OmborModule.forRoot/forRootAsync (dinamik modul), SaqlashYopuvchi va main.ts (lifecycle), marshrut testi (Discovery). Endi bitta commit:
git add -A
M="refactor: ombor DI ichida — OmborModule.forRootAsync"
M+=" (config bo'yicha, fail-fast, yopishda pool/redis),"
M+=" Discovery bilan 405 tekshiruvi"
git commit -m "$M"refactor — tashqi xulq (HTTP javoblari) o'zgarmadi, ichki tuzilma o'zgardi.
Commit'dan oldin git status bilan tarmoqni tekshiring: birinchi qatorda On branch nest bo'lishi kerak. Nest ko'chishi shu tarmoqda boradi; main esa 29-qismdagi ishlaydigan Express API bo'lib qoladi (Nest CLI va loyiha tuzilmasi darsidagi reja).
8. Real ishda
- Auth guard'lar —
Reflectorning eng ko'p ishlatiladigan joyi:@Public(),@Roles("admin")va guard ichidagetAllAndOverride(Global auth guard, @Public va @Roles). - Kutubxonalar —
@OnEvent,@Cron,@MessagePattern,@Process— hammasi "dekorator + Discovery" naqshi. Ichini bilsangiz, ularning xatolari tushunarli bo'ladi. - Arxitektura testlari — marshrut jadvali, "har controller'da auth bor", "har provider DEFAULT scope'da" kabi qoidalarni Discovery bilan test qilish. Masalan, "har yozuvchi marshrutda (
POST,PUT,PATCH)@JsonTana()belgisi bormi" — o'shamarshrutJadvalifunksiyasiga bittareflector.getqo'shilsa bo'ldi. - Plaginlar tizimi —
DiscoveryService.createDecoratorbilan belgilangan klasslar (to'lov usullari, xabar kanallari) ishga tushishda yig'iladi. Yangi kanal qo'shish — yangi klass va bitta dekorator; markaziy ro'yxatni tahrirlash shart emas. - Kod review'da yangi dekorator ko'rsangiz: "bu belgini kim o'qiydi va qaysi metod bilan — get, override yoki merge?". Hech kim o'qimaydigan belgi — o'lik kod.
- Intervyu: "
SetMetadatavaReflectorqanday ishlaydi?", "getAllAndOverridevagetAllAndMergefarqi?", "O'z dekoratoringiz bilan metodlarni qanday topasiz?".
Xulosa
- Dekorator metama'lumot yozadi: metod dekoratori — funksiyaga, klass dekoratori — klassga;
SetMetadata— satr kalit,Reflector.createDecorator<T>()— tipli. Reflector:get— bitta nishon,getAllAndOverride— birinchi topilgani (metod ustun),getAllAndMerge— hammasi birga.DiscoveryService+MetadataScanner— hamma provider/controller va ularning metodlari; o'z@OnXdekoratoringiz shu naqshda. Klass belgisi —DiscoveryService.createDecoratorvagetProviders({ metadataKey }).- REQUEST va TRANSIENT provider'ning
instancei — constructor'siz bo'sh qolip; Discovery'da ularni alohida hisobga oling. vazifalar-apida marshrutlar jadvali vaAllowsarlavhalari endi test bilan himoyalangan.- Holat commit qilindi (
nesttarmog'ida): ombor DI ichida, config bo'yicha, ilova bilan birga yopiladi; marshrutlar jadvali test bilan qotirildi.
Keyingi dars: DTO va class-validator — Nest'ning standart validatsiya usuli: DTO klasslari va dekoratorlar bilan kirishni tekshirish; keyin kanon nega Zod'da qolganini o'lchov bilan ko'ramiz.
Manbalar
- NestJS hujjati — Execution context, Reflection and metadata (
Reflector,createDecorator,getAllAndOverride): https://docs.nestjs.com/fundamentals/execution-context - NestJS hujjati — Discovery service: https://docs.nestjs.com/fundamentals/discovery-service
- Kanon tajribasi (IlmHamroh, 2026-10-27):
SetMetadatavacreateDecorator, uch o'qish metodi,@Hodisada+ Discovery, NestJS 12.2.0, Vitest muhitida; holat tekshiruvi (118 test). MiniReflector(ts) bloki — tsc 7.0.2 + reflect-metadata 0.2.2 da; Discovery qoliplari,DiscoveryService.createDecorator,DiscoveryModulesiz xato va ikkiSetMetadatasinovi;Allowsabotaj sinovi va bazasiz tekshiruv — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Node 24.21.0, PostgreSQL 17).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!