query-builder-prisma 0.0.0-stage → 0.1.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 +154 -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,155 @@
|
|
|
1
|
-
#
|
|
1
|
+
# query-builder-prisma
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Zero-coupling, fluent, fully type-safe query builder for any Prisma model delegate.
|
|
4
|
+
> **Status: v0.1.0 — core API working.** Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
5
|
+
> · Contributing: [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
6
|
+
|
|
7
|
+
Works with **every Prisma version — 5, 6, 7, 8 and future majors by design** — and every
|
|
8
|
+
database Prisma supports (PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, CockroachDB,
|
|
9
|
+
MongoDB), because it imports **nothing** from Prisma: all types are derived structurally
|
|
10
|
+
from the delegate you pass in.
|
|
11
|
+
|
|
12
|
+
## The idea
|
|
13
|
+
|
|
14
|
+
An npm package that imports Prisma types will break on Prisma's next major (import paths
|
|
15
|
+
changed in v7, the type utilities move in v8, the engine was rewritten...). This package
|
|
16
|
+
speaks only Prisma's stable public contract — the model-delegate call shape — so a Prisma
|
|
17
|
+
upgrade can never break it.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const { meta, data } = await new QueryBuilder(prisma.user)
|
|
21
|
+
.search({ searchText: q, fields: ['email', 'username'] }) // dialect-aware: no `mode` on MySQL/SQLite/SQLServer
|
|
22
|
+
.filter({ role, status }) // undefined values stripped
|
|
23
|
+
.range({ createdAt: { from, to }, 'avatar.size': { from: min } })
|
|
24
|
+
.sortBy({ sortBy: 'createdAt', sortOrder: 'desc' })
|
|
25
|
+
.paginate({ page: 1, limit: 20 }) // or .cursorPaginate({...})
|
|
26
|
+
.select({ id: true, email: true }) // output type narrows structurally
|
|
27
|
+
.executeWithMeta();
|
|
28
|
+
// meta: { page: 1, limit: 20, total: 142, totalPage: 8 }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Design rules (enforced in review — [ARCHITECTURE.md](./ARCHITECTURE.md) invariants)
|
|
32
|
+
|
|
33
|
+
1. **Zero Prisma coupling** — no runtime or type imports from Prisma; no peerDependencies.
|
|
34
|
+
2. **Everything is structural** — only requires that a "model" has `findMany` + `count`
|
|
35
|
+
with the standard call shape.
|
|
36
|
+
3. **Types degrade gracefully, never lie** — `select`/`include` narrowing is implemented
|
|
37
|
+
by our own structural projection engine; for 100% Prisma-native inference use the
|
|
38
|
+
passthrough `prisma.user.findMany(qb.buildArgs())`.
|
|
39
|
+
4. **Dialects are a capability registry** — `mode: 'insensitive'` is only emitted for
|
|
40
|
+
PostgreSQL/CockroachDB/MongoDB (it throws on MySQL/SQLite/SQL Server). New/future
|
|
41
|
+
databases need one `registerDialect()` call, no core changes.
|
|
42
|
+
|
|
43
|
+
## Dialect configuration
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { configureQueryBuilder } from 'query-builder-prisma';
|
|
47
|
+
|
|
48
|
+
configureQueryBuilder({ dialect: 'postgres' }); // global default
|
|
49
|
+
new QueryBuilder(prisma.user, { dialect: 'mysql' }); // per-builder override (recommended)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The global config state is shared through `globalThis` under a registered symbol, so
|
|
53
|
+
bundler graphs that load this package as both ESM and CJS still see ONE config.
|
|
54
|
+
The per-builder option stays the recommended path; unknown dialect names resolve
|
|
55
|
+
to `'universal'` (reported name included — the name never lies about the applied
|
|
56
|
+
capabilities).
|
|
57
|
+
|
|
58
|
+
## Behavior contracts
|
|
59
|
+
|
|
60
|
+
Semantics you can build on (the tests pin these):
|
|
61
|
+
|
|
62
|
+
- **Precedence** — explicit methods always win over `custom()` args, regardless of
|
|
63
|
+
call order: `.select(X)` beats `custom({ select: Y })`;
|
|
64
|
+
`.paginate(...)`/`.cursorPaginate(...)` beat `custom({ take })`;
|
|
65
|
+
`.sortBy()`/`.orderBy()` beat `custom({ orderBy })`.
|
|
66
|
+
- **`where` assembly** — conditions from `.search/.filter/.range/.where` are
|
|
67
|
+
AND-ed, a seeded `initialArgs.where` is AND-ed in front of them, and
|
|
68
|
+
`.custom({ where })` is AND-ed with the seed and previous custom `where`s —
|
|
69
|
+
it never replaces them.
|
|
70
|
+
- **`custom()` strips `undefined`** — at any depth, so
|
|
71
|
+
`custom({ distinct: undefined })` cannot overwrite an earlier value. Keys
|
|
72
|
+
named `__proto__`, `constructor` or `prototype` are refused (never written)
|
|
73
|
+
in `custom()`, `filter()` and dot-path expansion.
|
|
74
|
+
- **Sorting** — `.sortBy()` is single-key sugar and expands dot paths like
|
|
75
|
+
`.range()` (`'avatar.size'` → `{ avatar: { size: 'desc' } }`); `.orderBy()`
|
|
76
|
+
takes the full Prisma shape verbatim (arrays for multi-key, nested relation
|
|
77
|
+
orders). The latest of the two calls wins.
|
|
78
|
+
- **Cursor beats offset** — when both `.paginate()` and `.cursorPaginate()` are
|
|
79
|
+
configured, cursor mode wins. `executeWithCursor()` always returns rows in the
|
|
80
|
+
configured sort order, regardless of `direction`.
|
|
81
|
+
- **`executeWithMeta()` is offset-mode only** — it throws if `.cursorPaginate()`
|
|
82
|
+
was configured (offset meta cannot describe a seek query); use
|
|
83
|
+
`.executeWithCursor()` in cursor mode.
|
|
84
|
+
- **Cursor serialization** — `cursorPaginate({ cursor })` accepts a string,
|
|
85
|
+
number or `Date`; `Date` values (and `Date` cursor fields) serialize to
|
|
86
|
+
ISO-8601, so cursors are round-trippable and locale/TZ-independent.
|
|
87
|
+
- **Cursor flags are optimistic by default** — `hasNextPage`/`hasPrevPage` come
|
|
88
|
+
from a one-row over-fetch and can rarely claim a page exists that turns out
|
|
89
|
+
empty. `cursorPaginate({ strict: true })` spends one extra 1-row probe query
|
|
90
|
+
to make both flags exact.
|
|
91
|
+
- **Cursor projection guard** — `executeWithCursor()` throws when a `.select()`
|
|
92
|
+
projection drops `cursorField` (cursors would be built from `undefined`).
|
|
93
|
+
- **Clamping** — `limit` is clamped silently to `[1, 1000]`; pages below 1
|
|
94
|
+
become page 1; computed `skip` is capped at `100_000` (deep access → cursor
|
|
95
|
+
pagination). With `new QueryBuilder(delegate, { strictNumbers: true })` all of
|
|
96
|
+
these throw instead of clamping.
|
|
97
|
+
- **`includeTotal`** — `executeWithMeta({ includeTotal: false })` skips the
|
|
98
|
+
`COUNT` query; `meta` then carries `total: null` / `totalPage: null`.
|
|
99
|
+
- **Defaults** — `executeWithMeta()` without pagination defaults to page 1,
|
|
100
|
+
limit 10 so `meta` always describes the returned data.
|
|
101
|
+
- **`.first()`** — forces `take: 1` and drops `skip`/`cursor`: it returns the
|
|
102
|
+
first row of the whole result set, not of a configured page.
|
|
103
|
+
- **`search()`** — pass scalar string fields only (relation keys compile but
|
|
104
|
+
throw at Prisma runtime); empty or whitespace-only `searchText` adds no
|
|
105
|
+
condition; each `OR` arm gets its own match object.
|
|
106
|
+
- **`range()` reaches to-one paths only** — dot paths expand for to-one
|
|
107
|
+
relations and nested objects; filtering a to-many relation needs
|
|
108
|
+
`.custom({ where: { posts: { some: { ... } } } })`.
|
|
109
|
+
- **`_count` is typed wide** — `include({ _count: true })` widens the result
|
|
110
|
+
type with `_count: Record<string, number>` (present, correct keys are unknown
|
|
111
|
+
statically); the `prisma.user.findMany(qb.buildArgs())` passthrough recovers
|
|
112
|
+
the exact per-relation shape.
|
|
113
|
+
- **`cursorField`** — must be unique and sequentially ordered (pair it with a
|
|
114
|
+
matching `.sortBy()`); duplicates paginate wrong silently.
|
|
115
|
+
- **One builder per query** — instances are cheap; never share one across
|
|
116
|
+
concurrent requests. Framework recipes: [docs/framework-recipes.md](./docs/framework-recipes.md).
|
|
117
|
+
- **Dialect config** — global config state is shared via `globalThis`, so ESM+CJS
|
|
118
|
+
dual loading sees one config; the per-builder `dialect` option remains the
|
|
119
|
+
recommended path, and unknown names resolve to `'universal'` (name included).
|
|
120
|
+
|
|
121
|
+
The former HIGH-severity footguns (seed-`where` replacement, `executeWithMeta()`
|
|
122
|
+
under cursor mode, nested-include type narrowing) are fixed and
|
|
123
|
+
regression-tested. Remaining limitations are documented below.
|
|
124
|
+
|
|
125
|
+
## Known limitations
|
|
126
|
+
|
|
127
|
+
Open and documented rather than hidden — none produces a silently wrong result:
|
|
128
|
+
|
|
129
|
+
- **Plain `.execute()` after `.cursorPaginate()`** returns raw seek-order rows
|
|
130
|
+
including the one-row over-fetch — use `.executeWithCursor()` for trimmed,
|
|
131
|
+
sort-ordered pages (kept as raw-passthrough semantics by design; the
|
|
132
|
+
`buildArgs()` passthrough is always available).
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm run verify # lint + typecheck + test + build
|
|
138
|
+
npm test # unit tests (fake delegates, zero Prisma required)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- `src/core` — builder, condition assembly, pagination math
|
|
142
|
+
- `src/dialects` — capability registry, per-DB capabilities, auto-detect hook
|
|
143
|
+
- `src/types` — structural delegate contracts + projection type engine
|
|
144
|
+
- `test/types` — compile-time projection tests (run by `npm run typecheck`)
|
|
145
|
+
- `builder-old/` — the original prototype this package was extracted from (reference only)
|
|
146
|
+
|
|
147
|
+
Agent tooling (review agents, official Prisma skills, MCP servers) is documented
|
|
148
|
+
in [CONTRIBUTING.md](./CONTRIBUTING.md) and [AGENTS.md](./AGENTS.md).
|
|
149
|
+
|
|
150
|
+
Docs: [ARCHITECTURE.md](./ARCHITECTURE.md) (design + boundaries),
|
|
151
|
+
[AGENTS.md](./AGENTS.md) (agent/contributor rules),
|
|
152
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md) (how to contribute),
|
|
153
|
+
[framework-recipes](./docs/framework-recipes.md) (Next.js / NestJS wiring).
|
|
154
|
+
|
|
155
|
+
> **Note:** published on npm as `query-builder-prisma`.
|