query-builder-prisma 0.0.0-stage → 0.2.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.md +254 -2
- package/dist/index.cjs +636 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +481 -0
- package/dist/index.d.ts +481 -0
- package/dist/index.js +601 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mamun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,255 @@
|
|
|
1
|
-
#
|
|
1
|
+
# query-builder-prisma
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/query-builder-prisma)
|
|
4
|
+
[](https://github.com/almamun2b/query-builder-prisma/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/query-builder-prisma)
|
|
7
|
+
|
|
8
|
+
> Zero-coupling, fluent, fully type-safe query builder for any Prisma model
|
|
9
|
+
> delegate. Works with **every Prisma version — 5, 6, 7, 8 and future majors by
|
|
10
|
+
> design** — and every database Prisma supports.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const { meta, data } = await new QueryBuilder(prisma.user)
|
|
14
|
+
.search({ searchText: q, fields: ['email', 'username'] }) // dialect-aware
|
|
15
|
+
.filter({ role, status }) // undefined values stripped
|
|
16
|
+
.range({ createdAt: { from, to }, 'avatar.size': { from: min } })
|
|
17
|
+
.sortBy({ sortBy: 'createdAt', sortOrder: 'desc' })
|
|
18
|
+
.paginate({ page: 1, limit: 20 }) // or .cursorPaginate({...})
|
|
19
|
+
.select({ id: true, email: true }) // output type narrows structurally
|
|
20
|
+
.executeWithMeta();
|
|
21
|
+
// meta: { page: 1, limit: 20, total: 142, totalPage: 8 }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Why
|
|
25
|
+
|
|
26
|
+
An npm package that imports Prisma types breaks on Prisma's next major — import
|
|
27
|
+
paths changed in v7, the type utilities move in v8, the engine was rewritten.
|
|
28
|
+
This package speaks only Prisma's stable public contract, the **model-delegate
|
|
29
|
+
call shape**:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
model.findMany({ where, orderBy, skip, take, select, include, cursor, distinct })
|
|
33
|
+
model.count({ where })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It imports **nothing** from Prisma — no runtime imports, no type imports, no
|
|
37
|
+
peer dependencies. All types are derived structurally from the delegate you
|
|
38
|
+
pass in, so a Prisma upgrade can never break it. The test suite runs with zero
|
|
39
|
+
Prisma installed; that is the standing proof.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install query-builder-prisma
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Published on npm as [`query-builder-prisma`](https://www.npmjs.com/package/query-builder-prisma).
|
|
48
|
+
|
|
49
|
+
Node ≥ 18; TypeScript ≥ 5 for inference (plain JS works). Dual ESM + CJS; zero
|
|
50
|
+
runtime dependencies.
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
| | |
|
|
55
|
+
| -------------------------------------------------------------------- | -------------------------------------------------- |
|
|
56
|
+
| [Getting started](./docs/getting-started.md) | install, first query, imports |
|
|
57
|
+
| [Guides](./docs/index.md#guides) | filtering, sorting, both pagination modes, projections, dialects, advanced patterns |
|
|
58
|
+
| [API reference](./docs/api/query-builder.md) | every method, type and function |
|
|
59
|
+
| [Use cases](./docs/examples/use-cases.md) | end-to-end scenarios |
|
|
60
|
+
| [Framework recipes](./docs/framework-recipes.md) | Next.js, NestJS, Express, edge runtimes |
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- **Fluent chain** — `search`, `filter`, `range`, `where`, `sortBy`/`orderBy`,
|
|
65
|
+
`paginate`/`cursorPaginate`, `select`/`include`, `custom`, folded into one
|
|
66
|
+
`findMany` call.
|
|
67
|
+
- **Offset pagination with metadata** — `executeWithMeta()` returns
|
|
68
|
+
`page`/`limit`/`total`/`totalPage`, with `COUNT` + `findMany` in parallel and
|
|
69
|
+
an opt-in fast path (`includeTotal: false`) that skips the `COUNT`.
|
|
70
|
+
- **Cursor (seek) pagination** — stable deep pages for feeds and infinite
|
|
71
|
+
scroll, forward/backward walking, ISO-8601-safe `Date` cursors, exact flags
|
|
72
|
+
via `strict: true`.
|
|
73
|
+
- **Structural type projection** — `.select()`/`.include()` narrow result types
|
|
74
|
+
like Prisma's own overloads, nested includes included, without Prisma types.
|
|
75
|
+
- **Dialect capability registry** — `mode: 'insensitive'` only where Prisma
|
|
76
|
+
accepts it; new databases are one `registerDialect()` call.
|
|
77
|
+
- **Hardened by default** — `undefined` stripping at any depth, prototype-key
|
|
78
|
+
refusal, clamped (or thrown) pagination numbers, a seed `where` that cannot
|
|
79
|
+
be replaced.
|
|
80
|
+
|
|
81
|
+
### Offsetting and counting
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const { meta, data } = await new QueryBuilder(prisma.user)
|
|
85
|
+
.filter({ status: 'ACTIVE' })
|
|
86
|
+
.paginate({ page: 2, limit: 20 })
|
|
87
|
+
.executeWithMeta(); // meta: { page: 2, limit: 20, total, totalPage }
|
|
88
|
+
|
|
89
|
+
// Skip the COUNT query on large tables:
|
|
90
|
+
await qb.executeWithMeta({ includeTotal: false }); // total: null, totalPage: null
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Cursor pagination
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const page = await new QueryBuilder(prisma.post)
|
|
97
|
+
.sortBy({ sortBy: 'createdAt', sortOrder: 'desc' })
|
|
98
|
+
.cursorPaginate({ cursor: prev?.nextCursor, cursorField: 'createdAt', limit: 10 })
|
|
99
|
+
.executeWithCursor();
|
|
100
|
+
// meta: { limit, nextCursor, prevCursor, hasNextPage, hasPrevPage }
|
|
101
|
+
// rows always in configured sort order, whatever the walking direction
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Type-safe projections
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const users = await new QueryBuilder(prisma.user)
|
|
108
|
+
.select({ id: true, posts: { select: { title: true } } })
|
|
109
|
+
.execute();
|
|
110
|
+
// users: { id: string; posts: { title: string }[] }[] — no Prisma types involved
|
|
111
|
+
|
|
112
|
+
// 100% Prisma-native inference whenever you want it:
|
|
113
|
+
await prisma.user.findMany(qb.buildArgs());
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Dialects
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { configQueryBuilder } from 'query-builder-prisma';
|
|
120
|
+
|
|
121
|
+
configQueryBuilder({ dialect: 'postgresql' }); // global default
|
|
122
|
+
new QueryBuilder(prisma.user, { dialect: 'mysql' }); // per-builder override (recommended)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`search()` emits `mode: 'insensitive'` only on PostgreSQL / CockroachDB /
|
|
126
|
+
MongoDB (it throws on MySQL, SQLite, SQL Server); elsewhere the database's
|
|
127
|
+
collation or Prisma's limits apply. Unknown dialect names resolve to
|
|
128
|
+
`'universal'` — arguments valid on every database.
|
|
129
|
+
|
|
130
|
+
## Behavior contracts
|
|
131
|
+
|
|
132
|
+
Semantics you can build on (the tests pin these):
|
|
133
|
+
|
|
134
|
+
- **Precedence** — explicit methods always win over `custom()` args, regardless of
|
|
135
|
+
call order: `.select(X)` beats `custom({ select: Y })`;
|
|
136
|
+
`.paginate(...)`/`.cursorPaginate(...)` beat `custom({ take })`;
|
|
137
|
+
`.sortBy()`/`.orderBy()` beat `custom({ orderBy })`.
|
|
138
|
+
- **`where` assembly** — conditions from `.search/.filter/.range/.where` are
|
|
139
|
+
AND-ed, a seeded `initialArgs.where` is AND-ed in front of them, and
|
|
140
|
+
`.custom({ where })` is AND-ed with the seed and previous custom `where`s —
|
|
141
|
+
it never replaces them.
|
|
142
|
+
- **`custom()` strips `undefined`** — at any depth, so
|
|
143
|
+
`custom({ distinct: undefined })` cannot overwrite an earlier value. Keys
|
|
144
|
+
named `__proto__`, `constructor` or `prototype` are refused (never written)
|
|
145
|
+
in `custom()`, `filter()` and dot-path expansion.
|
|
146
|
+
- **Sorting** — `.sortBy()` is single-key sugar and expands dot paths like
|
|
147
|
+
`.range()` (`'avatar.size'` → `{ avatar: { size: 'desc' } }`); `.orderBy()`
|
|
148
|
+
takes the full Prisma shape verbatim (arrays for multi-key, nested relation
|
|
149
|
+
orders). The latest of the two calls wins.
|
|
150
|
+
- **Cursor beats offset** — when both `.paginate()` and `.cursorPaginate()` are
|
|
151
|
+
configured, cursor mode wins. `executeWithCursor()` always returns rows in the
|
|
152
|
+
configured sort order, regardless of `direction`.
|
|
153
|
+
- **`executeWithMeta()` is offset-mode only** — it throws if `.cursorPaginate()`
|
|
154
|
+
was configured (offset meta cannot describe a seek query); use
|
|
155
|
+
`.executeWithCursor()` in cursor mode.
|
|
156
|
+
- **Cursor serialization** — `cursorPaginate({ cursor })` accepts a string,
|
|
157
|
+
number or `Date`; `Date` values (and `Date` cursor fields) serialize to
|
|
158
|
+
ISO-8601, so cursors are round-trippable and locale/TZ-independent.
|
|
159
|
+
- **Cursor flags are optimistic by default** — `hasNextPage`/`hasPrevPage` come
|
|
160
|
+
from a one-row over-fetch and can rarely claim a page exists that turns out
|
|
161
|
+
empty. `cursorPaginate({ strict: true })` spends one extra 1-row probe query
|
|
162
|
+
to make both flags exact.
|
|
163
|
+
- **Cursor projection guard** — `executeWithCursor()` throws when a `.select()`
|
|
164
|
+
projection drops `cursorField` (cursors would be built from `undefined`).
|
|
165
|
+
- **Clamping** — `limit` is clamped silently to `[1, 1000]`; pages below 1
|
|
166
|
+
become page 1; computed `skip` is capped at `100_000` (deep access → cursor
|
|
167
|
+
pagination). With `new QueryBuilder(delegate, { strictNumbers: true })` all of
|
|
168
|
+
these throw instead of clamping.
|
|
169
|
+
- **`includeTotal`** — `executeWithMeta({ includeTotal: false })` skips the
|
|
170
|
+
`COUNT` query; `meta` then carries `total: null` / `totalPage: null`.
|
|
171
|
+
- **Defaults** — `executeWithMeta()` without pagination defaults to page 1,
|
|
172
|
+
limit 10 so `meta` always describes the returned data.
|
|
173
|
+
- **`.first()`** — forces `take: 1` and drops `skip`/`cursor`: it returns the
|
|
174
|
+
first row of the whole result set, not of a configured page.
|
|
175
|
+
- **`search()`** — pass scalar string fields only (relation keys compile but
|
|
176
|
+
throw at Prisma runtime); empty or whitespace-only `searchText` adds no
|
|
177
|
+
condition; each `OR` arm gets its own match object.
|
|
178
|
+
- **`range()` reaches to-one paths only** — dot paths expand for to-one
|
|
179
|
+
relations and nested objects; filtering a to-many relation needs
|
|
180
|
+
`.custom({ where: { posts: { some: { ... } } } })`.
|
|
181
|
+
- **`_count` is typed wide** — `include({ _count: true })` widens the result
|
|
182
|
+
type with `_count: Record<string, number>` (present, correct keys are unknown
|
|
183
|
+
statically); the `prisma.user.findMany(qb.buildArgs())` passthrough recovers
|
|
184
|
+
the exact per-relation shape.
|
|
185
|
+
- **`cursorField`** — must be unique and sequentially ordered (pair it with a
|
|
186
|
+
matching `.sortBy()`); duplicates paginate wrong silently.
|
|
187
|
+
- **One builder per query** — instances are cheap; never share one across
|
|
188
|
+
concurrent requests. Framework recipes: [docs/framework-recipes.md](./docs/framework-recipes.md).
|
|
189
|
+
- **Dialect config** — global config state is shared via `globalThis`, so ESM+CJS
|
|
190
|
+
dual loading sees one config; the per-builder `dialect` option remains the
|
|
191
|
+
recommended path, and unknown names resolve to `'universal'` (name included).
|
|
192
|
+
|
|
193
|
+
The former HIGH-severity footguns (seed-`where` replacement, `executeWithMeta()`
|
|
194
|
+
under cursor mode, nested-include type narrowing) are fixed and
|
|
195
|
+
regression-tested. Remaining limitations are documented below.
|
|
196
|
+
|
|
197
|
+
## Known limitations
|
|
198
|
+
|
|
199
|
+
Open and documented rather than hidden — none produces a silently wrong result:
|
|
200
|
+
|
|
201
|
+
- **Plain `.execute()` after `.cursorPaginate()`** returns raw seek-order rows
|
|
202
|
+
including the one-row over-fetch — use `.executeWithCursor()` for trimmed,
|
|
203
|
+
sort-ordered pages (kept as raw-passthrough semantics by design; the
|
|
204
|
+
`buildArgs()` passthrough is always available).
|
|
205
|
+
|
|
206
|
+
## FAQ
|
|
207
|
+
|
|
208
|
+
**Does this need Prisma installed?**
|
|
209
|
+
The package never imports Prisma, but of course you query *through* your
|
|
210
|
+
Prisma delegates — you pass them in. Any object with the `findMany` + `count`
|
|
211
|
+
call shape works, which is also how the test suite proves the decoupling.
|
|
212
|
+
|
|
213
|
+
**Which Prisma versions are supported?**
|
|
214
|
+
Every version whose model delegates keep the standard call shape — 5, 6, 7, 8
|
|
215
|
+
today, future majors by design. There is nothing to update on a Prisma
|
|
216
|
+
upgrade.
|
|
217
|
+
|
|
218
|
+
**Can I share one builder across requests?**
|
|
219
|
+
No — one builder per query. Instances are cheap; a shared instance mixes
|
|
220
|
+
conditions between concurrent requests. See
|
|
221
|
+
[Advanced patterns](./docs/guides/advanced.md#one-builder-per-query) and the
|
|
222
|
+
[framework recipes](./docs/framework-recipes.md).
|
|
223
|
+
|
|
224
|
+
**What if a type isn't narrow enough?**
|
|
225
|
+
The engine degrades wider, never wrong (today only `_count`). For exact
|
|
226
|
+
Prisma-native inference on any query, use the passthrough:
|
|
227
|
+
`prisma.user.findMany(qb.buildArgs())`.
|
|
228
|
+
|
|
229
|
+
**How do I add a database Prisma adds later?**
|
|
230
|
+
One `registerDialect(name, capabilities)` call — no core changes. See
|
|
231
|
+
[Dialects](./docs/guides/dialects.md).
|
|
232
|
+
|
|
233
|
+
## Development
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
npm run verify # lint + typecheck + test + build (the bar for every change)
|
|
237
|
+
npm test # unit tests — fake delegates, zero Prisma required
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- `src/core` — builder, condition assembly, pagination math
|
|
241
|
+
- `src/dialects` — capability registry, per-DB capabilities, auto-detect hook
|
|
242
|
+
- `src/types` — structural delegate contracts + projection type engine
|
|
243
|
+
- `test/types` — compile-time projection tests (run by `npm run typecheck`)
|
|
244
|
+
- `builder-old/` — the original prototype this package was extracted from (reference only)
|
|
245
|
+
|
|
246
|
+
### Links
|
|
247
|
+
|
|
248
|
+
- [ARCHITECTURE.md](./ARCHITECTURE.md) — design, boundaries, invariants
|
|
249
|
+
- [docs/index.md](./docs/index.md) — full documentation hub
|
|
250
|
+
- [CONTRIBUTING.md](./CONTRIBUTING.md) — how to contribute
|
|
251
|
+
- [SECURITY.md](./SECURITY.md) — vulnerability reporting
|
|
252
|
+
- [CHANGELOG.md](./CHANGELOG.md) — release history
|
|
253
|
+
|
|
254
|
+
> Published on npm as [`query-builder-prisma`](https://www.npmjs.com/package/query-builder-prisma).
|
|
255
|
+
> MIT licensed.
|