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 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
- # Temporary Holding Version
1
+ # query-builder-prisma
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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`.