IlmHamroh
JavaScript Full-stack/30-qism. NestJS9/65-dars21 daqiqa
Mundarija (30)

NestJS custom provider'lar: useValue, useClass, useFactory, useExisting va async fabrika

Qisqacha: Provider'ni klass nomi bilan emas, obyekt bilan ham yozish mumkin: { provide: TOKEN, … }. To'rt shakli bor: useValue — tayyor qiymat; useClass — shu token uchun boshqa klass; useFactory — qiymatni funksiya qaytaradi, unga boshqa provider'lar inject orqali beriladi; useExisting — boshqa tokenga taxallus. Fabrika Promise qaytarsa, Nest uni kutadi — ilova faqat u tayyor bo'lgach ishga tushadi. Bu baza ulanishi kabi asinxron resurslar uchun asosiy usul.

Bu darsda

  • Provider'ning to'rt shaklini va har biri qaysi muammoni hal qilishini bilasiz.
  • useFactory ga inject bilan boshqa provider'larni berasiz.
  • Async fabrika ilova ishga tushishini qanday "ushlab turishini" haqiqiy o'lchov bilan ko'rasiz.
  • Provider turlarini 40 qatorlik JS konteynerida o'zingiz ishlatib ko'rasiz.
  • vazifalar-api omborini Nest ichida, config bo'yicha yaratadigan provider'larni yozasiz.

Oldin bilishingiz kerak: DI va IoC konteyner, Provider'lar va service'lar, Connection pooling, Env validatsiyasi va tipli config obyekti.

O'tgan darsda Nest bog'liqlikni token bo'yicha qidirishini ko'rdik va CONFIG tokenini qo'shdik. Bu darsda token ortida nima turishini belgilashning hamma usullarini o'rganamiz. Ulardan biri — fabrika — vazifalar-api ning omborini yaratadi.

1. Nega bu kerak?

Hozir vazifalar-api da ombor Nest'dan oldin yaratiladi:

ts
// main.ts
const saqlash = await omborYarat(config, log);
// … keyin Nest'ga tayyor obyekt sifatida:
{ provide: VAZIFA_OMBORI, useValue: ombor }

Bu ishlaydi, lekin ikki joyda hayot sikli boshqariladi. Ombor main.ts da tug'iladi va main.ts da yopiladi (saqlash.yop()). Nest esa uni faqat "qarzga" oladi. Natijada ikki muammo:

  1. Testlar ham, main.ts ham omborni o'zi yaratishi kerak — kod takrorlanadi.
  2. Nest ilovani yopayotganda pool'ni yopish uning ishi emas — main.ts buni unutsa, ulanishlar ochiq qoladi.

25–27-qismlarda bu muammo yo'q edi, chunki Express'da konteyner umuman yo'q edi. Ombor ham, uni yopish ham bitta faylda — ishga-tushir.ts da — turardi va hammasini siz boshqarardingiz. Nest'ga o'tgach, obyektlarni yaratish konteynerga o'tdi. Lekin ombor hali eski joyda qoldi — shuning uchun "ikki xo'jayin" paydo bo'ldi.

Yaxshiroq yo'l: Nest'ga "omborni qanday yaratishni" aytish, tayyor obyektni emas. Bunday retsept — fabrika provider. Oshxona o'xshatishi: oshpazga tayyor osh emas, retsept beriladi. U kerak paytda o'zi pishiradi va idishlarni o'zi yuvadi.

2. To'rt shakl

2.1 Umumiy ko'rinish

Oddiy yozuv providers: [VazifalarService] — aslida qisqartma. To'liq shakli:

ts
providers: [
  { provide: VazifalarService, useClass: VazifalarService },
]

provide — token (kalit), use… — qiymat qanday olinishi. To'rt variant:

Shakl Qiymat Tipik ishlatilishi
useValue tayyor obyekt, son, satr sozlama, tashqarida yaratilgan obyekt, testdagi soxta
useClass Nest yaratadigan klass muhitga qarab boshqa amalga oshirish
useFactory funksiya natijasi baza ulanishi, config'ga bog'liq obyekt
useExisting boshqa token ortidagi o'sha obyekt eski nomni saqlash, ikki interfeys — bitta obyekt

2.2 useValue

ts
{ provide: LOG, useValue: log },
{ provide: ILOVA_SOZLAMALARI, useValue: { tz: "Asia/Tashkent" } },

Nest qiymatni yaratmaydi va o'zgartirmaydi, faqat beradi. vazifalar-api dagi BogliqliklarModule hammasi shu shaklda. Testlarda eng ko'p ishlatiladigan shakl ham shu: { provide: VAZIFA_OMBORI, useValue: soxtaOmbor }.

Bitta tuzoq: useValue ichidagi obyekt @Injectable() klass nusxasi bo'lsa ham, Nest uning bog'liqliklarini to'ldirmaydi — u tayyor deb hisoblanadi. Bog'liqlikni siz o'zingiz new bilan berasiz. Natijada konteynerdagi singleton bilan sizning nusxangiz — ikki xil obyekt.

