IlmHamroh
JavaScript Full-stack/30-qism. NestJS34/65-dars16 daqiqa
Mundarija (27)

Drizzle va Kysely bilan NestJS: custom provider, Symbol token va pool yopish

Qisqacha: Drizzle va Kysely uchun rasmiy Nest moduli yo'q — va kerak ham emas. Ulanish fabrika provider bilan yasaladi: { provide: DRIZZLE, useFactory: (pool) => drizzle(pool), inject: [PG_POOL] }. Token — Symbol, xizmat uni @Inject(DRIZZLE) db: NodePgDatabase bilan oladi. Ulanishlar hovuzini (pool) yopish — sizning ishingiz: onModuleDestroy da pool.end() va kysely.destroy(). Kanon tajribasida bir xil so'rov Drizzle'da 2.36 ms, Kysely'da 2.49 ms (TypeORM 2.18 ms) — farq ahamiyatsiz. vazifalar-api ning o'zi ham aynan shu naqsh bilan qurilgan.

Bu darsda

  • Rasmiy moduli yo'q kutubxonani useFactory + Symbol token bilan Nest'ga ulaysiz.
  • inject bilan provider'lar zanjirini (pool → Drizzle) tuzasiz.
  • Tokenning turi TypeScript tomonidan tekshirilmasligini va buni qanday yopishni bilasiz.
  • Pool'ni onModuleDestroy da to'g'ri yopasiz va "ortiqcha pool" tuzog'ini ko'rasiz.
  • vazifalar-api dagi OmborModule aynan shu naqsh ekanini tanib olasiz.

Oldin bilishingiz kerak: Drizzle ORM 1: sxema va so'rovlar, Kysely va Knex query builder'lari, Custom provider'lar, Lifecycle hook'lar, TypeORM bilan NestJS.

Uch darsda uch kutubxonani ko'rdik. Prisma uchun PrismaService ni o'zimiz yozdik, TypeORM va Mongoose uchun rasmiy modul bor edi. Endi to'rtinchi holat: modul yo'q. Bu dars eng muhimi — chunki u Nest'ning o'zi qanday ishlashini ochib beradi.

1. Nega bu kerak?

27-qismda Drizzle va Kysely'ni ko'rgandik (Drizzle, Kysely). Ikkalasi ham yengil: dekorator yo'q, klassdan meros yo'q, pg.Pool ustidagi tipli so'rov quruvchi. Ular uchun @nestjs/drizzle yoki @nestjs/kysely degan rasmiy paket yo'q.

Paket yo'qligi — muammo emas. TypeOrmModule.forRootAsync ichida aslida nima bor? Fabrika provider, token va lifecycle hook (Custom provider'lar, Lifecycle hook'lar). Rasmiy modul shularni sizning o'rningizga yozadi. Modul yo'q bo'lsa — o'zingiz yozasiz. Kod 20 qator.

O'xshatish: tayyor shkaf va o'zingiz yig'adigan shkaf. Tayyor shkaf (rasmiy modul) — qulay, lekin ichida nima borligini ko'rmaysiz. IKEA uslubidagi shkafni o'zingiz yig'asiz: har vintni bilasiz va kerak bo'lsa javonni boshqa joyga ko'chirasiz.

2. Drizzle — fabrika va token

2.1 Jadval tavsifi

ts
const vazifalar = pgTable("vazifalar", {
  id: bigint("id", { mode: "number" }).primaryKey(),
  egasiId: bigint("egasi_id", { mode: "number" }).notNull(),
  matn: text("matn").notNull(),
  bajarildi: boolean("bajarildi").notNull(),
  tartib: integer("tartib").notNull(),
  ochirilganVaqt: timestamp("ochirilgan_vaqt", {
    withTimezone: true,
  }),
});

