feat(extension-paradedb): query ops and demo#433
Conversation
📝 WalkthroughWalkthroughThis PR introduces a comprehensive ParadeDB full-text search integration for Prisma-Next: a new extension pack with proximity chain expression building and query operation types, a complete Docker-based demo example with BM25 indexing and CLI interface, and updates package entry points across the framework to use the ChangesParadeDB Extension: Type System and Core Builders
ParadeDB Extension: Descriptors and Configuration
ParadeDB Demo: Complete Example Application
Package Entry Point Modernization
Postgres Adapter Index Ordering Fix
🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/extension-cipherstash
@prisma-next/middleware-telemetry
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
6af7b34 to
e5643ee
Compare
8707445 to
36cbfdc
Compare
7022f98 to
b72e8ca
Compare
Adds the paradedb extension query plane on top of upstream bm25 index-type
registration:
- 11 query operations via two shared helpers — matchOp for the five
match-mode operators (paradeDbMatch @@@, paradeDbMatchAny |||,
paradeDbMatchAll &&&, paradeDbTerm ===, paradeDbPhrase ###) and
typmodCastOp for the four typmod casts (paradeDbFuzzy, paradeDbBoost,
paradeDbConst, paradeDbSlop). Cast args wrap a JS-side integer in
LiteralExpr.of(n) because PG rejects parameterized typmods; per-op
validators enforce the documented ranges.
- paradeDbScore (pdb.score(key)) and ParadeDbProximityChain — an
immutable builder with .within(distance, term, { ordered? }) for
arbitrary-length chains mixing ## and ##>. Distances render as
LiteralExpr (chained ##/##> only accept literal slop). The chain
implements Expression<text> so it composes directly through
paradeDbMatch via the @@@ overload.
- pg_search install via SqlControlExtensionDescriptor.databaseDependencies.
examples/paradedb-demo runs every operation end-to-end against a live
paradedb container. Contract authored with the new shape:
constraints.index([cols...], { type: "bm25", options: { key_field: "id" },
name: "item_bm25_idx" }). docker-compose.yaml + init script create a
dedicated demo database to sidestep the paradedb image preloaded PostGIS
tables in the default database. CLI commands: match, top, fuzzy,
proximity, proximity-chain, chain-demo, mode-tour (curated tour of the
five match modes against contrastive seed data), cast-demo.
Also fixes a pre-existing introspection bug in adapter-postgres: the
indexes query ordered columns by pg_attribute.attnum (table declaration
order) rather than by position within pg_index.indkey (the index key
order). Multi-column indexes whose declared column order differs from
the table column declaration order would spuriously fail schema
verification — including any contract whose columns get alphabetized
during canonicalization while an index declares its own ordering.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
`paradedbQueryOperations()` now returns `QueryOperationTypes<CT>` directly (post-#436 framework API), with per-op `impl` signatures inferred from the declared type instead of hand-annotated. The `matchOp` / `typmodCastOp` helpers and the per-entry `method` field are gone; `register(name, op)` gets the name from the record key. The dead `parameterizedCodecs` slot drops from the runtime descriptor too, since codecs are now a unified single-array contributor. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
b72e8ca to
c24bc63
Compare
Adds `orm-top <query> [limit]` command that uses `@prisma-next/sql-orm-client` to fetch BM25 matches ordered by paradedb score, mirroring the SQL-builder `top` query but going through the ORM lane. Includes an integration test that exercises the full path against the docker paradedb instance. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
examples/paradedb-demo/docker-compose.yaml (1)
3-3: ⚡ Quick winPin the ParadeDB image instead of
latest.Line [3] using
latestmakes the demo non-reproducible and can introduce surprise breakages over time. Prefer an immutable version tag (or digest).Proposed change
- image: paradedb/paradedb:latest + image: paradedb/paradedb:<pinned-version-or-digest>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/paradedb-demo/docker-compose.yaml` at line 3, Replace the floating image tag "image: paradedb/paradedb:latest" in the docker-compose.yaml with an immutable version (e.g., a specific semver tag or an image digest) so the demo is reproducible; update the "image: paradedb/paradedb:latest" entry to "image: paradedb/paradedb:<stable-version-or-digest>" and, if applicable, add a short comment noting the chosen pinned version.examples/paradedb-demo/src/orm-client/client.ts (1)
8-8: 💤 Low valueConsider improving type surface to avoid cast.
The cast to
ExecutionContext<Contract>is acceptable in example code, but ideallydb.contextwould be typed correctly at the source. If this pattern appears frequently, consider whether the framework's type exports could be refined to eliminate the need for the cast.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/paradedb-demo/src/orm-client/client.ts` at line 8, The code casts db.context to ExecutionContext<Contract>; instead of casting, make the DB/client instance carry the correct generic so db.context is already typed. Locate where the DB/client is created (the factory or constructor that returns db) and add/propagate the Contract generic so the returned type is Database<Contract> or Client<Contract>, which will make db.context have the type ExecutionContext<Contract> without a cast; alternatively, update the context property declaration in the client/DB type to be generic (ExecutionContext<T>) so callers like db.context are correctly typed.examples/paradedb-demo/src/main.ts (1)
55-123: ⚡ Quick winAvoid
process.exit()inside thetrycommand branch; prefer exit codes + return.Using
process.exit(1)in command branches makes cleanup flow less explicit. Setprocess.exitCodeandreturnsoLine 122cleanup is always reached through normal control flow.Suggested pattern
- process.exit(1); + process.exitCode = 1; + return; ... - process.exit(1); + process.exitCode = 1; + return;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/paradedb-demo/src/main.ts` around lines 55 - 123, Replace direct process.exit(1) calls in the command branches with setting process.exitCode = 1 and returning so the finally block (which calls await runtime.close()) always runs; update every branch that currently does process.exit(1) (e.g., the 'match', 'top', 'fuzzy', 'proximity', and 'orm-top' argument-check blocks) to set process.exitCode = 1 and return from the enclosing async function/handler instead of calling process.exit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/paradedb-demo/src/main.ts`:
- Around line 36-40: The parsing of proximity-chain distances uses
Number.parseInt(distStr, 10) which accepts partial numerics like "2abc"; change
the check to first validate distStr strictly (e.g., require it matches /^\d+$/
or use a parse+string comparison) before converting so only pure non-negative
integer strings are accepted; if the validation fails, throw the same Error
(referencing distRaw and the existing message), otherwise parse into distance
and proceed (update the code around distStr, distance, and the existing throw).
In `@examples/paradedb-demo/test/bm25.integration.test.ts`:
- Line 8: The SKIP gate currently only checks for undefined; change the logic
that sets the SKIP constant (SKIP) to treat empty strings and strings containing
only whitespace as missing too by trimming the value and checking for falsiness
(e.g., const val = process.env['DATABASE_URL']; const SKIP = !val || val.trim()
=== ''), so any empty/whitespace DATABASE_URL will cause tests to skip.
---
Nitpick comments:
In `@examples/paradedb-demo/docker-compose.yaml`:
- Line 3: Replace the floating image tag "image: paradedb/paradedb:latest" in
the docker-compose.yaml with an immutable version (e.g., a specific semver tag
or an image digest) so the demo is reproducible; update the "image:
paradedb/paradedb:latest" entry to "image:
paradedb/paradedb:<stable-version-or-digest>" and, if applicable, add a short
comment noting the chosen pinned version.
In `@examples/paradedb-demo/src/main.ts`:
- Around line 55-123: Replace direct process.exit(1) calls in the command
branches with setting process.exitCode = 1 and returning so the finally block
(which calls await runtime.close()) always runs; update every branch that
currently does process.exit(1) (e.g., the 'match', 'top', 'fuzzy', 'proximity',
and 'orm-top' argument-check blocks) to set process.exitCode = 1 and return from
the enclosing async function/handler instead of calling process.exit.
In `@examples/paradedb-demo/src/orm-client/client.ts`:
- Line 8: The code casts db.context to ExecutionContext<Contract>; instead of
casting, make the DB/client instance carry the correct generic so db.context is
already typed. Locate where the DB/client is created (the factory or constructor
that returns db) and add/propagate the Contract generic so the returned type is
Database<Contract> or Client<Contract>, which will make db.context have the type
ExecutionContext<Contract> without a cast; alternatively, update the context
property declaration in the client/DB type to be generic (ExecutionContext<T>)
so callers like db.context are correctly typed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: ea872488-9383-4a95-977b-7a1a704e536b
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (44)
examples/paradedb-demo/.env.exampleexamples/paradedb-demo/README.mdexamples/paradedb-demo/biome.jsoncexamples/paradedb-demo/docker-compose.yamlexamples/paradedb-demo/init/01-create-demo-db.sqlexamples/paradedb-demo/package.jsonexamples/paradedb-demo/prisma-next.config.tsexamples/paradedb-demo/prisma/contract.tsexamples/paradedb-demo/scripts/drop-db.tsexamples/paradedb-demo/scripts/seed.tsexamples/paradedb-demo/src/app-config.tsexamples/paradedb-demo/src/main.tsexamples/paradedb-demo/src/orm-client/bm25-top-matches.tsexamples/paradedb-demo/src/orm-client/client.tsexamples/paradedb-demo/src/orm-client/collections.tsexamples/paradedb-demo/src/prisma/contract.d.tsexamples/paradedb-demo/src/prisma/contract.jsonexamples/paradedb-demo/src/prisma/db.tsexamples/paradedb-demo/src/queries/bm25-cast-demo.tsexamples/paradedb-demo/src/queries/bm25-chain-demo.tsexamples/paradedb-demo/src/queries/bm25-fuzzy.tsexamples/paradedb-demo/src/queries/bm25-match.tsexamples/paradedb-demo/src/queries/bm25-mode-tour.tsexamples/paradedb-demo/src/queries/bm25-proximity-chain.tsexamples/paradedb-demo/src/queries/bm25-proximity.tsexamples/paradedb-demo/src/queries/bm25-top-by-score.tsexamples/paradedb-demo/test/bm25.integration.test.tsexamples/paradedb-demo/tsconfig.jsonexamples/paradedb-demo/vitest.config.tspackages/1-framework/3-tooling/vite-plugin-contract-emit/package.jsonpackages/3-extensions/paradedb/package.jsonpackages/3-extensions/paradedb/src/core/descriptor-meta.tspackages/3-extensions/paradedb/src/core/proximity-chain.tspackages/3-extensions/paradedb/src/exports/control.tspackages/3-extensions/paradedb/src/exports/operation-types.tspackages/3-extensions/paradedb/src/exports/runtime.tspackages/3-extensions/paradedb/src/types/operation-types.tspackages/3-extensions/paradedb/test/operations.test.tspackages/3-extensions/paradedb/tsdown.config.tspackages/3-extensions/paradedb/vitest.config.tspackages/3-extensions/postgres/package.jsonpackages/3-extensions/sql-orm-client/package.jsonpackages/3-extensions/sqlite/package.jsonpackages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
💤 Files with no reviewable changes (4)
- packages/3-extensions/sql-orm-client/package.json
- packages/3-extensions/postgres/package.json
- packages/3-extensions/sqlite/package.json
- packages/1-framework/3-tooling/vite-plugin-contract-emit/package.json
| const distance = Number.parseInt(distStr, 10); | ||
| if (!Number.isInteger(distance) || distance < 0) { | ||
| throw new Error( | ||
| `proximity-chain: distance at position ${i + 1} must be a non-negative integer (optionally prefixed '>' for ordered); got '${distRaw}'`, | ||
| ); |
There was a problem hiding this comment.
Harden distance parsing to reject partial numerics.
Number.parseInt() accepts values like "2abc" as 2, so malformed proximity-chain distance tokens currently pass validation.
Suggested fix
- const distance = Number.parseInt(distStr, 10);
- if (!Number.isInteger(distance) || distance < 0) {
+ if (!/^\d+$/.test(distStr)) {
throw new Error(
`proximity-chain: distance at position ${i + 1} must be a non-negative integer (optionally prefixed '>' for ordered); got '${distRaw}'`,
);
}
+ const distance = Number(distStr);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const distance = Number.parseInt(distStr, 10); | |
| if (!Number.isInteger(distance) || distance < 0) { | |
| throw new Error( | |
| `proximity-chain: distance at position ${i + 1} must be a non-negative integer (optionally prefixed '>' for ordered); got '${distRaw}'`, | |
| ); | |
| if (!/^\d+$/.test(distStr)) { | |
| throw new Error( | |
| `proximity-chain: distance at position ${i + 1} must be a non-negative integer (optionally prefixed '>' for ordered); got '${distRaw}'`, | |
| ); | |
| } | |
| const distance = Number(distStr); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/paradedb-demo/src/main.ts` around lines 36 - 40, The parsing of
proximity-chain distances uses Number.parseInt(distStr, 10) which accepts
partial numerics like "2abc"; change the check to first validate distStr
strictly (e.g., require it matches /^\d+$/ or use a parse+string comparison)
before converting so only pure non-negative integer strings are accepted; if the
validation fails, throw the same Error (referencing distRaw and the existing
message), otherwise parse into distance and proceed (update the code around
distStr, distance, and the existing throw).
| import { bm25Match } from '../src/queries/bm25-match'; | ||
| import { bm25TopByScore } from '../src/queries/bm25-top-by-score'; | ||
|
|
||
| const SKIP = process.env['DATABASE_URL'] === undefined; |
There was a problem hiding this comment.
Handle blank DATABASE_URL in the skip gate.
Line 8 only checks for undefined; an empty value ('' or whitespace) won’t skip and will fail later with config/runtime errors.
Suggested patch
-const SKIP = process.env['DATABASE_URL'] === undefined;
+const SKIP = (process.env['DATABASE_URL']?.trim() ?? '') === '';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/paradedb-demo/test/bm25.integration.test.ts` at line 8, The SKIP
gate currently only checks for undefined; change the logic that sets the SKIP
constant (SKIP) to treat empty strings and strings containing only whitespace as
missing too by trimming the value and checking for falsiness (e.g., const val =
process.env['DATABASE_URL']; const SKIP = !val || val.trim() === ''), so any
empty/whitespace DATABASE_URL will cause tests to skip.
## Intent
Wire up the paradedb extension's query plane on top of the bm25
index-type registration that landed on `psl-index-plus`. Authoring +
emit + DDL already work via the upstream registry; this PR adds the
runtime-side: a complete set of paradedb query operations, the proximity
chain builder, and an end-to-end CLI demo that exercises everything
against a live paradedb container.
## Change map
The extension itself (`packages/3-extensions/paradedb`) gains:
- **11 query operations** through two shared helpers in
`src/core/descriptor-meta.ts`:
- `matchOp` covers the five match-mode operators (`paradeDbMatch`/`@@@`,
`paradeDbMatchAny`/`|||`, `paradeDbMatchAll`/`&&&`,
`paradeDbTerm`/`===`, `paradeDbPhrase`/`###`).
- `typmodCastOp` covers the four typmod casts (`paradeDbFuzzy`,
`paradeDbBoost`, `paradeDbConst`, `paradeDbSlop`). Each wraps its
integer argument in `LiteralExpr.of(n)` because Postgres rejects
parameterized typmods, with per-op range validators.
- **`paradeDbScore`** for `pdb.score(<key>)`.
- **`ParadeDbProximityChain`** in `src/core/proximity-chain.ts` — an
immutable builder with `.within(distance, term, { ordered? })` for
multi-step chains mixing `##` and `##>`. Distances render as
`LiteralExpr` (chained operators only accept literal slop). The chain
implements `Expression<text>` so it composes through `paradeDbMatch` via
the `@@@` overload.
- **Runtime descriptor** at `src/exports/runtime.ts` and **`pg_search`
install** via `SqlControlExtensionDescriptor.databaseDependencies`.
`examples/paradedb-demo` is a new CLI example. `prisma/contract.ts`
authors a single `Item` model with a bm25 index using the upstream
`constraints.index([...], { type: 'bm25', options: { key_field: 'id' }
})` shape; `pnpm db:init` produces the actual `CREATE INDEX … USING bm25
…` DDL via the registry path. A `docker-compose.yaml` plus
`init/01-create-demo-db.sql` create a dedicated `demo` database to
sidestep the paradedb image's preloaded PostGIS tables.
CLI commands: `match`, `top`, `fuzzy`, `proximity`, `proximity-chain`,
`chain-demo` (hardcoded multi-step + mixed direction), `mode-tour`
(curated comparison of the five match modes against contrastive seed
data), and `cast-demo` (boost/const/slop).
Also includes a small adapter fix in
`packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts` —
index introspection now orders columns by `array_position(indkey,
attnum)` rather than `attnum`, so multi-column indexes whose declared
column order differs from the table's column declaration order satisfy
schema verification.
## Behavior evidence
- `pnpm start -- mode-tour` returns distinct, contrastive results across
the five match modes against the seed data, demonstrating each
operator's character (any vs all vs term vs phrase).
- `pnpm start -- cast-demo` shows boost/const/slop modifying the BM25
score as expected (`pdb.boost(5)` raises score ~2 → ~10; `pdb.const(1)`
flattens to 1; `pdb.slop(1)` matches non-adjacent phrase tokens).
- 17 paradedb unit tests cover all 11 operations + the proximity chain.
## Follow-ups / open questions
- `paradeDbFuzzy/Boost/Const/Slop` are typed as returning `pg/text@1`;
actual SQL types are paradedb-specific (`pdb.fuzzy`, `pdb.boost`, etc.).
The TS fudge works because match operators are overloaded to accept
these types alongside `text`, but a more honest typing would introduce a
paradedb codec.
- `text[]` query input (term-set with `ARRAY[...]`, pretokenized phrase)
is deferred pending scalar-list support.
- Tokenizer-override casts (`'q'::pdb.whitespace`), highlight
(`pdb.snippet*`), aggregations, and search-time joins are not yet
exposed.
## Non-goals
- The bm25 index-type registry, adapter introspection of
`type`/`options`, and PSL `@@index` parsing all live in
`psl-index-plus`, not here.
- The legacy `bm25Index({...})` and `bm25.text/numeric/...` authoring
helpers the original branch carried were intentionally dropped in favor
of the generic `constraints.index({ type, options })` registry path.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Complete ParadeDB demo example with BM25 search, proximity chains,
fuzzy matching, and scoring operations.
* ParadeDB proximity chain query builder for advanced search patterns.
* New ParadeDB operation types and runtime exports.
* **Bug Fixes**
* Fixed Postgres index column ordering in schema introspection.
* **Refactoring**
* Standardized package exports field across multiple packages, removing
legacy main/module fields.
* **Tests**
* Added ParadeDB operation descriptor and BM25 integration test suites.
[](https://app.coderabbit.ai/change-stack/prisma/prisma-next/pull/433)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Intent
Wire up the paradedb extension's query plane on top of the bm25 index-type registration that landed on
psl-index-plus. Authoring + emit + DDL already work via the upstream registry; this PR adds the runtime-side: a complete set of paradedb query operations, the proximity chain builder, and an end-to-end CLI demo that exercises everything against a live paradedb container.Change map
The extension itself (
packages/3-extensions/paradedb) gains:src/core/descriptor-meta.ts:matchOpcovers the five match-mode operators (paradeDbMatch/@@@,paradeDbMatchAny/|||,paradeDbMatchAll/&&&,paradeDbTerm/===,paradeDbPhrase/###).typmodCastOpcovers the four typmod casts (paradeDbFuzzy,paradeDbBoost,paradeDbConst,paradeDbSlop). Each wraps its integer argument inLiteralExpr.of(n)because Postgres rejects parameterized typmods, with per-op range validators.paradeDbScoreforpdb.score(<key>).ParadeDbProximityChaininsrc/core/proximity-chain.ts— an immutable builder with.within(distance, term, { ordered? })for multi-step chains mixing##and##>. Distances render asLiteralExpr(chained operators only accept literal slop). The chain implementsExpression<text>so it composes throughparadeDbMatchvia the@@@overload.src/exports/runtime.tsandpg_searchinstall viaSqlControlExtensionDescriptor.databaseDependencies.examples/paradedb-demois a new CLI example.prisma/contract.tsauthors a singleItemmodel with a bm25 index using the upstreamconstraints.index([...], { type: 'bm25', options: { key_field: 'id' } })shape;pnpm db:initproduces the actualCREATE INDEX … USING bm25 …DDL via the registry path. Adocker-compose.yamlplusinit/01-create-demo-db.sqlcreate a dedicateddemodatabase to sidestep the paradedb image's preloaded PostGIS tables.CLI commands:
match,top,fuzzy,proximity,proximity-chain,chain-demo(hardcoded multi-step + mixed direction),mode-tour(curated comparison of the five match modes against contrastive seed data), andcast-demo(boost/const/slop).Also includes a small adapter fix in
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts— index introspection now orders columns byarray_position(indkey, attnum)rather thanattnum, so multi-column indexes whose declared column order differs from the table's column declaration order satisfy schema verification.Behavior evidence
pnpm start -- mode-tourreturns distinct, contrastive results across the five match modes against the seed data, demonstrating each operator's character (any vs all vs term vs phrase).pnpm start -- cast-demoshows boost/const/slop modifying the BM25 score as expected (pdb.boost(5)raises score ~2 → ~10;pdb.const(1)flattens to 1;pdb.slop(1)matches non-adjacent phrase tokens).Follow-ups / open questions
paradeDbFuzzy/Boost/Const/Slopare typed as returningpg/text@1; actual SQL types are paradedb-specific (pdb.fuzzy,pdb.boost, etc.). The TS fudge works because match operators are overloaded to accept these types alongsidetext, but a more honest typing would introduce a paradedb codec.text[]query input (term-set withARRAY[...], pretokenized phrase) is deferred pending scalar-list support.'q'::pdb.whitespace), highlight (pdb.snippet*), aggregations, and search-time joins are not yet exposed.Non-goals
type/options, and PSL@@indexparsing all live inpsl-index-plus, not here.bm25Index({...})andbm25.text/numeric/...authoring helpers the original branch carried were intentionally dropped in favor of the genericconstraints.index({ type, options })registry path.Summary by CodeRabbit
New Features
Bug Fixes
Refactoring
Tests