@prisma/orm-mongo 8.0.0-rc.17-dev.2 → 8.0.0-rc.17-dev.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/orm-mongo",
3
- "version": "8.0.0-rc.17-dev.2",
3
+ "version": "8.0.0-rc.17-dev.4",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -10,18 +10,18 @@
10
10
  "skills"
11
11
  ],
12
12
  "dependencies": {
13
- "@prisma/orm-family-mongo": "8.0.0-rc.17-dev.2",
14
- "@prisma/orm-framework": "8.0.0-rc.17-dev.2",
15
- "@prisma/orm-target-mongo": "8.0.0-rc.17-dev.2",
16
- "@prisma/orm-toolchain": "8.0.0-rc.17-dev.2",
13
+ "@prisma/orm-family-mongo": "8.0.0-rc.17-dev.4",
14
+ "@prisma/orm-framework": "8.0.0-rc.17-dev.4",
15
+ "@prisma/orm-target-mongo": "8.0.0-rc.17-dev.4",
16
+ "@prisma/orm-toolchain": "8.0.0-rc.17-dev.4",
17
17
  "mongodb-connection-string-url": "^7.0.1",
18
18
  "pathe": "^2.0.3",
19
19
  "tinyglobby": "^0.2.17"
20
20
  },
21
21
  "devDependencies": {
22
- "@internal/mongo": "8.0.0-rc.17-dev.2",
23
- "@repo/tsconfig": "8.0.0-rc.17-dev.2",
24
- "@repo/tsdown": "8.0.0-rc.17-dev.2",
22
+ "@internal/mongo": "8.0.0-rc.17-dev.4",
23
+ "@repo/tsconfig": "8.0.0-rc.17-dev.4",
24
+ "@repo/tsdown": "8.0.0-rc.17-dev.4",
25
25
  "tsdown": "0.22.14",
26
26
  "typescript": "5.9.3"
27
27
  },
@@ -15,7 +15,7 @@ description: >-
15
15
  ORM 7 or earlier (schema.prisma + @prisma/client).
16
16
  metadata:
17
17
  library: '@prisma/orm-mongo'
18
- library_version: '8.0.0-rc.17-dev.2'
18
+ library_version: '8.0.0-rc.17-dev.4'
19
19
  version: '2026-09-12'
20
20
  ---
21
21
 
