Skip to content

feat(extension-paradedb): query ops and demo#433

Merged
SevInf merged 3 commits into
mainfrom
worktree/parade-db-ops
May 11, 2026
Merged

feat(extension-paradedb): query ops and demo#433
SevInf merged 3 commits into
mainfrom
worktree/parade-db-ops

Conversation

@SevInf
Copy link
Copy Markdown
Contributor

@SevInf SevInf commented May 7, 2026

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.

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.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

📝 Walkthrough

Walkthrough

This 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 exports field.

Changes

ParadeDB Extension: Type System and Core Builders

Layer / File(s) Summary
Query Operation Types
packages/3-extensions/paradedb/src/types/operation-types.ts
Exports QueryOperationTypes<CT> extending SqlQueryOperationTypes with ParadeDB operations: match/matchAny/matchAll/term/phrase (boolean results), score (float), fuzzy/boost/const/slop (text), and proximity (chain builder).
Proximity Chain Builder
packages/3-extensions/paradedb/src/core/proximity-chain.ts
Implements ParadeDbProximityChain class with .within(distance, term, options?) chainable steps and buildAst() to generate SQL lowering templates using ## (unordered) and ##> (ordered) operators with distance validation.
Operation Types Export
packages/3-extensions/paradedb/src/exports/operation-types.ts
Re-exports QueryOperationTypes as public API.
Operation Tests
packages/3-extensions/paradedb/test/operations.test.ts
Validates operation metadata, lowering AST templates, distance/range validation, codec dispatch hints, proximity chaining with mixed ordering, registry integration, and descriptor structure.

ParadeDB Extension: Descriptors and Configuration

Layer / File(s) Summary
Descriptor Metadata
packages/3-extensions/paradedb/src/core/descriptor-meta.ts
Updates paradedbPackMeta to include types.queryOperationTypes.import metadata mapping to the public operation-types export.
Control Extension Descriptor
packages/3-extensions/paradedb/src/exports/control.ts
Adds paradedbExtensionDescriptor with database dependency manager (paradedbDatabaseDependencies) that orchestrates pg_search extension creation (precheck/execute/postcheck SQL steps).
Runtime Extension Descriptor
packages/3-extensions/paradedb/src/exports/runtime.ts
Adds paradedbRuntimeDescriptor wiring query operations and empty codec registry for PostgreSQL runtime target.
Package Exports and Build Config
packages/3-extensions/paradedb/package.json, packages/3-extensions/paradedb/tsdown.config.ts, packages/3-extensions/paradedb/vitest.config.ts
Adds ./operation-types and ./runtime exports, expands workspace dependencies (family-sql, framework-components, sql-contract/operations/relational-core/runtime), adds Vitest config with coverage thresholds, updates build entry points.

ParadeDB Demo: Complete Example Application

Layer / File(s) Summary
Infrastructure and Configuration
examples/paradedb-demo/docker-compose.yaml, examples/paradedb-demo/init/01-create-demo-db.sql, examples/paradedb-demo/.env.example, examples/paradedb-demo/package.json, examples/paradedb-demo/prisma-next.config.ts, examples/paradedb-demo/tsconfig.json, examples/paradedb-demo/vitest.config.ts, examples/paradedb-demo/biome.jsonc
Sets up ParadeDB container (port 5434), database initialization script, environment variables, workspace dependencies, Prisma-Next contract emission config, TypeScript/Vitest/Biome tooling.
Database Contract and Models
examples/paradedb-demo/prisma/contract.ts, examples/paradedb-demo/src/prisma/contract.d.ts, examples/paradedb-demo/src/prisma/contract.json
Defines Item model with id/description/category/rating columns and BM25 index configuration; generates typed contract declarations and JSON runtime metadata.
Database Runtime Client
examples/paradedb-demo/src/prisma/db.ts
Creates Postgres runtime with ParadeDB extension using typed contract.
Application Configuration
examples/paradedb-demo/src/app-config.ts
Loads and validates DATABASE_URL using arktype schema validation.
Query Helpers and Demos
examples/paradedb-demo/src/queries/bm25-*.ts
Implements nine query modules: match, top-by-score, fuzzy, proximity, proximity-chain, mode-tour, cast-demo, and chain-demo; each builds and executes ParadeDB SQL plans.
ORM Client Collections
examples/paradedb-demo/src/orm-client/collections.ts, examples/paradedb-demo/src/orm-client/client.ts, examples/paradedb-demo/src/orm-client/bm25-top-matches.ts
Defines ItemCollection extending ORM base, creates ORM client factory with typed context and collections registry, exports ORM-based BM25 top-matches query.
CLI Entry Point
examples/paradedb-demo/src/main.ts
Implements CLI dispatcher supporting commands (match, top, fuzzy, proximity, proximity-chain, chain-demo, mode-tour, cast-demo, orm-top) with per-command argument parsing, database connection lifecycle, and formatted JSON output.
Database Management Scripts
examples/paradedb-demo/scripts/drop-db.ts, examples/paradedb-demo/scripts/seed.ts
Scripts for schema reset (public/prisma_contract) and seeding with hardcoded item catalog.
Integration Tests
examples/paradedb-demo/test/bm25.integration.test.ts
Three integration tests validating bm25Match, bm25TopByScore, and ormClientBm25TopMatches with connection cleanup and result assertions.
README Documentation
examples/paradedb-demo/README.md
Documents ParadeDB demo features, supported operations, Docker run/teardown commands, and database initialization workflow.