27-qismdagi Drizzle: jadval — oddiy obyekt, dekoratorsiz. bigint(..., { mode: "number" }) — bigint ustunni JavaScript number qilib qaytarish (TypeORM darsidagi bigint tuzog'ining Drizzle yechimi).

2.2 Provider'lar

ts
const DRIZZLE = Symbol("DRIZZLE");
const PG_POOL = Symbol("PG_POOL");

// modulning providers massivida:
{
  provide: PG_POOL,
  useFactory: () => new pg.Pool({ connectionString: URL, max: 5 }),
},
{
  provide: DRIZZLE,
  useFactory: (p: pg.Pool) => drizzle(p),
  inject: [PG_POOL],
},

Ikki provider — zanjir:

  • PG_POOL — pg.Pool nusxasi. Fabrika bir marta chaqiriladi; hamma iste'molchi bitta pool'ni oladi (provider — singleton).
  • DRIZZLE — Drizzle obyekti. inject: [PG_POOL] — fabrikaga pool beriladi. Nest avval PG_POOL ni yaratadi, keyin DRIZZLE ni (DI va IoC konteyner).
  • Symbol — token. Nega satr ("DRIZZLE") emas? Ikki kutubxona bir xil satrni ishlatsa, tokenlar to'qnashadi. Symbol esa har safar noyob.

2.3 Xizmatda

ts
@Injectable()
class DrizzleOmbor {
  constructor(@Inject(DRIZZLE) private readonly db: NodePgDatabase) {}
  royxat() {
    return this.db.select().from(vazifalar)
      .where(and(
        eq(vazifalar.egasiId, 1),
        isNull(vazifalar.ochirilganVaqt),
      ))
      .orderBy(asc(vazifalar.tartib), asc(vazifalar.id))
      .limit(50);
  }
}

@Inject(DRIZZLE) — "shu tokendagi qiymatni ber". NodePgDatabase turi — Drizzle'ning pg ustidagi obyekti. So'rov — 27-qismdagi Drizzle tili, Nest unga tegmaydi.

3. Kysely — xuddi shu qolip

ts
type DB = {
  vazifalar: {
    id: string; egasi_id: string; matn: string; bajarildi: boolean;
    tartib: number; ochirilgan_vaqt: Date | null;
  };
};
const KYSELY = Symbol("KYSELY");

@Injectable()
class KyselyOmbor {
  constructor(@Inject(KYSELY) private readonly db: Kysely<DB>) {}
  royxat() {
    return this.db.selectFrom("vazifalar")
      .select(["id", "matn", "bajarildi", "tartib"])
      .where("egasi_id", "=", "1")
      .where("ochirilgan_vaqt", "is", null)
      .orderBy("tartib").orderBy("id")
      .limit(50)
      .execute();
  }
}

// providers:
{
  provide: KYSELY,
  useFactory: () => new Kysely<DB>({
    dialect: new PostgresDialect({
      pool: new pg.Pool({ connectionString: URL, max: 5 }),
    }),
  }),
},

DB turi — Kysely'ga bazaning tuzilishini aytadi: shu tufayli selectFrom("vazifalar") va select(["id", ...]) tipli (Kysely). egasi_id: string — Kysely bigint ni pg dan qanday kelsa shunday beradi: satr.

3.1 Drizzle yoki Kysely — farqi nimada?

Nest uchun ikkalasi bir xil ulanadi: token, fabrika, yopuvchi. Farq kutubxonaning o'zida. Ikkalasini 27-qismda ko'rgansiz; bu yerda faqat tanlovga ta'sir qiladigan joylarni yonma-yon qo'yamiz:

Drizzle Kysely
Jadval tavsifi TypeScript'da pgTable(...) — kod ichida DB turi — o'zingiz yozasiz yoki bazadan generatsiya qilasiz
Migratsiya drizzle-kit sxemadan SQL yasaydi o'zingiz yozgan migratsiya fayllari (Migrator)
bigint mode: "number" bilan son pg qanday bersa shunday — satr
So'rov uslubi SQL'ga yaqin + relyatsion so'rovlar sof SQL quruvchi
Nest'ga ulanish fabrika + token fabrika + token

Oxirgi qatorga qarang: Nest nuqtai nazaridan ular orasida farq yo'q. Bu darsning asosiy fikri ham shu — Nest kutubxonani tanimaydi, u faqat "token → qiymat" juftini biladi. Kutubxonani loyihaning talabiga qarab tanlaysiz (migratsiya kerakmi, tur qayerdan keladi), Nest'ga qarab emas.

Bir narsaga e'tibor bering: Kysely fabrikasi yangi pg.Pool yaratdi — PG_POOL ni inject qilmadi. Bu kanon tajribasida ataylab shunday (har kutubxona alohida o'lchansin). Lekin bu degani — ilovada ikki pool. Bu haqda «Ortiqcha pool tuzog'i» bo'limida.

4. Yopish — sizning ishingiz