Demo loyihada shuni tekshirdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0). Hisobot klassi Soat ga tayanadi. Bir token ostida uni useClass bilan, ikkinchisida useValue: new Hisobot(new Soat()) bilan berdik. Keyin har biridagi soat konteynerdagi Soat bilan bir xilmi (===) — shuni chiqardik:

ts
{ provide: "HISOBOT_VALUE", useValue: new Hisobot(new Soat()) },
{ provide: "HISOBOT_CLASS", useClass: Hisobot },
text
useClass: Hisobot (09:00) true
useValue: Hisobot (09:00) false

Nima bo'ldi? Ikkalasi ham ishladi — sarlavha bir xil. Farq oxirgi so'zda. Birinchi qatorda Nest Soat ni o'zi topdi va o'sha singleton'ni berdi (true). useValue da esa Soat ni biz yaratdik — bu konteynerdagidan boshqa, ikkinchi nusxa (false). Soat holat saqlasa (kesh, hisoblagich, pool), ikki nusxa bir-birini ko'rmaydi. Xato chiqmaydi, faqat ma'lumot "ikkiga bo'linadi" — bunday xatoni topish qiyin.

2.3 useClass

ts
{
  provide: VAZIFA_OMBORI,
  useClass: process.env["SAQLASH"] === "xotira"
    ? XotiraOmbori
    : PgOmbori,
}

Token bitta, klass — tanlov bo'yicha. 25-qismda xuddi shu tanlov omborYarat ichida oddiy if bilan yozilgan edi: shartga qarab new XotiraOmbori() yoki new PgOmbori(pool). Bu yerda farq bitta: new ni siz emas, Nest qiladi. Nest tanlangan klassni o'zi yaratadi va uning constructor bog'liqliklarini to'ldiradi. Muhim farq useValue dan: bu yerda klassning o'z DI zanjiri ishlaydi.

Qachon kerak? Bitta interfeysning bir nechta amalga oshirilishi bo'lganda: xotira va Postgres ombori, haqiqiy va soxta SMS jo'natuvchi. Lekin tanlov process.env ga bevosita qarasa — config tekshiruvi chetlab o'tiladi. Shuning uchun vazifalar-api tanlovni fabrika ichida, tekshirilgan config bo'yicha qiladi.

2.4 useFactory + inject

ts
{
  provide: VAZIFA_OMBORI,
  useFactory: (config: Config, log: Log) => yarat(config, log),
  inject: [CONFIG, LOG],
}

useFactory — oddiy funksiya, uning qaytargani token qiymati bo'ladi. inject — funksiya argumentlari uchun tokenlar ro'yxati, tartib bilan: birinchi token — birinchi argument. Bu @Inject(...) ning fabrika uchun shakli.

Fabrika ham singleton: Nest uni bir marta chaqiradi va natijani saqlaydi. Kanon tajribasida fabrika ikki marta so'ralganda ham bir marta chaqirildi (pastdagi chiqishda 1).

inject tartibi adashsa nima bo'ladi? Demo loyihada inject: [LOG, CONFIG] yozdik, fabrika esa (config, log) kutadi:

ts
{
  provide: "SOZLAMA",
  useFactory: (config: { tz: string }, log: unknown) =>
    `tz=${config.tz}, log=${typeof log}`,
  inject: [LOG, CONFIG],
}
text
inject almashgan: tz=undefined, log=object

Nest hech narsa demadi. config o'rniga log obyekti keldi, unda tz yo'q — undefined. TypeScript ham ushlamadi: inject — oddiy tokenlar massivi, u fabrika parametrlarining turini bilmaydi. Shuning uchun inject ni fabrika parametrlari bilan bir qatorda, bir tartibda yozing va fabrikani kichik tuting.

Tekshirib ko'ring: useValue: new Hisobot(new Soat()) dagi "ikki nusxa" muammosini fabrika bilan qanday yechasiz?

Javob

{ provide: "HISOBOT", useFactory: (s: Soat) => new Hisobot(s), inject: [Soat] }. Endi Soat ni biz emas, Nest beradi — konteynerdagi o'sha singleton. new baribir sizning kodingizda, lekin bog'liqlik inject orqali keladi. Eng sodda yo'l esa useClass: Hisobot (yoki shunchaki providers: [Hisobot]): klassning bog'liqliklarini Nest o'zi to'ldiradi. Fabrika faqat yaratishda qo'shimcha kod kerak bo'lsa foydali.

inject dagi tokenni ixtiyoriy qilish mumkin: inject: [{ token: XABARCHI, optional: true }] — topilmasa undefined. Bu @Optional() ning fabrika uchun shakli.

2.5 useExisting

ts
{ provide: "OMBOR_ALIAS", useExisting: OMBOR }