@@ -127,15 +127,20 @@ Then run `pnpm prisma contract emit` (or rely on the Vite plugin — see `refere
127
127
 
128
128
  `name:` declares a wire-named index (physical name `<name>_<8-hex hash>`, renames plan as `ALTER INDEX … RENAME`); `map:` adopts an exact physical name verbatim (for infer-captured objects — combining it with a SQL body warns, because drift detection byte-compares the authored text against Postgres's reprint). An `expression:` requires `name:` or `map:`. The TS builder mirrors this via `constraints.index([cols.x], {...})` / `constraints.index({ expression, ... })` — see `packages/2-sql/2-authoring/contract-ts/README.md`.
129
129
 
130
- **Full-text search indexes (PostgreSQL).** Do not hand-write the `to_tsvector` expression — Postgres only uses the index when it is the same `to_tsvector` over the same configuration literal and the same column as the query. Declare `@@fullTextIndex`, which renders the same expression `fullTextMatches` / `fullTextRank` / `fullTextHeadline` lower to (see `references/queries-postgres.md`):
130
+ **Full-text search indexes (PostgreSQL).** Do not hand-write the `to_tsvector` expression — Postgres only uses the index when the query carries the same search document: the same columns, configuration and weights. Declare `@@fullTextIndex`, and the index and the queries `fullTextMatches` / `fullTextRank` / `fullTextHeadline` build are rendered from the same definition (see `references/queries-postgres.md`):
131
131
 
132
132
  ```prisma
133
133
  @@fullTextIndex([text], name: "message_text_search")
134
134
  @@fullTextIndex([summary], language: "german", name: "message_summary_search_de")
135
135
  @@fullTextIndex([text], where: "archived_at IS NULL", name: "message_text_search_live")
136
+ @@fullTextIndex([[title, subtitle], body], name: "post_search")
136
137
  ```
137
138
 
138
- The TS builder has the same helper: `fullTextIndex(cols.text, { name: 'message_text_search' })`, from `@prisma/orm-postgres/contract-builder`, inside the model's `sql({ indexes: [...] })`. It takes exactly one field, an optional `language` (default `english`, from the same allowlist the operations accept), an optional `where:` for a partial index, and `name:` xor `map:` like any expression index. It is repeatable, so a model may index several columns. Give the index and the operation the same `language` — a mismatch is silent, costing the index and falling back to a sequential scan. The parser that builds the query (`websearchToTsquery` and the others) takes its own `language` for the query side; it does not affect index use. It lowers to a GIN index over `to_tsvector('<language>', "<column>")` — the column name resolved through `@map` — so `@@index(expression: …)` remains only for expressions this attribute does not cover.
139
+ The fields are one field, or a list whose items are fields or lists of fields. Each top-level item is a weight group, strongest first (`A` to `D`, so at most four groups); fields in a nested list share a weight. `[title, body]` is two groups, `[[title, body]]` one. A single field has no weight. Diagnostics refuse more than four groups, an empty group, a field named twice, an unknown field and a field that is not text.
140
+
141
+ The TS builder has the same helper: `fullTextIndex(cols.text, { name: 'message_text_search' })` or `fullTextIndex([[cols.title, cols.subtitle], cols.body], { name: 'post_search' })`, from `@prisma/orm-postgres/contract-builder`, inside the model's `sql({ indexes: [...] })`. Both take an optional `language` (default `english`, from the same allowlist the operations accept), an optional `where:` for a partial index, and `name:` xor `map:`. Both are repeatable. In the SQL builder, pass the index itself, `table.indexes.<name>`, to `fullTextMatches` and `fullTextRank`, which then search its document with its groups and language. A column or a `fullTextDocument` given other groups or another `language` than the index is silent, costing the index and falling back to a sequential scan. The parser that builds the query (`websearchToTsquery` and the others) takes its own `language` for the query side; it does not affect index use. With `map:` the index keeps the exact database name you give it, and `db verify` compares the rendered search document with the text Postgres prints back for the index exactly, character for character. Postgres prints it in its own form (`to_tsvector('english'::regconfig, title)`), so a `map:` full-text index reports drift; `@@fullTextIndex` and `fullTextIndex` warn about this with `PN_EXACT_NAME_BODY_COMPARISON`. Use `name:` unless the database already has the index under that name.
142
+
143
+ The contract stores the index as data, not SQL: an index of type `fullText` whose `columns` are the covered storage columns and whose `options` are `{ weightGroups, language }`, the weight groups as storage column names. The DDL is a GIN index over the rendered document, `to_tsvector('<language>', "<column>")` for one field, and `setweight(...) || ...` with every column wrapped in `coalesce` for several. `@@index(expression: …)` remains only for expressions this attribute does not cover.
139
144
 
140
145
  PSL alias surface for repeated types lives in a top-level `types {}` block:
141
146
 
@@ -100,7 +100,7 @@ const snippets = db.sql.public.message
100
100
  .build();
101
101
  ```
102
102
 
103
- Without an index Postgres recomputes `to_tsvector` for every row, and it only uses one whose expression is the same `to_tsvector` over the same configuration literal and the same column. `@@fullTextIndex` renders that expression for you — pass it the field and, if you use one, the same language:
103
+ Without an index Postgres recomputes `to_tsvector` for every row, and it only uses one whose expression is the same `to_tsvector` over the same configuration literal and the same column. `@@fullTextIndex` declares that index for you — pass it the field and, if you use one, the same language:
104
104
 
105
105
  ```prisma
106
106
  @@fullTextIndex([text], name: "message_text_search")
@@ -117,6 +117,28 @@ model('Message', { fields: { id, text } }).sql(({ cols }) => ({
117
117
  }));
118
118
  ```
119
119
 
120
+ **Searching several columns with weights.** One index can cover several columns. Each top-level item of the list is a weight group, strongest first; fields in a nested list share a weight:
121
+
122
+ ```prisma
123
+ @@fullTextIndex([[title, subtitle], body], name: "post_search")
124
+ ```
125
+
126
+ **Searching an index from the SQL builder.** A table's `indexes` holds each of its indexes under the name the contract gave it, `name:` or `map:`. Pass the full-text index to `fns.fullTextMatches` and `fns.fullTextRank` in place of a column: the operation searches the document the index was built over, with the index's weight groups and language, so the query always matches the index and a title match ranks above a body match. The index states its language, so passing `language` with one is a type error. An index of another type is a type error too.
127
+
128
+ ```typescript
129
+ const q = websearchToTsquery(query);
130
+ const post = db.sql.public.post;
131
+ const posts = post
132
+ .select('id', 'title')
133
+ .where((_f, fns) => fns.fullTextMatches(post.indexes.post_search, q))
134
+ .orderBy((_f, fns) => fns.fullTextRank(post.indexes.post_search, q), { direction: 'desc' })
135
+ .build();
136
+ ```
137
+
138
+ An index read from an aliased table searches that alias's columns, so `post.as('p').indexes.post_search` works in a self-join.
139
+
140
+ To search several columns that no index covers, build the document with `fullTextDocument` from `@prisma/orm-postgres/target/full-text`, which takes the weight groups of columns, and pass the language in the options: `fns.fullTextMatches(fullTextDocument([[f.title, f.subtitle], [f.body]]), q, { language: 'english' })`. Postgres uses an index only for a query over the same document, so a document with another grouping, order or language than an index runs without it, and raises no error. `fullTextHeadline` stays per column. One column, `fns.fullTextMatches(f.title, q)` or `row.title.fullTextMatches(q)`, searches that column and uses a single-field index with the same language.
141
+
120
142
  **There is no `.between(a, b)` operator.** Express ranges either as two chained `.where(...)` clauses (the idiomatic form — clauses AND-compose) or with the `and(...)` combinator inside one clause:
121
143
 
122
144
  ```typescript
@@ -76,7 +76,7 @@ for await (const user of db.orm.public.User.select('id', 'email').all()) {
76
76
  }
77
77
  ```
78
78
 
79
- Two single-row shortcuts also exist on the result, in addition to the collection-level `.first()` (which issues `LIMIT 1` on Postgres):
79
+ Two single-row shortcuts also exist on the result, in addition to the collection-level `.first()` and `.firstOrThrow()` (which issue `LIMIT 1` on Postgres):
80
80
 
81
81
  ```typescript
82
82
  const user = await db.orm.public.User.where({ id }).all().first();
@@ -85,7 +85,7 @@ const required = await db.orm.public.User.where({ id }).all().firstOrThrow();
85
85
  // ^? Row ← buffers; throws `RUNTIME.NO_ROWS` if empty.
86
86
  ```
87
87
 
88
- For genuine single-row reads, prefer the *collection*-level `.first()` (which adds `LIMIT 1` to the SQL on Postgres) over `.all().first()` (which fetches all rows and discards the rest). The result-level helpers are for cases where you already need the full result and want the first row without an extra round-trip.
88
+ For genuine single-row reads, prefer the *collection*-level `.first()` or `.firstOrThrow()` (which add `LIMIT 1` to the SQL on Postgres) over `.all().first()` / `.all().firstOrThrow()` (which fetch all rows and discard the rest). Collection-level `.firstOrThrow()` runs the same query as `.first()`, returns the row without `null`, and rejects with `RUNTIME.NO_ROWS` when nothing matches: `await db.orm.public.User.firstOrThrow({ id })` on Postgres, `await db.orm.users.where({ email }).firstOrThrow()` on Mongo (no arguments there). The result-level helpers are for cases where you already need the full result and want the first row without an extra round-trip.
89
89
 
90
90
  **The result is single-consumption.** Each `AsyncIterableResult` instance can be consumed once — by `await`, by `.toArray()`, or by `for await`. Trying to consume it a second time throws **`RUNTIME.ITERATOR_CONSUMED`**. The fix is almost always to store the array in a variable on first consumption and reuse the variable:
91
91