4.1 PoolYopuvchi

Rasmiy modul destroy() ni o'zi chaqiradi. Bizda modul yo'q — yopishni ham o'zimiz yozamiz. Kanon tajribasida:

ts
/** pool'ni ilova yopilganda yopish — rasmiy modul bo'lmasa
 *  bu sizning ishingiz (onModuleDestroy) */
@Injectable()
class PoolYopuvchi implements OnModuleDestroy {
  constructor(
    @Inject(PG_POOL) private readonly pool: pg.Pool,
    @Inject(KYSELY) private readonly k: Kysely<DB>,
  ) {}
  async onModuleDestroy() {
    await this.k.destroy();
    await this.pool.end();
    console.log("  onModuleDestroy: Kysely va pg pool yopildi");
  }
}

Yopuvchi alohida provider: u pool'larni inject qilib oladi va app.close() da ularni yopadi. k.destroy() — Kysely o'zining pool'ini yopadi; pool.end() — Drizzle ishlatgan pool. Ikkalasi await bilan: ulanishlar haqiqatan yopilmaguncha ilova chiqmaydi.

Yopilmasa nima bo'ladi? pg.Pool ochiq ulanishlar bilan Node jarayonini tirik ushlab turadi: app.close() dan keyin jarayon chiqmaydi, test esa "osilib qoladi" (Server timeout'lari va keep-alive).

4.2 Kanon chiqishi

Tajriba bitta jadvaldan uch kutubxona bilan bir xil 12 qatorni o'qidi. Chiqishda uch narsaga qarang: mediana vaqti, id ning turi va oxirgi qatordagi yopilish xabari.