Taxallus (alias) — yangi obyekt yaratilmaydi, ikki token bitta nusxaga ishora qiladi. Masalan, eski kod "ombor" satr tokenini so'raydi, yangi kod esa VAZIFA_OMBORI ni — ikkalasi bitta omborni olsin. useValue: ombor dan farqi: useExisting haqiqiy provider'ga ishora qiladi va uning hayot siklini (yaratilish vaqti, scope) meros oladi.

Tekshirib ko'ring: { provide: A, useClass: Soat } va { provide: B, useClass: Soat } — A va B bitta nusxami? { provide: B, useExisting: A } bo'lsa-chi?

Javob

Birinchi holatda — ikki nusxa: har useClass yozuvi o'z provider'i, Nest har biriga alohida new Soat() qiladi. Modullar darsidagi "ikki joyda providers" tuzog'ining kichik shakli. useExisting bilan esa bitta nusxa — B shunchaki A ning ikkinchi nomi.

3. Kanon tajribasi: hamma shakl bir joyda

Kanon beshta tokenni bir modulda ro'yxatdan o'tkazdi (kanon tajribasi, Vitest muhitida). Kod qisqartirilgan:

ts
@Module({
  providers: [
    Soat,
    { provide: OMBOR, useValue: { ol: () => "useValue" } },
    {
      provide: "OMBOR_KLASS",
      useClass: class implements Ombor {
        ol() { return "useClass"; }
      },
    },
    {
      provide: "OMBOR_FABRIKA",
      useFactory: (s: Soat) => {
        fabrikaChaqiruv++;
        return { ol: () => `useFactory (${s.hozir()})` };
      },
      inject: [Soat],
    },
    {
      provide: "OMBOR_ASYNC",
      useFactory: async () => {
        await new Promise((r) => setTimeout(r, 50));
        return { ol: () => "async useFactory (50 ms kutildi)" };
      },
    },
    { provide: "OMBOR_ALIAS", useExisting: OMBOR },
    TokenliXizmat,
  ],
})
class Turlar {}

Chiqishda uch narsaga qarang: birinchi qatordagi vaqt (ms), har token yonidagi qiymat va oxirgi ikki qatordagi true va 1:

text
5) custom provider'lar (kontekst 56 ms — async fabrika kutildi):
   Symbol(OMBOR) → useValue
   OMBOR_KLASS → useClass
   OMBOR_FABRIKA → useFactory (2026-10-27T00:00:00.000Z)
   OMBOR_ASYNC → async useFactory (50 ms kutildi)
   OMBOR_ALIAS → useValue
   useExisting bir xil obyekt: true; fabrika necha marta chaqirildi (singleton): 1
   @Optional() @Inject("YOQ") → undefined

Chiqishni o'qiymiz:

  • 56 ms. Kontekst yaratish (createApplicationContext) 56 ms oldi — shundan 50 ms async fabrikaning setTimeout i. Nest Promise'ni kutdi: ilova u tayyor bo'lgunicha ko'tarilmadi. Bu — async provider'ning asosiy xususiyati.
  • OMBOR_ALIAS → useValue — taxallus OMBOR ning o'zini berdi. true — bir xil obyekt (===).
  • 1 — fabrika ikki marta so'ralgan, lekin bir marta chaqirilgan: singleton.
  • undefined — @Optional() li token ro'yxatda yo'q, xato o'rniga undefined.
  • createApplicationContext — HTTP serversiz Nest ilovasi: faqat DI konteyner. Test va skriptlar uchun qulay; batafsil — Standalone ilova darsida.
flowchart LR
  T1["CONFIG"] -->|useValue| V["tayyor obyekt"]
  T2["SAQLASH"] -->|"useFactory (async)"| F["omborYarat(config, log)"]
  F -. "inject" .-> T1
  T3["VAZIFA_OMBORI"] -->|useFactory| G["saqlash.ombor"]
  G -. "inject" .-> T2
  T4["OMBOR_ALIAS"] -->|useExisting| T3

Diagramma loyiha qadamidagi zanjirni ko'rsatadi: punktir — inject (fabrika nimani so'raydi), yorliqli strelka — provider shakli.

4. JS modelida provider turlari

To'rt shaklni kichik konteynerda takrorlaymiz — brauzerda ishlaydi. ol(token) provider shakliga qarab qiymatni oladi; async fabrikani kutadi; natijani saqlaydi (singleton). Nest kabi, hamma provider ishga tushishda yaratiladi:

js
const kut = (ms) => new Promise((r) => setTimeout(r, ms));

