@querykitjs/prisma-pg 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +363 -0
- package/README.md +362 -0
- package/dist/index.cjs +844 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +494 -0
- package/dist/index.d.ts +494 -0
- package/dist/index.js +837 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
<div align="right">
|
|
2
|
+
|
|
3
|
+
[English](./README.en.md) · **O'zbekcha**
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
# @querykitjs/prisma-pg
|
|
8
|
+
|
|
9
|
+
> Prisma (PostgreSQL) uchun **advanced filtering** va **moslashuvchan paginatsiya** beruvchi repository qatlami.
|
|
10
|
+
|
|
11
|
+
Prisma bilan qo'lda yozib chiqiladigan ko'p qismlarni — murakkab filtrlar, uch xil paginatsiya, tranzaksiya, RBAC scope, soft-delete, bulk/agregatsiya — bitta tipli repository ostiga jamlaydi. `columns`/`with` bo'yicha natija tipi **avtomatik** aniqlanadi.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const { data, meta } = await usersRepository.findList({
|
|
15
|
+
page: 2,
|
|
16
|
+
perPage: 20,
|
|
17
|
+
filter: f.and(f.eq("status", "active"), f.gte("age", 18)),
|
|
18
|
+
sort: [{ key: "createdAt", direction: "desc" }],
|
|
19
|
+
});
|
|
20
|
+
// ^? data: User[] meta: { total_items, total_pages, has_next, ... }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Imkoniyatlar
|
|
24
|
+
|
|
25
|
+
- **Advanced filterlar** — nested `and`/`or`/`not`, 27 operator, xom Prisma `where` escape-hatch, `f` yordamchilari.
|
|
26
|
+
- **3 xil paginatsiya** — offset (`findList`), infinite-scroll (`findInfinite`), cursor/keyset (`findCursor`).
|
|
27
|
+
- **`columns` + `with` inference** — tanlangan maydonlar va relation'lar qo'lda generic yozmasdan tiplanadi.
|
|
28
|
+
- **Multi-field sort**, `count`, `exists`, `aggregate` (count/sum/avg/min/max + groupBy).
|
|
29
|
+
- **Tranzaksiyalar** — `registry.transaction()`, ichidagi repolar avtomatik ulanadi.
|
|
30
|
+
- **Scoped repository** — RBAC/multi-tenancy uchun doimiy base filter + create default.
|
|
31
|
+
- **Soft-delete** — `deletedAt` maydoni bo'lsa avtomatik; `softDelete`/`restore`/`withDeleted`.
|
|
32
|
+
- **Upsert / bulk** — `upsert`, `upsertMany`.
|
|
33
|
+
- **Framework-agnostik** — HTTP'ni bilmaydi: Express, Hono, NestJS, fon vazifalari — hammasiga tushadi.
|
|
34
|
+
- **Cross-adapter parity** — `@querykitjs/drizzle-pg` va `@querykitjs/mongoose` bilan bir xil yuza va bir xil natija ([PARITY.md](./PARITY.md)).
|
|
35
|
+
|
|
36
|
+
## O'rnatish
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun add @querykitjs/prisma-pg @prisma/client
|
|
40
|
+
# yoki: npm i @querykitjs/prisma-pg @prisma/client
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`@prisma/client` (>=5) — peer dependency. Paket generatsiya qilingan client'ni **import qilmaydi**: model metadata'sini ish vaqtida sizning client instansiyangizdan o'qiydi, tiplarni esa siz uzatgan delegate'dan chiqaradi.
|
|
44
|
+
|
|
45
|
+
## Sozlash
|
|
46
|
+
|
|
47
|
+
### 1. Registry (bir marta)
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// db/registry.ts
|
|
51
|
+
import { PrismaClient } from "@prisma/client";
|
|
52
|
+
import { createRegistry } from "@querykitjs/prisma-pg";
|
|
53
|
+
|
|
54
|
+
export const prisma = new PrismaClient();
|
|
55
|
+
export const registry = createRegistry(prisma);
|
|
56
|
+
// yoki pagination default'larini sozlab:
|
|
57
|
+
// export const registry = createRegistry(prisma, { defaultPerPage: 20, defaultLimit: 20 });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Repository (model bo'yicha)
|
|
61
|
+
|
|
62
|
+
Model **handle** bilan olinadi — `drizzle-pg` jadval obyektini, `mongoose` esa `Model`ni qabul qilgani kabi. Nom (delegate kaliti, camelCase) ham qabul qilinadi: `User` → `"user"`, `LegalEntity` → `"legalEntity"`.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// db/users.repository.ts
|
|
66
|
+
import { prisma, registry } from "./registry";
|
|
67
|
+
|
|
68
|
+
export const usersRepository = registry.repository(prisma.user);
|
|
69
|
+
// …yoki nomi bilan: registry.repository("user") — ikkalasi bir xil
|
|
70
|
+
|
|
71
|
+
// custom metodlar bilan:
|
|
72
|
+
export const postsRepository = registry.repository("post", base => ({
|
|
73
|
+
findBySlug: (slug: string) => base.findOne({ filter: [{ key: "slug", value: slug }] }),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// repository sozlamalari bilan (RBAC / projeksiya):
|
|
77
|
+
export const safeUsers = registry.repository("user", {
|
|
78
|
+
forcedColumns: { id: true, name: true, email: true }, // parol hech qachon chiqmaydi
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Framework-agnostik: Express, Hono, NestJS
|
|
83
|
+
|
|
84
|
+
Bu paket **HTTP'ni bilmaydi**. Request'ni parse qilish va validatsiya app tomonda (`@querykitjs/zod` yoki `@querykitjs/class-validator`), repository esa hamma joyda bir xil chaqiriladi.
|
|
85
|
+
|
|
86
|
+
**Express**
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import express from "express";
|
|
90
|
+
import { offsetParamsSchema } from "@querykitjs/zod";
|
|
91
|
+
import { usersRepository } from "./db/users.repository";
|
|
92
|
+
|
|
93
|
+
const app = express();
|
|
94
|
+
app.post("/users/list", express.json(), async (req, res) => {
|
|
95
|
+
const params = offsetParamsSchema.parse(req.body);
|
|
96
|
+
res.json(await usersRepository.findList(params));
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Hono**
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { Hono } from "hono";
|
|
104
|
+
import { offsetParamsSchema } from "@querykitjs/zod";
|
|
105
|
+
import { usersRepository } from "./db/users.repository";
|
|
106
|
+
|
|
107
|
+
const app = new Hono();
|
|
108
|
+
app.post("/users/list", async c => {
|
|
109
|
+
const params = offsetParamsSchema.parse(await c.req.json());
|
|
110
|
+
return c.json(await usersRepository.findList(params));
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**NestJS**
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { Body, Controller, Post } from "@nestjs/common";
|
|
118
|
+
import { OffsetParamsDto } from "@querykitjs/class-validator";
|
|
119
|
+
import { usersRepository } from "./db/users.repository";
|
|
120
|
+
|
|
121
|
+
@Controller("users")
|
|
122
|
+
export class UsersController {
|
|
123
|
+
@Post("list")
|
|
124
|
+
list(@Body() params: OffsetParamsDto) {
|
|
125
|
+
return usersRepository.findList(params);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Validatsiyalangan payload repository params'iga **cast'siz** tushadi — kontrakt `@querykitjs/core` orqali yopilgan.
|
|
131
|
+
|
|
132
|
+
## Advanced filterlar
|
|
133
|
+
|
|
134
|
+
Filter — field shartlari va mantiqiy guruhlar (`and`/`or`/`not`) daraxti. Flat massiv implicit `AND`.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { createFilters } from "@querykitjs/prisma-pg";
|
|
138
|
+
const f = createFilters<typeof prisma.user>(); // maydon-nomi autocomplete
|
|
139
|
+
|
|
140
|
+
await usersRepository.findAll({
|
|
141
|
+
filter: f.and(f.eq("status", "active"), f.or(f.gte("age", 18), f.in("role", ["admin", "owner"])), f.not(f.isNull("deletedAt"))),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// obyekt shakli (yordamchisiz):
|
|
145
|
+
await usersRepository.findAll({
|
|
146
|
+
filter: [
|
|
147
|
+
{ key: "status", operation: "=", value: "active" },
|
|
148
|
+
{ key: "name", operation: "%_%", value: "ali" },
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Operatorlar
|
|
154
|
+
|
|
155
|
+
| Guruh | Operatorlar |
|
|
156
|
+
| ---------- | -------------------------------------------------------------------------------- |
|
|
157
|
+
| Taqqoslash | `=`/`eq`, `!=`/`ne`, `>`/`gt`, `>=`/`gte`, `<`/`lt`, `<=`/`lte` |
|
|
158
|
+
| Matn | `contains`/`%_%`, `startsWith`/`%_`, `endsWith`/`_%`, `like`, `ilike`, `notLike` |
|
|
159
|
+
| To'plam | `in`, `notIn`, `between`, `notBetween` |
|
|
160
|
+
| NULL | `isNull`, `isNotNull` |
|
|
161
|
+
|
|
162
|
+
`contains`/`startsWith`/`endsWith` — **case-insensitive** (`mode: "insensitive"`), `like` — case-sensitive, `ilike` — insensitive. Bu drizzle-pg adapteri bilan aynan bir xil.
|
|
163
|
+
|
|
164
|
+
> **LIKE pattern'lari.** Prisma `where` ichida xom SQL `LIKE` yo'q, shuning uchun pattern tarjima qilinadi — lekin **aniq**, ya'ni har qanday pattern drizzle-pg bilan bir xil qatorlarni qaytaradi: `%ali%` → `contains`, `ali%` → `startsWith`, `%ali` → `endsWith`, wildcard'siz → `equals`. Ichki `%` va `_` uchun ikkita ayniyat ishlatiladi — `LIKE 'A%S'` ≡ `LIKE 'A%S%' AND LIKE '%S'`, va `%`siz pattern uchun `LIKE 'P'` ≡ `LIKE 'P%' AND NOT LIKE 'P_%'` (uzunlikni qadaydi). Faqat **buzuq** pattern (oxirida osilib qolgan `\`) tashlanadi va reportlanadi. Batafsil: [PARITY.md](./PARITY.md) §5.1.
|
|
165
|
+
|
|
166
|
+
### Xom `where` escape-hatch
|
|
167
|
+
|
|
168
|
+
DSL ifodalay olmaydigan narsalar (relation predikatlari, `mode`, JSON filtrlari) uchun to'g'ridan-to'g'ri Prisma `where` obyekti tugun sifatida beriladi:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
await usersRepository.findAll({ filter: { posts: { some: { title: { contains: "querykit" } } } } });
|
|
172
|
+
|
|
173
|
+
await usersRepository.findAll({
|
|
174
|
+
filter: f.or(f.eq("role", "admin"), { posts: { some: { published: true } } }),
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Noma'lum kalitlar: kuzatish va qattiq rejim
|
|
179
|
+
|
|
180
|
+
Noma'lum filter/sort kaliti **jimgina tashlanadi** (legacy parity). Bu esa natijani kengaytiradi, shuning uchun uni ko'rinadigan qilish mumkin:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
// 1-qadam: kuzatish (xulq o'zgarmaydi)
|
|
184
|
+
createRegistry(prisma, { onSkippedCondition: info => logger.warn({ querykit: info }, "shart tashlab yuborildi") });
|
|
185
|
+
|
|
186
|
+
// 2-qadam: log tozalanganda — qattiq rejim
|
|
187
|
+
createRegistry(prisma, { strict: true });
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { QueryKitError } from "@querykitjs/core";
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
return await usersRepository.findList(params);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (err instanceof QueryKitError) return res.status(400).json({ error: err.message, info: err.info });
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`scope` va `cursorKey` kaliti **har doim** fatal (`strict`dan qat'i nazar) — ular serverning o'z ishi, jimgina tashlash xavfsizlik nuqsoni bo'lardi.
|
|
202
|
+
|
|
203
|
+
## Multi-field sort
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
await usersRepository.findAll({
|
|
207
|
+
sort: [
|
|
208
|
+
{ key: "role", direction: "asc" },
|
|
209
|
+
{ key: "createdAt", direction: "desc" },
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Noma'lum kalit skip qilinadi; yaroqli sort qolmasa `createdAt desc`, u ham bo'lmasa `<id> desc` — paginatsiya barqaror bo'lishi uchun.
|
|
215
|
+
|
|
216
|
+
## Paginatsiya — 3 strategiya
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// 1) Offset — total_items/total_pages bilan
|
|
220
|
+
const list = await usersRepository.findList({ page: 2, perPage: 20 });
|
|
221
|
+
list.meta; // { total_items, total_pages, current_page, per_page, has_next, has_prev }
|
|
222
|
+
|
|
223
|
+
// 2) Infinite-scroll — COUNT'siz
|
|
224
|
+
const feed = await postsRepository.findInfinite({ limit: 20, offset: 40 });
|
|
225
|
+
feed.meta; // { limit, offset, count, has_more, next_offset }
|
|
226
|
+
|
|
227
|
+
// 3) Cursor (keyset) — insert'larga barqaror
|
|
228
|
+
const p1 = await postsRepository.findCursor({ limit: 20, order: "desc" });
|
|
229
|
+
const p2 = await postsRepository.findCursor({ limit: 20, cursor: p1.meta.next_cursor });
|
|
230
|
+
p1.meta; // { limit, has_next, has_prev, next_cursor, prev_cursor }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`perPage`/`limit` **cheklangan** (`DEFAULT_MAX_PER_PAGE` = 200) — validatsiya chetlab o'tilsa ham repository o'zi clamp qiladi.
|
|
234
|
+
|
|
235
|
+
> Cursor tokeni `drizzle-pg` va `mongoose` adapterlari bilan **bayt-ma-bayt bir xil** — backend ORM'i almashsa ham eski token ishlayveradi.
|
|
236
|
+
|
|
237
|
+
## Tipli `columns` va `with`
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const picked = await usersRepository.findAll({ columns: { id: true, email: true } });
|
|
241
|
+
// ^? { id: number; email: string }[] — `name` tipda ham yo'q
|
|
242
|
+
|
|
243
|
+
const withPosts = await usersRepository.findAll({ with: { posts: true } });
|
|
244
|
+
withPosts[0].posts[0].title; // relation tiplangan
|
|
245
|
+
|
|
246
|
+
const both = await usersRepository.findAll({ columns: { id: true }, with: { posts: true } });
|
|
247
|
+
// ^? { id: number; posts: Post[] }[]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
> ⚠️ Prisma bitta darajada `select` va `include`ni **birga qabul qilmaydi**. Adapter buni o'zi hal qiladi: ikkalasi berilsa relation'lar bitta `select` ichiga joylanadi. Siz farqni sezmaysiz.
|
|
251
|
+
|
|
252
|
+
`with` qiymati `true` yoki Prisma relation konfiguratsiyasi bo'lishi mumkin:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
await usersRepository.findAll({ with: { posts: { select: { title: true }, take: 5, orderBy: { createdAt: "desc" } } } });
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Tranzaksiyalar
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
await registry.transaction(async () => {
|
|
262
|
+
const user = await usersRepository.create({ name: "Ali", email: "ali@example.com" });
|
|
263
|
+
await postsRepository.create({ title: "Salom", authorId: user.id });
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Ichidagi repositorylar ambient kontekst (AsyncLocalStorage) orqali avtomatik tranzaksiya client'ida ishlaydi — qo'lda `tx` uzatish shart emas. Xato tashlansa hammasi rollback.
|
|
268
|
+
|
|
269
|
+
> ⚠️ Prisma savepoint bermaydi, shuning uchun **ichma-ich** `transaction()` chaqirilsa mavjud tranzaksiya qayta ishlatiladi (yangi ochilmaydi). `mongoose` adapteri ham shunday; `drizzle-pg` esa haqiqiy savepoint yaratadi.
|
|
270
|
+
|
|
271
|
+
## Scoped repository (RBAC / multi-tenancy)
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
const mine = postsRepository.scoped({ authorId: user.id });
|
|
275
|
+
await mine.findList({ page: 1 }); // faqat shu foydalanuvchi postlari
|
|
276
|
+
await mine.create({ title: "Yangi" }); // authorId avtomatik qo'yiladi
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Scope har bir o'qish/yozishga `AND` bilan qo'shiladi va insert'larga singdiriladi (scope ustun keladi). Scope kaliti model'da topilmasa repository **yaratilishida** xato beradi — jimgina tushib qolsa filtr yo'qolardi.
|
|
280
|
+
|
|
281
|
+
## Projeksiya guard'lari
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
registry.repository("user", { forcedColumns: { id: true, name: true } }); // client `columns`i e'tiborsiz
|
|
285
|
+
registry.repository("user", { allowedColumns: ["id", "name", "email"] }); // client tanlovi kesiladi
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Kesishma bo'sh bo'lsa natija **to'liq qator emas**, allowlist'ning o'zi bo'ladi. Guard `aggregate`ga ham tegishli, va `cursorKey` orqali taqiqlangan maydonni o'qib olishning oldi olingan.
|
|
289
|
+
|
|
290
|
+
> ⚠️ Guard'lar faqat **o'qish** metodlariga ta'sir qiladi. Yozish metodlari tip kontrakti bo'yicha to'liq `Row` qaytaradi — natijani clientga berishdan oldin `findById` bilan qayta o'qing.
|
|
291
|
+
|
|
292
|
+
## Soft-delete
|
|
293
|
+
|
|
294
|
+
Model'da `deletedAt` maydoni bo'lsa avtomatik yoqiladi:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
await postsRepository.softDelete(id); // deletedAt = now()
|
|
298
|
+
await postsRepository.findAll({}); // o'chirilganlar chiqmaydi
|
|
299
|
+
await postsRepository.findAll({ withDeleted: true }); // hammasi
|
|
300
|
+
await postsRepository.restore(id); // deletedAt = null
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`updatedAt` maydoni bo'lsa har bir update'da yangilanadi (`@updatedAt` bo'lsa buni Prisma o'zi qiladi — adapter aralashmaydi).
|
|
304
|
+
|
|
305
|
+
## Upsert, bulk va agregatsiya
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
await usersRepository.upsert({ email: "a@b.com", name: "Ali" }, { target: "email" });
|
|
309
|
+
await usersRepository.upsertMany(rows, { target: "externalId" });
|
|
310
|
+
|
|
311
|
+
await postsRepository.aggregate({ count: true, groupBy: "authorId" });
|
|
312
|
+
// → [{ authorId: 1, count: 12 }, …]
|
|
313
|
+
await ordersRepository.aggregate({ sum: "amount", avg: "amount", groupBy: "region" });
|
|
314
|
+
// → [{ region: "TAS", sum_amount: 900, avg_amount: 75 }, …]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
> **Compound unique.** Ko'p maydonli `target` Prisma'ning generatsiya qilingan nomi bilan ishlaydi: `{ target: ["a", "b"] }` → `where: { a_b: { a, b } }`. `@@unique(name: "...")` bilan nomlangan constraint uchun xom client'dan foydalaning.
|
|
318
|
+
>
|
|
319
|
+
> **Bulk.** Prisma'da bulk upsert yo'q — `upsertMany` ketma-ket `upsert` qiladi (kirish tartibi saqlanadi). Katta importlar uchun `registry.transaction()` ichida chaqiring.
|
|
320
|
+
|
|
321
|
+
## Cheklovlar
|
|
322
|
+
|
|
323
|
+
- `updateById` / `deleteById` / `softDelete` / `restore` — **bitta maydonli `@id`** talab qiladi. Prisma `update`/`delete` faqat unique `where` qabul qiladi, querykit predikati esa scope va soft-delete guard'ini ham olib yuradi. Kompozit `@@id` bo'lsa `updateWhere`/`deleteWhere` ishlating.
|
|
324
|
+
- Filter/sort kalitlari — **Prisma maydon nomi** (`createdAt`), DB ustun nomi (`created_at`) emas. Prisma `where`ning o'zi ham `@map`langan nomni qabul qilmaydi.
|
|
325
|
+
- Matn operatorlari faqat `String` maydonlarda; boshqa tipda shart tashlanadi va reportlanadi.
|
|
326
|
+
|
|
327
|
+
To'liq ro'yxat va uchala adapter solishtiruvi: [PARITY.md](./PARITY.md).
|
|
328
|
+
|
|
329
|
+
## API ma'lumotnoma
|
|
330
|
+
|
|
331
|
+
| Metod | Tavsif |
|
|
332
|
+
| -------------------------------------------- | ------------------------------------- |
|
|
333
|
+
| `findAll(params?)` | Barcha mos qatorlar |
|
|
334
|
+
| `findOne(params?)` / `findById(id, params?)` | Bitta qator yoki `undefined` |
|
|
335
|
+
| `findList(params?)` | Offset paginatsiya — `{ data, meta }` |
|
|
336
|
+
| `findInfinite(params?)` | Infinite-scroll — `{ data, meta }` |
|
|
337
|
+
| `findCursor(params?)` | Cursor/keyset — `{ data, meta }` |
|
|
338
|
+
| `count(filter?)` / `exists(filter?)` | Soni / mavjudligi |
|
|
339
|
+
| `create` / `createMany` | Yaratish |
|
|
340
|
+
| `upsert` / `upsertMany` | Insert yoki conflict'da update |
|
|
341
|
+
| `updateById` / `updateWhere` | Yangilash (+ `updatedAt` bump) |
|
|
342
|
+
| `deleteById` / `deleteWhere` | Butunlay o'chirish |
|
|
343
|
+
| `softDelete` / `restore` | Yumshoq o'chirish / tiklash |
|
|
344
|
+
| `aggregate(spec)` | count/sum/avg/min/max + groupBy |
|
|
345
|
+
| `scoped(scope)` | Doimiy scope bilan yangi repository |
|
|
346
|
+
|
|
347
|
+
`createRegistry(prisma, options?)` — `defaultPerPage`, `defaultLimit`, `maxPerPage`, `maxLimit`, `strict`, `onSkippedCondition`.
|
|
348
|
+
|
|
349
|
+
## Ishlab chiqish
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
bun run build
|
|
353
|
+
bun run typecheck
|
|
354
|
+
bun run test:generate # fixture Prisma client (DB kerak emas)
|
|
355
|
+
bun run test:query # kompilyator + parity testlari (DB kerak emas)
|
|
356
|
+
bun run test:types # tip inference testi
|
|
357
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/db bun run test:smoke
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Litsenziya
|
|
361
|
+
|
|
362
|
+
MIT
|