text
kontekst (TypeORM ulanishi bilan): 79 ms
TypeORM (Repository.find): 12 qator, mediana 2.18 ms; id turi: string; birinchisi: {"id":"1","egasiId":"1","matn":"JavaScript kursining 26-qismini boshlash #o'qish","bajarildi":false,"tartib":1
Drizzle: 12 qator, mediana 2.36 ms; id turi: number; birinchisi: {"id":1,"egasiId":1,"matn":"JavaScript kursining 26-qismini boshlash #o'qish","bajarildi":false,"tartib":1,"oc
Kysely: 12 qator, mediana 2.49 ms; id turi: string; birinchisi: {"id":"1","matn":"JavaScript kursining 26-qismini boshlash #o'qish","bajarildi":false,"tartib":1}
  onModuleDestroy: Kysely va pg pool yopildi

(Kanon o'lchovi, IlmHamroh, 2026-10, NestJS 12.2.0; bitta so'rov 100 marta, mediana; uzun qatorlar kanon chiqishida kesilgan.)

Nima bo'ldi?

  • Drizzle: id — number (mode: "number"), Kysely: string. Bir xil jadval, bir xil drayver — tur kutubxona sozlamasiga bog'liq.
  • Tezlik farqi 0.3 ms ichida — bitta mashinada, bir vaqtda o'lchangan; xulosa: teng. Tanlov tezlik bo'yicha emas.
  • Oxirgi qator — PoolYopuvchi ishladi.

5. Provider'lar xaritasi

flowchart TB
  U["URL (config)"] --> P["PG_POOL (pg.Pool)"]
  P --> D["DRIZZLE"]
  U --> K["KYSELY (o'z pool'i)"]
  D --> DO["DrizzleOmbor"]
  K --> KO["KyselyOmbor"]
  P --> Y["PoolYopuvchi"]
  K --> Y

Uchta narsani ko'ring. Birinchisi: DRIZZLE va KYSELY — ikki alohida tarmoq, ikki pool. Ikkinchisi: PoolYopuvchi ikkalasiga tayanadi — ularning birortasi unutilsa, ulanishlar yopilmaydi. Uchinchisi: hech qayerda rasmiy modul yo'q — faqat token, fabrika va hook.

6. Ikki tuzoq

6.1 Token turi tekshirilmaydi

ts
// ❌ token DRIZZLE, tur esa Kysely — TypeScript jim
constructor(@Inject(DRIZZLE) private readonly db: Kysely<DB>) {}

@Inject(DRIZZLE) va db: Kysely<DB> o'rtasida TypeScript hech qanday bog'lanishni bilmaydi: token — oddiy Symbol, tur esa — sizning va'dangiz. Kompilyatsiya o'tadi, xato ish vaqtida — this.db.selectFrom is not a function.

Yechim — token va turni bitta joyda bog'lash:

ts
export const DRIZZLE = Symbol("DRIZZLE");
export type Drizzle = NodePgDatabase;
export const InjectDrizzle = () => Inject(DRIZZLE);

// xizmatda:
constructor(@InjectDrizzle() private readonly db: Drizzle) {}

InjectDrizzle — custom dekorator: @Inject(DRIZZLE) ning nomli qisqartmasi. Endi xizmat token nomini yozmaydi, faqat @InjectDrizzle(). Xato ehtimoli kamayadi, lekin to'liq yo'qolmaydi — tur baribir siz yozgan. Kanonda ham shunday: tokenlar.ts dagi VAZIFA_OMBORI va @Inject(VAZIFA_OMBORI) ombor: VazifaOmbori.

6.2 Ortiqcha pool

Tajribada uchta pool bor edi: TypeORM'niki, PG_POOL va Kysely'niki — har biri max: 5. Bitta ilova bazaga 15 tagacha ulanish ochadi. Postgres'ning max_connections i cheklangan (Connection pooling): 4 nusxada ishlasa — 60 ulanish.

To'g'ri yo'l — bitta PG_POOL va hamma kutubxona uni inject qiladi:

ts
{
  provide: KYSELY,
  useFactory: (p: pg.Pool) =>
    new Kysely<DB>({ dialect: new PostgresDialect({ pool: p }) }),
  inject: [PG_POOL],
},

Lekin ehtiyot bo'ling: endi kysely.destroy() shu umumiy pool'ni ham yopadi. Yopuvchi faqat bitta narsani yopishi kerak — pool'ning egasini. Kim yaratgan bo'lsa, o'sha yopadi.

Tekshirib ko'ring: KYSELY endi PG_POOL ni inject qiladi. PoolYopuvchi avvalgidek await this.k.destroy(); await this.pool.end(); qiladi. Nima bo'ladi?

Javob

k.destroy() umumiy pool'ni yopadi, keyin pool.end() uni ikkinchi marta yopishga urinadi. Biz sinadik (biz ishga tushirdik, pg 8.23.1, kysely 0.29.6): Kysely bir marta ishlatilgan pool'ni destroy() da yopdi va keyingi pool.end() — Error: Called end on pool more than once ("pool'da end bir martadan ko'p chaqirildi"). Qoida: bitta pool — bitta yopuvchi. Bu holatda faqat pool.end() qoladi (Kysely'ga destroy shart emas — uning pool'i o'ziniki emas).

6.3 "Bitta egasi" qoidasi — kichik model

Xatoni brauzerda ham ko'rish mumkin. Quyidagi model pg.Pool ning faqat bitta xususiyatini takrorlaydi: end() ikkinchi marta chaqirilsa, xato tashlaydi. kysely.destroy() ichida esa xuddi Kysely'dagidek umumiy pool'ning end() i chaqiriladi.

js
function poolYasa(nom) {
  let yopilgan = false;
  return {
    nom,
    end() {
      if (yopilgan) {
        throw new Error("Called end on pool more than once");
      }
      yopilgan = true;
      console.log(`${nom}: yopildi`);
    },
  };
}

// Kysely umumiy pool'ni oldi; destroy() ichida pool.end() bor
const umumiy = poolYasa("PG_POOL");
const kysely = { destroy: () => umumiy.end() };

// xato: ikki yopuvchi
try {
  kysely.destroy();
  umumiy.end();
} catch (e) {
  console.log("xato:", e.message);
}

// to'g'ri: pool'ni yaratgan provider yopadi
const yangi = poolYasa("PG_POOL-2");
const egalar = [{ nom: "PoolYopuvchi", yop: () => yangi.end() }];
for (const e of egalar) e.yop();
console.log("yopuvchilar soni:", egalar.length);

Konsolda:

text
PG_POOL: yopildi
xato: Called end on pool more than once
PG_POOL-2: yopildi
yopuvchilar soni: 1

Nima bo'ldi? Birinchi qator — kysely.destroy() pool'ni yopdi. Ikkinchisi — umumiy.end() o'sha pool'ni yana yopmoqchi bo'ldi va pg dagi xabarning o'zini oldi. Pastki qism — bitta egasi bor: pool bir marta yopildi, xato yo'q. Model soddalashtirilgan: haqiqiy pool.end() Promise qaytaradi va ochiq ulanishlarni kutadi.

Qoidani yodda saqlash uchun savol bering: "bu obyektni kim new qildi?" Kim yaratgan bo'lsa, o'sha yopadi. Boshqalar faqat foydalanadi.

7. Dinamik modulga o'rash

Bir loyihada bir necha marta kerak bo'lsa, provider'larni dinamik modul ichiga yig'ish mumkin — o'zingizning "rasmiy moduli"ngiz:

ts
@Global()
@Module({})
export class DrizzleModule {
  static forRootAsync(): DynamicModule {
    return {
      module: DrizzleModule,
      providers: [
        {
          provide: PG_POOL,
          useFactory: (c: Config) =>
            new pg.Pool({ connectionString: c.DATABASE_URL }),
          inject: [CONFIG],
        },
        {
          provide: DRIZZLE,
          useFactory: (p: pg.Pool) => drizzle(p),
          inject: [PG_POOL],
        },
        PoolYopuvchi,
      ],
      exports: [DRIZZLE],
    };
  }
}

Bu — naqsh: biz uni tip tekshiruvidan o'tkazdik (tsc), bazaga ulanib sinamadik. E'tibor bering: exports da faqat DRIZZLE — pool tashqariga chiqmaydi. Xizmat bazaga faqat Drizzle orqali kiradi, pool'ni yopib qo'ya olmaydi. Jamoa paketlari ham bor (Drizzle uchun bir nechta), lekin ular rasmiy emas — ichini o'qimasdan ishlatmang.

8. Kanon: vazifalar-api — aynan shu naqsh

Bu darsdagi hamma narsa loyihada Dynamic modullar darsidan beri bor. OmborModule.forRootAsync():

  • Token: VAZIFA_OMBORI va SAQLASH — Symbol (tokenlar.ts, ombor.module.ts).
  • Fabrika: useFactory: (config, log) => omborYarat(config, log), inject: [CONFIG, LOG] — async: Nest Promise'ni kutadi, baza yo'q bo'lsa ilova ishga tushmaydi.
  • Yopish: SaqlashYopuvchi — onApplicationShutdown hook'ida saqlash.yop(): pool, Prisma klienti, Redis.
  • Bitta pool: har saqlash usuli o'z pool'ini bitta yaratadi va yopishni o'zi biladi (Saqlash = { ombor, yop }).

Farq bitta: kanon onModuleDestroy emas, onApplicationShutdown ni ishlatadi. Sabab — vaqt: onApplicationShutdown HTTP server yopilgandan keyin chaqiriladi, boshlangan so'rovlar bazani ishlatib bo'lgach (Lifecycle hook'lar). Kanonda Drizzle va Kysely'ning o'zi yo'q — 27-qism ORM tanlovi (pg + Prisma) o'zgarmadi.

Tajriba (Drizzle/Kysely) Kanon (OmborModule)
Token DRIZZLE, KYSELY VAZIFA_OMBORI, SAQLASH
Fabrika sinxron async (fail-fast tekshiruvlar bilan)
Yopish onModuleDestroy onApplicationShutdown
Pool ikkita (ataylab) bitta

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
app.close() dan keyin jarayon chiqmaydi pool yopilmagan yopuvchi provider
Called end on pool more than once ikki joy bitta pool'ni yopdi bitta egasi yopadi
selectFrom is not a function token va tur mos emas token + tur + dekorator bitta faylda
too many clients already har kutubxona o'z pool'i bitta PG_POOL ni inject
Nest can't resolve … Symbol(DRIZZLE) at index [0] provider boshqa modulda, eksport yo'q exports: [DRIZZLE] yoki @Global

Oxirgi qatordagi xabarni demo loyihada oldik (biz ishga tushirdik): Nest can't resolve dependencies of the DrizzleOmbor (?). Please make sure that the argument Symbol(DRIZZLE) at index [0] is available in the M module. Token Symbol(DRIZZLE) bo'lib chiqdi — Symbol("DRIZZLE") dagi tavsif. Shuning uchun Symbol() ga doim nom bering: Symbol() ning o'zi xabarda Symbol() bo'lib, hech narsa aytmaydi.

10. Mashqlar

Mashqlar kurs/mashqlar/30/34-drizzle-kysely-nestjs/ da.

1-mashq (oson): qaysi kalit?

To'ldiring:

  • Fabrikaga boshqa provider berish uchun kalit —
  • Kutubxonalar to'qnashmasligi uchun token turi —
  • Drizzle'da bigint ni son qilib olish: { mode: }
  • Kanon pool'ni qaysi hook'da yopadi? —
Yechim
  • inject — inject: [PG_POOL].
  • Symbol — har biri noyob.
  • "number" — bigint("id", { mode: "number" }).
  • onApplicationShutdown — HTTP server yopilgandan keyin.

2-mashq (o'rta): fabrika provider'lar konteyneri

Brauzerda ishlaydigan kichik konteyner yozing: royxat — { provide, useFactory, inject } lar. ol(token) provider'ni bir marta yaratsin (keyingi chaqiruvda o'sha nusxa), inject dagilarni avval yaratsin. yop() — yaratilganlarni teskari tartibda aylanib, yop metodi borlarini chaqirsin. Sinov: PG_POOL (obyekt { nom: "pool", yop }), DRIZZLE (pool'ga tayanadi), KYSELY (o'sha pool'ga tayanadi). Ishora: Map keshi (Map).

Yechim
js
function konteyner(royxat) {
  const nomli = new Map(royxat.map((p) => [p.provide, p]));
  const tayyor = new Map();
  function ol(token) {
    if (tayyor.has(token)) return tayyor.get(token);
    const p = nomli.get(token);
    const deps = (p.inject ?? []).map(ol);
    const qiymat = p.useFactory(...deps);
    console.log(`yaratildi: ${token.description}`);
    tayyor.set(token, qiymat);
    return qiymat;
  }
  async function yop() {
    for (const q of [...tayyor.values()].reverse()) await q.yop?.();
  }
  return { ol, yop };
}

const PG_POOL = Symbol("PG_POOL");
const DRIZZLE = Symbol("DRIZZLE");
const KYSELY = Symbol("KYSELY");

const k = konteyner([
  {
    provide: PG_POOL,
    useFactory: () => ({
      nom: "pool", yop: () => console.log("pool.end()"),
    }),
  },
  {
    provide: DRIZZLE,
    useFactory: (p) => ({ pool: p }),
    inject: [PG_POOL],
  },
  {
    provide: KYSELY,
    useFactory: (p) => ({ pool: p }),
    inject: [PG_POOL],
  },
]);

const d = k.ol(DRIZZLE);
const ky = k.ol(KYSELY);
console.log("bitta pool:", d.pool === ky.pool);
await k.yop();

Konsolda:

text
yaratildi: PG_POOL
yaratildi: DRIZZLE
yaratildi: KYSELY
bitta pool: true
pool.end()

PG_POOL faqat bir marta yaratildi — tayyor keshi provider'ni singleton qildi. bitta pool: true — ikkala kutubxona bitta hovuzni oldi («Ortiqcha pool» bo'limidagi to'g'ri yo'l). Yopishda faqat pool'da yop bor — u bir marta chaqirildi. token.description — Symbol("PG_POOL") dagi nom; Nest xabarlari ham shundan foydalanadi.

3-mashq (qiyin): tipli token yordamchisi

Token bilan turni ajralmas qilish uchun yordamchi yozing (TypeScript, Node'da tekshiriladi). tokenYarat<T>(nom) — { belgi: Symbol(nom) } qaytarsin, T esa faqat tur darajasida. Konteyner klassi: ber<T>(token, qiymat: T) va ol<T>(token): T. Shunda ol(DRIZZLE) ning turi avtomatik bo'ladi, noto'g'ri tur bilan ber esa tip xatosi beradi. Ishora: Generics va "fantom" tur: { readonly belgi: symbol; readonly _tur?: T }.

Yechim
ts
type Token<T> = { readonly belgi: symbol; readonly _tur?: T };

function tokenYarat<T>(nom: string): Token<T> {
  return { belgi: Symbol(nom) };
}

class Konteyner {
  #qiymatlar = new Map<symbol, unknown>();
  ber<T>(token: Token<T>, qiymat: T): void {
    this.#qiymatlar.set(token.belgi, qiymat);
  }
  ol<T>(token: Token<T>): T {
    if (!this.#qiymatlar.has(token.belgi)) {
      throw new Error(`${String(token.belgi)} topilmadi`);
    }
    return this.#qiymatlar.get(token.belgi) as T;
  }
}

type Drizzle = { select(): string };
type Kysely = { selectFrom(jadval: string): string };

const DRIZZLE = tokenYarat<Drizzle>("DRIZZLE");
const KYSELY = tokenYarat<Kysely>("KYSELY");

const k = new Konteyner();
k.ber(DRIZZLE, { select: () => "drizzle so'rovi" });
k.ber(KYSELY, { selectFrom: (j) => `kysely: ${j}` });

console.log(k.ol(DRIZZLE).select());
console.log(k.ol(KYSELY).selectFrom("vazifalar"));
try {
  k.ol(tokenYarat<Drizzle>("DRIZZLE"));
} catch (e) {
  console.log((e as Error).message);
}

Konsolda:

text
drizzle so'rovi
kysely: vazifalar
Symbol(DRIZZLE) topilmadi

k.ol(DRIZZLE) ning turi — Drizzle, .select() ni TypeScript biladi. k.ber(DRIZZLE, { selectFrom: ... }) deb yozsangiz — tip xatosi: «Token turi tekshirilmaydi» bo'limidagi muammo shu yerda yopildi. Oxirgi qator: bir xil nomli yangi token — boshqa Symbol, qiymat topilmadi. Symbol har safar noyob ekanining isboti. Nest'ning @Inject i bunday tipli tokenni qo'llamaydi — bu yordamchi g'oyani ko'rsatadi.

11. Loyiha qadami: o'zgarish yo'q — naqsh allaqachon bor

vazifalar-api o'zgarmaydi. Bu darsning naqshi — fabrika provider, Symbol token, yopuvchi hook — Dynamic modullar darsidan beri OmborModule da ishlaydi («Kanon» bo'limi). Drizzle va Kysely'ning o'zi loyihaga kirmaydi: 27-qism ORM tanlovi o'zgarmadi.

O'z loyihangizda bir kichik tekshiruv qiling: src/ombor.module.ts ni oching va undagi uch narsani toping — token (SAQLASH), fabrika (omborYarat), yopuvchi (SaqlashYopuvchi). Endi siz ularni "Nest sehri" emas, o'zingiz yozgan qismlar sifatida o'qiysiz.

12. Real ishda

  • Rasmiy moduli yo'q kutubxona — Nest'da kundalik holat: Drizzle, Kysely, Redis klienti, S3, Elasticsearch, to'lov SDK'lari. Hammasi shu darsdagi uch qadam bilan ulanadi (Tashqi xizmatlarni Nest modullariga o'rash darsida yana ko'ramiz).
  • Pool soni — prodakshnda "baza ulanishlari tugadi" hodisasining eng ko'p uchraydigan sababi; review'da new pg.Pool necha joyda borligi qidiriladi.
  • Drizzle + Nest juftligi yangi loyihalarda tez o'smoqda — aynan rasmiy modulga ehtiyoj yo'qligi uchun.
  • Intervyu: "Rasmiy moduli yo'q kutubxonani Nest'ga qanday ulaysiz?", "useFactory va inject nima?", "pool'ni qachon yopasiz?".

Xulosa

  • Rasmiy modul — fabrika provider + token + lifecycle hook; yo'q bo'lsa, o'zingiz yozasiz (20 qator).
  • { provide: TOKEN, useFactory, inject } — zanjir: pool → Drizzle/Kysely → ombor.
  • Token — Symbol (nomi bilan); turni TypeScript tekshirmaydi — token, tur va @InjectX() dekoratorini bitta joyda saqlang.
  • Yopish — sizning ishingiz: onModuleDestroy / onApplicationShutdown; bitta pool — bitta yopuvchi.
  • Har kutubxona o'z pool'ini ochsa — ulanishlar ko'payadi; bitta PG_POOL ni inject qiling.
  • Kanon tajribasida TypeORM, Drizzle, Kysely tezligi teng; vazifalar-api ning OmborModule i — shu naqsh.

Keyingi dars: Tranzaksiyalar NestJS'da — bir nechta xizmat chaqiruvini bitta tranzaksiyaga o'raymiz va 25–27-qismdan beri yashirinib yotgan haqiqiy xatoni topamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Drizzle va Kysely bilan NestJS: custom provider, Symbol token va pool yopish — IlmHamroh