async function konteynerYarat(providerlar) {
  const tavsif = new Map(providerlar.map((p) => [p.provide, p]));
  const tayyor = new Map();
  async function ol(token) {
    if (tayyor.has(token)) return tayyor.get(token);
    const p = tavsif.get(token);
    if (!p) throw new Error(`${String(token)} topilmadi`);
    const bogliq = (r = []) => Promise.all(r.map(ol));
    let q;
    if ("useValue" in p) {
      q = p.useValue;
    } else if ("useClass" in p) {
      q = new p.useClass(...await bogliq(p.kerak));
    } else if ("useFactory" in p) {
      q = await p.useFactory(...await bogliq(p.inject));
    } else {
      q = await ol(p.useExisting);
    }
    tayyor.set(token, q);
    return q;
  }
  // Nest kabi: hammasi ishga tushishda yaratiladi
  for (const t of tavsif.keys()) await ol(t);
  return (t) => tayyor.get(t);
}

class XotiraOmbori { tur = "xotira"; }
class PgOmbori { tur = "postgres"; }
let fabrika = 0;

const CONFIG = Symbol("CONFIG");
const SAQLASH = Symbol("SAQLASH");
const OMBOR = Symbol("OMBOR");
const ol = await konteynerYarat([
  { provide: CONFIG, useValue: { SAQLASH: "xotira" } },
  {
    provide: SAQLASH,
    inject: [CONFIG],
    useFactory: async (config) => {
      fabrika++;
      await kut(50); // baza ulanishi
      const K = config.SAQLASH === "xotira" ? XotiraOmbori : PgOmbori;
      return { ombor: new K() };
    },
  },
  { provide: OMBOR, inject: [SAQLASH], useFactory: (s) => s.ombor },
  { provide: "OMBOR_TAXALLUS", useExisting: OMBOR },
]);
console.log(ol(OMBOR).tur);
console.log(ol("OMBOR_TAXALLUS") === ol(OMBOR));
console.log(`fabrika: ${fabrika} marta`);

Konsolda:

text
xotira
true
fabrika: 1 marta

SAQLASH fabrikasi ikki joydan so'ralgan (to'g'ridan va OMBOR orqali), lekin bir marta ishladi. konteynerYarat qaytgan paytda hamma qiymat tayyor — await kut(50) tugagan. Model soddalashtirilgan: unda modul chegarasi va aylanma bog'liqlik tekshiruvi yo'q, useClass bog'liqliklari esa metama'lumotdan emas, kerak maydonidan olinadi.

5. Async fabrika: ilova kutadi

5.1 Nima uchun async

Baza ulanishi asinxron: pool yaratiladi, birinchi so'rov yuboriladi, javob kutiladi. omborYarat ham async — 27-qismda u EGASI_ID ni bazadan tekshiradi va migratsiya holatini o'qiydi (fail-fast).

Async fabrika provider bilan bu tekshiruvlar Nest bootstrap ichida bo'ladi. Fabrika rad etilgan Promise qaytarsa (baza yo'q, EGASI_ID noto'g'ri), NestFactory.create ham rad etiladi — ilova ishga tushmaydi. 27-qismdagi xulq saqlanadi, faqat joyi o'zgaradi.

5.2 Biz o'lchadik

Loyiha qadamidagi provider'larni (pastda) alohida sinov ilovasida ishlatdik: CONFIG va LOG ni beradigan kichik global modul + yangi OmborModule (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0, PostgreSQL 17 — o'z vaqtinchalik klasterimiz, kanon sxemasi bilan). Uch xil config bilan uch marta ishga tushirdik. Har qatorda qaysi ombor kelganiga va qavs ichidagi vaqtga qarang:

text
xotira: XotiraOmbori (27 ms)
postgres: PgOmbori (132 ms)
postgres EGASI_ID=999: XATO — EGASI_ID=999 foydalanuvchilar jadvalida yo'q
  • SAQLASH=xotira — fabrika XotiraOmbori berdi, kontekst 27 ms da tayyor.
  • SAQLASH=postgres — PgOmbori, 132 ms: pool ochildi, EGASI_ID tekshirildi, migratsiya holati o'qildi. Nest shuni kutdi.
  • EGASI_ID=999 — fabrika rad etildi, createApplicationContext ham rad etildi. Xabar — 27-qismdagi omborYarat ning o'zi.

Vaqtlar mashinaga bog'liq — sizda boshqa raqam chiqadi. Muhimi nisbat: async fabrika ilova ishga tushishini resurs tayyor bo'lguncha ushlab turadi.

Diqqat: Async fabrika ichidagi uzoq ish ilovani ham ushlab turadi. Baza 30 s javob bermasa, ilova 30 s ko'tarilmaydi va hech narsa yozmaydi. omborYarat dagi connectionTimeoutMillis: 5_000 (Connection pooling) shuning uchun muhim: baza javob bermasa, 5 s da aniq xato.

Tekshirib ko'ring: Nega VAZIFA_OMBORI alohida fabrika ((s) => s.ombor)? SAQLASH ning o'zini VAZIFA_OMBORI qilsa bo'lmaydimi?

Javob