Package Entry Point Modernization

Layer / File(s) Summary
Vite Plugin Contract Emit Exports
packages/1-framework/3-tooling/vite-plugin-contract-emit/package.json
Removes main/module fields, relies on exports for . and ./package.json.
Postgres Extension Exports
packages/3-extensions/postgres/package.json
Adds types to exports, removes top-level main/module.
SQL ORM Client Exports
packages/3-extensions/sql-orm-client/package.json
Adds exports map for . and ./package.json, removes main/module.
SQLite Extension Exports
packages/3-extensions/sqlite/package.json
Updates exports for ./runtime and ./package.json, removes main/module.

Postgres Adapter Index Ordering Fix

Layer / File(s) Summary
Index Column Ordering
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
Fixes composite index column ordering by using array_position(ix.indkey::int[], a.attnum) instead of a.attnum in schema introspection query.

🎯 4 (Complex) | ⏱️ ~60 minutes


🐰 Through the garden paths of Postgres bloom,
Full-text searches chase away the gloom,
Proximity chains link terms so near,
ParadeDB magic makes matches clear,
A demo grows where queries cheer!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(extension-paradedb): query ops and demo' directly and accurately summarizes the main changes: implementing query operations for the ParadeDB extension alongside a demonstration example.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree/parade-db-ops

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 7, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-runtime@433

@prisma-next/family-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-mongo@433

@prisma-next/sql-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-runtime@433

@prisma-next/family-sql

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-sql@433

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-arktype-json@433

@prisma-next/extension-cipherstash

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-cipherstash@433

@prisma-next/middleware-telemetry

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/middleware-telemetry@433

@prisma-next/mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo@433

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-paradedb@433

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-pgvector@433

@prisma-next/postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/postgres@433

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-orm-client@433

@prisma-next/sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sqlite@433

@prisma-next/target-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-mongo@433

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-mongo@433

@prisma-next/driver-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-mongo@433

@prisma-next/contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract@433

@prisma-next/utils

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/utils@433

@prisma-next/config

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/config@433

@prisma-next/errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/errors@433

@prisma-next/framework-components

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/framework-components@433

@prisma-next/operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/operations@433

@prisma-next/ts-render

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ts-render@433

@prisma-next/contract-authoring

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract-authoring@433

@prisma-next/ids

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ids@433

@prisma-next/psl-parser

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-parser@433

@prisma-next/psl-printer

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-printer@433

@prisma-next/cli

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/cli@433

@prisma-next/emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/emitter@433

@prisma-next/migration-tools

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/migration-tools@433

prisma-next

npm i https://pkg.pr.new/prisma/prisma-next@433

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/vite-plugin-contract-emit@433

@prisma-next/mongo-codec

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-codec@433

@prisma-next/mongo-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract@433

@prisma-next/mongo-value

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-value@433

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-psl@433

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-ts@433

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-emitter@433

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-schema-ir@433

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-ast@433

@prisma-next/mongo-orm

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-orm@433

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-builder@433

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-lowering@433

@prisma-next/mongo-wire

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-wire@433

@prisma-next/sql-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract@433

@prisma-next/sql-errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-errors@433

@prisma-next/sql-operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-operations@433

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-schema-ir@433

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-psl@433

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-ts@433

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-emitter@433

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-lane-query-builder@433

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-relational-core@433

@prisma-next/sql-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-builder@433

@prisma-next/target-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-postgres@433

@prisma-next/target-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-sqlite@433

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-postgres@433

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-sqlite@433

@prisma-next/driver-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-postgres@433

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-sqlite@433

commit: 638acce

