Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. Drizzle — fabrika va token
- 2.1 Jadval tavsifi
- 2.2 Provider'lar
- 2.3 Xizmatda
- 3. Kysely — xuddi shu qolip
- 3.1 Drizzle yoki Kysely — farqi nimada?
- 4. Yopish — sizning ishingiz
- 4.1 PoolYopuvchi
- 4.2 Kanon chiqishi
- 5. Provider'lar xaritasi
- 6. Ikki tuzoq
- 6.1 Token turi tekshirilmaydi
- 6.2 Ortiqcha pool
- 6.3 "Bitta egasi" qoidasi — kichik model
- 7. Dinamik modulga o'rash
- 8. Kanon: vazifalar-api — aynan shu naqsh
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): qaysi kalit?
- 2-mashq (o'rta): fabrika provider'lar konteyneri
- 3-mashq (qiyin): tipli token yordamchisi
- 11. Loyiha qadami: o'zgarish yo'q — naqsh allaqachon bor
- 12. Real ishda
- Xulosa
- Manbalar
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: NodePgDatabasebilan oladi. Ulanishlar hovuzini (pool) yopish — sizning ishingiz:onModuleDestroydapool.end()vakysely.destroy(). Kanon tajribasida bir xil so'rov Drizzle'da 2.36 ms, Kysely'da 2.49 ms (TypeORM 2.18 ms) — farq ahamiyatsiz.vazifalar-apining o'zi ham aynan shu naqsh bilan qurilgan.
Bu darsda
- Rasmiy moduli yo'q kutubxonani
useFactory+Symboltoken bilan Nest'ga ulaysiz. injectbilan provider'lar zanjirini (pool → Drizzle) tuzasiz.- Tokenning turi TypeScript tomonidan tekshirilmasligini va buni qanday yopishni bilasiz.
- Pool'ni
onModuleDestroyda to'g'ri yopasiz va "ortiqcha pool" tuzog'ini ko'rasiz. vazifalar-apidagiOmborModuleaynan 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
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
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.Poolnusxasi. Fabrika bir marta chaqiriladi; hamma iste'molchi bitta pool'ni oladi (provider — singleton).DRIZZLE— Drizzle obyekti.inject: [PG_POOL]— fabrikaga pool beriladi. Nest avvalPG_POOLni yaratadi, keyinDRIZZLEni (DI va IoC konteyner).Symbol— token. Nega satr ("DRIZZLE") emas? Ikki kutubxona bir xil satrni ishlatsa, tokenlar to'qnashadi.Symbolesa har safar noyob.
2.3 Xizmatda
@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
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:
/** 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.
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 —
PoolYopuvchiishladi.
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 --> YUchta 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
// ❌ 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:
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:
{
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:
KYSELYendiPG_POOLni inject qiladi.PoolYopuvchiavvalgidekawait 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.
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:
PG_POOL: yopildi
xato: Called end on pool more than once
PG_POOL-2: yopildi
yopuvchilar soni: 1Nima 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:
@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_OMBORIvaSAQLASH—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—onApplicationShutdownhook'idasaqlash.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
bigintni 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
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:
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
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:
drizzle so'rovi
kysely: vazifalar
Symbol(DRIZZLE) topilmadik.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.Poolnecha 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?", "
useFactoryvainjectnima?", "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_POOLniinjectqiling. - Kanon tajribasida TypeORM, Drizzle, Kysely tezligi teng;
vazifalar-apiningOmborModulei — 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
- NestJS hujjati — Custom providers (
useFactory,inject, tokenlar): https://docs.nestjs.com/fundamentals/custom-providers - NestJS hujjati — Lifecycle events (
onModuleDestroy,onApplicationShutdown): https://docs.nestjs.com/fundamentals/lifecycle-events - Drizzle ORM — node-postgres: https://orm.drizzle.team/docs/get-started-postgresql
- Kysely — PostgresDialect: https://kysely.dev/docs/getting-started
- Kanon (IlmHamroh, 2026-10-28): Drizzle/Kysely tajribasi (NestJS 12.2.0) — mediana 2.36 / 2.49 ms,
idturlari,PoolYopuvchi;OmborModule. Tajriba kodining tip tekshiruvi (drizzle-orm 0.45.4, kysely 0.29.6) — biz ishga tushirdik (2026-11-02).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!