omborYarat { ombor, yop } qaytaradi — ombor va uni yopish funksiyasi. Xizmatlarga faqat ombor kerak (VazifaOmbori interfeysi), yop esa ilovani yopayotgan kodga. Ikki token ikki iste'molchi uchun: VAZIFA_OMBORI — xizmatlarga, SAQLASH — yopuvchiga (keyinroq Lifecycle hook'lar darsida). Shunda xizmat tasodifan yop() ni chaqira olmaydi — u bunday metodni ko'rmaydi.

6. Qaysi shaklni tanlash

6.1 Uch savol

Yangi provider yozayotganda ketma-ket uch savol bering:

  1. Qiymat Nest'dan oldin tayyormi? (sozlama obyekti, test soxtasi, tashqarida yaratilgan logger) — useValue. Nest uni yaratmaydi va yopmaydi; hayot sikli sizda.
  2. Qiymat klassmi va uning bog'liqliklarini Nest to'ldirsinmi? — useClass (yoki oddiy providers: [Klass]). Tanlov kerak bo'lsa (ikki amalga oshirish), tanlovni config bo'yicha qiling.
  3. Qiymatni yaratish uchun kod kerakmi (ulanish ochish, await, boshqa provider'dan hisoblash)? — useFactory + inject.

useExisting — alohida holat: yangi qiymat emas, mavjudiga ikkinchi nom.

Uch savolni bitta misolda sinaymiz. 29-qismdagi SMS jo'natuvchi Nest'ga ko'chadi. SMS_PROVAYDER=eskiz bo'lsa haqiqiy provayder kerak, u esa ishga tushishda token oladi (await). Testda esa yuborilgan xabarlarni massivga yozadigan soxta obyekt yetadi. Bo'shliqlarni to'ldiring:

  • Prodakshndagi provayder: avval token so'raladi, keyin obyekt beriladi —
  • Unga config kerak — tokenlar ro'yxati maydoni —
  • Testdagi soxta obyekt —

Birinchi javob — useFactory: "yaratish uchun kod va await kerakmi?" degan savolga "ha". Ikkinchisi — inject: [CONFIG]. Uchinchisi — useValue: soxta obyekt Nest'dan oldin tayyor, unga bog'liqlik kerak emas.

6.2 Testda almashtirish

Custom provider'larning eng ko'p foydasi — testda. Haqiqiy xizmat o'rniga soxtasini o'sha token bilan beramiz:

ts
const modul = await Test.createTestingModule({
  controllers: [VazifalarController],
  providers: [
    { provide: VazifalarService, useValue: soxtaXizmat },
    { provide: ILOVA_SOZLAMALARI, useValue: { tz: "UTC" } },
  ],
}).compile();

Controller hech narsani sezmaydi: u VazifalarService tokenini so'raydi va javobda soxtaXizmat ni oladi. Klass tokeni bilan useValue — "shu klass o'rniga mana bu obyekt". To'liq test naqshlari — NestJS unit testlari darsida.

6.3 Provider'lar qayerda yashaydi

Kichik modulda provider'lar to'g'ridan @Module({ providers: [...] }) ichida. Ro'yxat uzunlashsa, katta loyihalarda alohida fayl ochiladi:

ts
// ombor.providers.ts
export const omborProviderlari: Provider[] = [
  { provide: SAQLASH, useFactory: …, inject: [CONFIG, LOG] },
  { provide: VAZIFA_OMBORI, useFactory: …, inject: [SAQLASH] },
];

Modul esa providers: [...omborProviderlari] bilan oladi. Bu faqat tartib uchun — Nest uchun farqi yo'q. vazifalar-api da provider'lar kam, shuning uchun ular modul faylida.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Fabrika argumentlari undefined inject unutilgan yoki tartibi xato inject ni argumentlar tartibida yozing
useValue dagi xizmatning bog'liqliklari undefined yoki "boshqa nusxa" Nest useValue ni to'ldirmaydi useClass yoki useFactory + inject
Fabrika argumenti boshqa obyekt (tz=undefined) inject tartibi parametrlar tartibiga mos emas inject ni parametrlar bilan yonma-yon yozing
Ikki useClass — ikki nusxa har yozuv alohida provider useExisting taxallus
Ilova uzoq ko'tarilmaydi, log yo'q async fabrika osilib qoldi resurs timeout'i (connectionTimeoutMillis)
Fabrika har so'rovda chaqiriladi deb kutish fabrika singleton — bir marta har so'rov uchun — scope (Provider scope'lari)

8. Mashqlar

Mashqlar kurs/mashqlar/30/09-custom-provider/ da.

1-mashq (oson): qaysi shakl?

  • Testda soxta ombor berish —
  • SMS_PROVAYDER=eskiz bo'lsa EskizSms, aks holda SoxtaSms klassi —
  • Redis ulanishini ochib, keyin berish —
  • Eski "ombor" tokenini yangi VAZIFA_OMBORI ga bog'lash —
  • useFactory argumentlari qaysi maydonda? —
Yechim
  • useValue — tayyor soxta obyekt.
  • useClass — tanlov bo'yicha klass; Nest uni o'zi yaratadi.
  • useFactory — async: await redis.connect().
  • useExisting — taxallus, bitta nusxa.
  • inject — tokenlar ro'yxati, argumentlar tartibida.

2-mashq (o'rta): muhitga qarab provider

omborProvideri(saqlash) funksiyasini yozing: "xotira" bo'lsa { provide: OMBOR, useClass: XotiraOmbori }, "postgres" bo'lsa { provide: OMBOR, useClass: PgOmbori, kerak: [POOL] }, boshqa qiymatda Error("noma'lum saqlash: …"). Darsdagi konteynerYarat bilan ikkala holatni sinang (POOL — useValue: { nomi: "pool" }), oxirida noma'lum qiymatni. Ishora: switch (switch) yoki obyekt-jadval.

Yechim
js
async function konteynerYarat(providerlar) {
  const tavsif = new Map(providerlar.map((p) => [p.provide, p]));
  const tayyor = new Map();
  async function ol(token) {
    if (tayyor.has(token)) return tayyor.get(token);
    const p = tavsif.get(token);
    if (!p) throw new Error(`${String(token)} topilmadi`);
    const args = await Promise.all((p.kerak ?? []).map(ol));
    const q = "useValue" in p ? p.useValue : new p.useClass(...args);
    tayyor.set(token, q);
    return q;
  }
  for (const t of tavsif.keys()) await ol(t);
  return (t) => tayyor.get(t);
}

class XotiraOmbori { tur = "xotira"; }
class PgOmbori {
  constructor(pool) { this.tur = `postgres (${pool.nomi})`; }
}
const OMBOR = Symbol("OMBOR");
const POOL = Symbol("POOL");

function omborProvideri(saqlash) {
  switch (saqlash) {
    case "xotira":
      return { provide: OMBOR, useClass: XotiraOmbori };
    case "postgres":
      return { provide: OMBOR, useClass: PgOmbori, kerak: [POOL] };
    default:
      throw new Error(`noma'lum saqlash: ${saqlash}`);
  }
}

for (const s of ["xotira", "postgres", "mongo"]) {
  try {
    const ol = await konteynerYarat([
      { provide: POOL, useValue: { nomi: "pool" } },
      omborProvideri(s),
    ]);
    console.log(s, "→", ol(OMBOR).tur);
  } catch (e) {
    console.log(s, "→", e.message);
  }
}

Konsolda:

text
xotira → xotira
postgres → postgres (pool)
mongo → noma'lum saqlash: mongo

Noma'lum qiymat konteyner yaratilishidan oldin xato berdi — fail-fast. Haqiqiy vazifalar-api da bu tekshiruv bundan ham oldin: configYukla SAQLASH ni Zod enum bilan tekshiradi, noto'g'ri qiymatda ilova 78 kodi bilan to'xtaydi.

3-mashq (qiyin): ixtiyoriy inject

Konteynerga inject ichida obyekt yozishni qo'shing: { token, optional: true }. Bunday token topilmasa — undefined berilsin, oddiy token topilmasa — eski xato. Sinov: BILDIRISHNOMA fabrikasi [LOG, { token: XABARCHI, optional: true }] oladi va xabarchi ? "webhook bilan" : "webhooksiz" qaytaradi. Avval XABARCHI siz, keyin u bilan. Ishora: typeof k === "object" — obyekt shakli; tavsif.has(k.token).

Yechim
js
async function konteynerYarat(providerlar) {
  const tavsif = new Map(providerlar.map((p) => [p.provide, p]));
  const tayyor = new Map();
  async function ol(token) {
    if (tayyor.has(token)) return tayyor.get(token);
    const p = tavsif.get(token);
    if (!p) throw new Error(`${String(token)} topilmadi`);
    const args = await Promise.all((p.inject ?? []).map((k) => {
      if (typeof k === "object" && k.optional) {
        return tavsif.has(k.token) ? ol(k.token) : undefined;
      }
      return ol(k);
    }));
    const q = "useValue" in p
      ? p.useValue
      : await p.useFactory(...args);
    tayyor.set(token, q);
    return q;
  }
  for (const t of tavsif.keys()) await ol(t);
  return (t) => tayyor.get(t);
}

const LOG = Symbol("LOG");
const XABARCHI = Symbol("XABARCHI");
const BILDIRISHNOMA = Symbol("BILDIRISHNOMA");
const bildirishnoma = {
  provide: BILDIRISHNOMA,
  inject: [LOG, { token: XABARCHI, optional: true }],
  useFactory: (log, xabarchi) =>
    `${log.nomi}, ${xabarchi ? "webhook bilan" : "webhooksiz"}`,
};

const log = { provide: LOG, useValue: { nomi: "pino" } };
const ol1 = await konteynerYarat([log, bildirishnoma]);
console.log(ol1(BILDIRISHNOMA));
const ol2 = await konteynerYarat([
  log, bildirishnoma, { provide: XABARCHI, useValue: () => {} },
]);
console.log(ol2(BILDIRISHNOMA));

Konsolda:

text
pino, webhooksiz
pino, webhook bilan

Nest'da xuddi shu yozuv ishlaydi: inject: [LOG, { token: XABARCHI, optional: true }]. vazifalar-api boshqa yo'lni tanladi — XABARCHI har doim ro'yxatda, qiymati esa null bo'lishi mumkin (Provider'lar darsi). Ikkalasi to'g'ri; muhimi — loyiha ichida bitta usul.

9. Loyiha qadami: ombor fabrikasi

9.1 Reja

Bu dars ikki ish qiladi:

  1. omborYarat alohida faylga ko'chadi — ombor-yarat.ts.
  2. Yangi ombor.module.ts — config bo'yicha omborni yaratadigan provider'lar.

Yangi modul hali ulanmaydi: ombor main.ts da yaratilib, BogliqliklarModule orqali berilishda davom etadi. Ikkita bir xil token (VAZIFA_OMBORI) daraxtda bo'lsa, ulardan qaysi biri ishlashi chalkash bo'ladi. Almashtirish — Dynamic modullar darsida, bitta qadamda. Bu darsda faqat "retsept" yoziladi va alohida sinaladi; ilovaning xulqi esa o'zgarmaydi. Kichik qadam: biror narsa buzilsa, aybdorni qidirish oson.

9.2 ombor-yarat.ts

27-qismdagi ishga-tushir.ts ning yarmi — OmborLogi turi, migratsiyaniTekshir, Saqlash turi, omborYarat va asosiyOmbor — yangi faylga o'zgarishsiz ko'chadi. Faylning boshi:

ts
// ombor-yarat.ts — config bo'yicha ombor (xotira, fayl, pg, prisma,
// + Redis kesh) va uni yopish. 27-qism ishga-tushir.ts dan ajratildi:
// Nest OmborModule.forRootAsync() fabrikasi shu funksiyani chaqiradi
// (ilova.ts ↔ modul aylanma importi bo'lmasin)
import type { Config } from "./config.ts";
import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";
// … qolgan importlar: fayl, pg, prisma, redis, kesh omborlari

Nega alohida fayl? Fabrika omborYarat ni chaqiradi. Agar uni eski joyidan — ishga-tushir.ts dan — import qilsak, zanjir yopiladi: ilova.ts → app.module.ts → ombor.module.ts → ishga-tushir.ts → ilova.ts. Bu — fayllar orasidagi aylanma import. ESM'da u ba'zan ishlaydi, ba'zan build'dan keyin yiqiladi; kanon buni o'lchagan, natijasi Circular dependency darsida. Eng yaxshi yechim — aylanmani umuman qurmaslik.

ishga-tushir.ts da eski joy saqlanadi — 27-qism testlari (pg-ombor.test, prisma-ombor.test) omborYarat ni shu yerdan import qiladi:

ts
// ishga-tushir.ts — Nest ilovasi, http server timeout'lari va
// tinglash
import type { Server } from "node:http";
import type { AddressInfo } from "node:net";
import type {
  NestExpressApplication,
} from "@nestjs/platform-express";
import { ilovaYarat } from "./ilova.ts";
import type { Bogliqliklar } from "./ilova.ts";
import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";

// 27 testlari (pg-ombor, prisma-ombor) omborYarat'ni shu fayldan
// import qiladi — eski yo'l saqlanadi
export { omborYarat } from "./ombor-yarat.ts";
export type { OmborLogi, Saqlash } from "./ombor-yarat.ts";

Faylning qolgan qismi (Ishlayotgan turi va ishgaTushir) o'zgarmaydi. export { … } from — qayta eksport: fayl o'zi ishlatmaydi, faqat uzatadi (ESM import). Pg, Prisma, Redis importlari bu fayldan ketdi — ular endi ombor-yarat.ts da.

9.3 ombor.module.ts

ts
// ombor.module.ts — VAZIFA_OMBORI ni config bo'yicha yaratadigan
// provider'lar: ASYNC fabrika — Nest bootstrap paytida Promise'ni
// kutadi (baza ulanishi, migratsiya tekshiruvi — fail-fast).
// Hozircha AppModule'ga ulanmagan
import { Global, Module } from "@nestjs/common";
import { omborYarat } from "./ombor-yarat.ts";
import type { Saqlash } from "./ombor-yarat.ts";
import type { Config } from "./config.ts";
import type { Log } from "./umumiy/log.ts";
import { CONFIG, LOG, VAZIFA_OMBORI } from "./tokenlar.ts";

const SAQLASH = Symbol("SAQLASH");

@Global()
@Module({
  providers: [
    // async fabrika: Nest Promise'ni kutadi; xato bo'lsa — ilova
    // ishga TUSHMAYDI (EGASI_ID yo'q, baza yo'q, migratsiya orqada)
    {
      provide: SAQLASH,
      useFactory: (config: Config, log: Log): Promise<Saqlash> =>
        omborYarat(config, log),
      inject: [CONFIG, LOG],
    },
    {
      provide: VAZIFA_OMBORI,
      useFactory: (s: Saqlash) => s.ombor,
      inject: [SAQLASH],
    },
  ],
  exports: [VAZIFA_OMBORI],
})
export class OmborModule {}

Har qatorni o'qing:

  • SAQLASH — eksport qilinmagan token, faylning ichida. Tashqaridan uni hech kim so'ray olmaydi — faqat shu modul.
  • Birinchi fabrika: inject: [CONFIG, LOG] — o'tgan darsda qo'shgan CONFIG tokeni shu yerda ishga tushdi. Fabrika Promise<Saqlash> qaytaradi — Nest kutadi.
  • Ikkinchi fabrika: inject: [SAQLASH] — birinchisining natijasidan faqat omborni oladi.
  • exports: [VAZIFA_OMBORI] — tashqariga faqat ombor. @Global() — xizmatlar uni import qilmasdan oladi, hozir BogliqliklarModule dan olgandek.

Bu provider'lar «Biz o'lchadik» bo'limidagi sinovda ishlatilgan — XotiraOmbori, PgOmbori va EGASI_ID xatosi shu moduldan.

9.4 Tekshiruv

bash
npm run tip
npm run lint
npm test

Biz kanon nusxasida yurgizdik: tip 0, lint 0, Vitest — o'tgan darsdagidek (71 o'tdi, bazasiz 5 fayl SKIP). Modul ulanmagani uchun ilova xulqi o'zgarmadi. Hali commit qilmang — holat commit'i Reflector va DiscoveryService darsida.

10. Real ishda

  • TypeOrmModule.forRootAsync({ useFactory, inject: [ConfigService] }) — Nest ekotizimidagi deyarli har modul (JwtModule, BullModule, CacheModule) shu naqsh: async fabrika + config'dan olish.
  • Testlarda Test.createTestingModule(...).overrideProvider(VAZIFA_OMBORI).useValue(soxta) — useValue ning eng ko'p uchraydigan joyi (NestJS unit testlari).
  • Prodakshnda async fabrika "ilova tayyorlik" (readiness) bilan bog'lanadi: ilova faqat baza ulangach portni ochadi, orkestrator esa shundan keyin trafik yuboradi (Health check'lar).
  • Kod review'da useValue: new Xizmat(new Bogliqlik()) ko'rinsa, birinchi savol: "bu bog'liqlik konteynerda ham bormi?". Bor bo'lsa — ikki nusxa yashayapti, useClass yoki inject li fabrika taklif qilinadi. Uzun inject ro'yxati ham ogohlantiruvchi belgi: fabrika juda ko'p ish qilyapti.
  • Intervyu: "Custom provider turlarini ayting", "useClass va useExisting farqi?", "Async provider nima va ilova uni qanday kutadi?".

Xulosa

  • Provider obyekt shaklida: { provide: TOKEN, useValue | useClass | useFactory | useExisting }.
  • useValue — tayyor qiymat (Nest to'ldirmaydi), useClass — tanlangan klass (Nest yaratadi va to'ldiradi).
  • useFactory + inject — funksiya natijasi; inject tartibi — argumentlar tartibi; { token, optional: true } mumkin.
  • useExisting — taxallus, bitta nusxa; ikki useClass esa ikki nusxa.
  • useValue ga new bilan yaratilgan xizmat konteynerdagi singleton'ni ko'rmaydi; inject tartibini esa na Nest, na TypeScript tekshiradi.
  • Async fabrika — Nest Promise'ni kutadi; rad etilsa, ilova ishga tushmaydi (fail-fast).
  • vazifalar-api da ombor fabrikasi yozildi (OmborModule), omborYarat alohida faylga ko'chdi — aylanma importsiz.

Keyingi dars: Provider scope'lari — singleton'dan tashqari REQUEST va TRANSIENT, ularning "yuqishi" va narxi kanon o'lchovi bilan.

Manbalar

  • NestJS hujjati — Custom providers (useValue, useClass, useFactory, useExisting, optional inject): https://docs.nestjs.com/fundamentals/custom-providers
  • NestJS hujjati — Asynchronous providers: https://docs.nestjs.com/fundamentals/async-providers
  • Kanon tajribasi (IlmHamroh, 2026-10-27): beshta provider shakli bir kontekstda va async fabrika kutilishi. NestJS 12.2.0, Vitest muhitida. OmborModule provider'lari sinovi (xotira, postgres, EGASI_ID xatosi) — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Node 24.21.0, PostgreSQL 17).
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
NestJS custom provider'lar: useValue, useClass, useFactory, useExisting va async fabrika — IlmHamroh