@SevInf SevInf force-pushed the worktree/parade-db-ops branch from 6af7b34 to e5643ee Compare May 7, 2026 13:23
@SevInf SevInf force-pushed the psl-index-plus branch 4 times, most recently from 8707445 to 36cbfdc Compare May 11, 2026 13:35
Base automatically changed from psl-index-plus to main May 11, 2026 13:45
@SevInf SevInf force-pushed the worktree/parade-db-ops branch 3 times, most recently from 7022f98 to b72e8ca Compare May 11, 2026 15:26
SevInf and others added 2 commits May 11, 2026 17:27
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>
@SevInf SevInf force-pushed the worktree/parade-db-ops branch from b72e8ca to c24bc63 Compare May 11, 2026 15:27
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>
@SevInf SevInf marked this pull request as ready for review May 11, 2026 15:38
@SevInf SevInf requested a review from a team as a code owner May 11, 2026 15:38
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
examples/paradedb-demo/docker-compose.yaml (1)

3-3: ⚡ Quick win

Pin the ParadeDB image instead of latest.

Line [3] using latest makes 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 value

Consider improving type surface to avoid cast.

The cast to ExecutionContext<Contract> is acceptable in example code, but ideally db.context would 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 win

Avoid process.exit() inside the try command branch; prefer exit codes + return.

Using process.exit(1) in command branches makes cleanup flow less explicit. Set process.exitCode and return so Line 122 cleanup 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

📥 Commits

Reviewing files that changed from the base of the PR and between 84e9d88 and 638acce.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (44)
  • examples/paradedb-demo/.env.example
  • examples/paradedb-demo/README.md
  • examples/paradedb-demo/biome.jsonc
  • examples/paradedb-demo/docker-compose.yaml
  • examples/paradedb-demo/init/01-create-demo-db.sql
  • examples/paradedb-demo/package.json
  • examples/paradedb-demo/prisma-next.config.ts
  • examples/paradedb-demo/prisma/contract.ts
  • examples/paradedb-demo/scripts/drop-db.ts
  • examples/paradedb-demo/scripts/seed.ts
  • examples/paradedb-demo/src/app-config.ts
  • examples/paradedb-demo/src/main.ts
  • examples/paradedb-demo/src/orm-client/bm25-top-matches.ts
  • examples/paradedb-demo/src/orm-client/client.ts
  • examples/paradedb-demo/src/orm-client/collections.ts
  • examples/paradedb-demo/src/prisma/contract.d.ts
  • examples/paradedb-demo/src/prisma/contract.json
  • examples/paradedb-demo/src/prisma/db.ts
  • examples/paradedb-demo/src/queries/bm25-cast-demo.ts
  • examples/paradedb-demo/src/queries/bm25-chain-demo.ts
  • examples/paradedb-demo/src/queries/bm25-fuzzy.ts
  • examples/paradedb-demo/src/queries/bm25-match.ts
  • examples/paradedb-demo/src/queries/bm25-mode-tour.ts
  • examples/paradedb-demo/src/queries/bm25-proximity-chain.ts
  • examples/paradedb-demo/src/queries/bm25-proximity.ts
  • examples/paradedb-demo/src/queries/bm25-top-by-score.ts
  • examples/paradedb-demo/test/bm25.integration.test.ts
  • examples/paradedb-demo/tsconfig.json
  • examples/paradedb-demo/vitest.config.ts
  • packages/1-framework/3-tooling/vite-plugin-contract-emit/package.json
  • packages/3-extensions/paradedb/package.json
  • packages/3-extensions/paradedb/src/core/descriptor-meta.ts
  • packages/3-extensions/paradedb/src/core/proximity-chain.ts
  • packages/3-extensions/paradedb/src/exports/control.ts
  • packages/3-extensions/paradedb/src/exports/operation-types.ts
  • packages/3-extensions/paradedb/src/exports/runtime.ts
  • packages/3-extensions/paradedb/src/types/operation-types.ts
  • packages/3-extensions/paradedb/test/operations.test.ts
  • packages/3-extensions/paradedb/tsdown.config.ts
  • packages/3-extensions/paradedb/vitest.config.ts
  • packages/3-extensions/postgres/package.json
  • packages/3-extensions/sql-orm-client/package.json
  • packages/3-extensions/sqlite/package.json
  • packages/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

Comment on lines +36 to +40
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}'`,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

@SevInf SevInf enabled auto-merge (squash) May 11, 2026 15:58
@SevInf SevInf disabled auto-merge May 11, 2026 16:04
@SevInf SevInf merged commit 416562a into main May 11, 2026
24 of 25 checks passed
@SevInf SevInf deleted the worktree/parade-db-ops branch May 11, 2026 16:05
wmadden pushed a commit that referenced this pull request May 12, 2026
## 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.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant