Skip to content

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

Draft
SevInf wants to merge 27 commits intopsl-index-plusfrom
worktree/parade-db-ops
Draft

feat(extension-paradedb): query ops and demo#433
SevInf wants to merge 27 commits intopsl-index-plusfrom
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.

SevInf added 25 commits May 6, 2026 19:08
Establishes the index-type registry primitive: factory builder declaring
the type literal once, phantom-typed __indexTypes pack threading,
validation seam in validateSqlStorage, single Postgres-style renderer,
and the lockstep using/config → type/options field-name rename.
Adds defineIndexTypes() factory builder and createIndexTypeRegistry()
in @prisma-next/sql-contract/index-types. The builder declares each
type literal exactly once and exposes both a phantom IndexTypes map
(for type-level narrowing) and a runtime entry list. The registry is
instance-based — created per contract assembly, not module-global —
so two contracts with different pack lists see different valid type
sets. No consumers wired yet.

Also clarifies ADR 210: the per-contract registry is assembled from
each pack's entries during contract definition, not via global
register() calls at module load.
Lockstep rename across the IR, the SQL family validator, the schema-IR,
the TS authoring surface (IndexOptions, IndexConstraint, IndexNode,
contract-lowering, build-contract), and the contract emitter. The
schema-IR SqlIndexIR gains the new fields. The ParadeDB bm25Index()
helper writes the new field names but is otherwise untouched (full
removal happens when the registry is wired in M3).

The placeholder fields are inert in every active code path today, so
this rename is observationally silent. No backwards-compatibility
shim.
…ntract definition

ExtractIndexTypesFromPack pulls the phantom __indexTypes off any pack
that carries one. AllIndexTypeLiterals walks all literal keys across a
record of packs, and MergeExtensionIndexTypes builds the merged map by
looking each literal up in its contributing pack. IndexTypesFromDefinition
folds target + extension packs into a single record and feeds it through
MergeExtensionIndexTypes. SqlContractResult exposes the merged map as
a phantom __indexTypes? field.

The implementation deliberately avoids UnionToIntersection so the empty
case resolves cleanly to Record<never, never> rather than the
UnionToIntersection<never> = unknown footgun.

Type tests cover single-pack extraction, fall-through for packs without
__indexTypes, multi-pack merging, definition-level resolution, and the
end-to-end defineContract path.
validateContract grows an optional indexTypeRegistry option. When
supplied, validateSqlStorage looks up each index's type in the
registry, validates options against the registered arktype validator,
and rejects unknown types or options that fail validation. Even when no
registry is supplied, options-without-type is still rejected.

Test coverage: registered+valid passes, unregistered type rejects with
the type name, invalid option fails with arktype path, strict-mode extra
key rejects, options-without-type rejects, no type/options accepts, no
registry skips type lookup but still rejects options-without-type.
…ions are set

createIndex grows an optional extras argument carrying type and options.
When supplied, it emits CREATE INDEX <name> ON <table> USING <method>
(<cols>) WITH (<key> = <literal>, ...). The framework owns the renderer:
strings are escapeLiteral-quoted, finite numbers stringify, booleans
emit true/false, and any other leaf shape (null, NaN, objects) throws.

CreateIndexCall and renderTypeScript carry the new args verbatim; the
issue planner threads contract index.type / index.options through.
Existing callers without extras get the same plain CREATE INDEX as
before.

Tests: 8 DDL emission cases in index-ddl.test.ts (plain, USING-only,
USING+WITH, empty options, mixed scalars, single-quote escape, null
rejection, NaN rejection) plus a renderTypeScript case for the new
extras argument shape.
…ema indexes

verifyIndexes now treats type and options as part of an index's identity.
A contract index with type "gin" no longer matches a schema index that
has columns the same but type "btree" (or no type at all); instead the
verifier emits index_mismatch (the contract index needs creation) plus
extra_index in strict mode (the schema index needs to be dropped). The
issue planner naturally translates this pair into DROP + CREATE — the
spec'd behavior for any change to columns / type / options.

Unique constraints can only satisfy a contract index requirement when
that requirement does not specify a type or options, since a unique
constraint carries no index-method or storage-parameter information of
its own.

Tests: three new cases in schema-verify.semantic-satisfaction.test.ts —
type differs (mismatch + extra), options differ (mismatch + extra), and
type/options match exactly (no issues). The createContractTable /
createSchemaTable helpers grow optional type / options fields so the
index inputs accept the new shape.
…ry; drop helpers

Replace the bespoke bm25Index() helper, the bm25 namespace
(text/numeric/boolean/json/datetime/range/expression builders), the
Bm25*FieldConfig / *FieldOptions types, the TokenizerId catalog, and
the rich Bm25IndexConfig with a single defineIndexTypes().add("bm25",
{ options }) registration. The arktype validator narrows options to
{ key_field: string } in strict mode; per-field tokenizer / column
configuration is deferred to expression-index support.

paradedbPackMeta exposes the resulting __indexTypes phantom map so
SqlContractResult can narrow constraints.index({ type: "bm25" }) at
the call site, and exposes the registry entries on indexTypes so
contract assembly can register them at validation time.

Tests rewritten around the new shape: pack identity + capability
checks, single-entry verification, and the four validator paths
(valid options, missing key_field, extra key, wrong type).

Adds @prisma-next/sql-contract and arktype to paradedb deps.
…pe, options })

Threads the merged IndexTypes map all the way to the constraints DSL
inside .sql(({ constraints }) => ...) so wrong index type literals and
wrong option shapes surface as TS errors on the offending
constraints.index(...) line — not buried in a deep defineContract type.

Wiring:
- ContractModelBuilder gains a sixth generic IndexTypes (default wildcard)
- SqlContext<Fields, IndexTypes> picks PackAwareSqlConstraints<IndexTypes>
- A discriminated-union IndexInput<Name, IndexTypes> rejects unregistered
  types and bad option shapes; "options without type" is also rejected
  via the type?: never branch
- ComposedAuthoringHelpers.model becomes PackAwareModel<MergeAllPackIndexTypes>
  so the helpers form (defineContract({...}, ({ model }) => ...)) carries
  the merged map; the bare model() import keeps the wildcard default to
  preserve backward compat

The runtime is unchanged — narrowing is pure type machinery.

e2e: test/integration/test/authoring/paradedb-bm25-narrowing.test.ts
exercises the real paradedbPack and asserts six cases via vitest +
@ts-expect-error: well-formed bm25 typechecks; unknown options key
rejects; missing key_field rejects; unregistered type rejects; options
without type rejects; bare model() degraded path still accepts (no
narrowing).

Adds extension-paradedb to integration-tests deps.
Adds parseObjectLiteralStringMap to psl-attribute-parsing — parses
brace-balanced { key: "value", ... } argument values into
Record<string, string>. Bare-identifier keys, quoted-string-literal
values; boolean / number / nested-object leaves are rejected with a
clear diagnostic (V1 is string-leaves-only per the spec).

The PSL grammar itself needs no change: splitTopLevelSegments is
already brace-aware, so the existing parser preserves the raw
{...} value through to the interpreter.

The @@index interpreter now extracts the type and options named
arguments alongside the existing @Map handling. Validates that
options requires a surrounding type, that type is a quoted string
literal, and lowers everything into IndexNode.type / IndexNode.options.

Tests cover: documented example shape, multi-key options, boolean
leaf rejection, number leaf rejection, options-without-type, malformed
object literal, and the unchanged "no type or options" path.
…pack

Adds an integration test that exercises the full PSL -> IR -> registry
validation path with the real paradedbPack:

1. The documented spec example (@@index([body], type: "bm25",
   options: { key_field: "id" }, map: "doc_body_bm25_idx")) lowers
   to a Contract IR index node carrying type, options, and name.
2. The lowered contract validates clean against a registry built from
   paradedbIndexTypes.entries.
3. A PSL-authored bm25 index whose options miss the required key_field
   is rejected by the registry with a clear error.

This complements the unit tests in contract-psl by going through the
actual pack metadata and validateContract path.
…exIR

The Postgres introspection query joins pg_am and reads
pg_class.reloptions so introspected SqlIndexIR now carries:
- type: the index method (dropped to undefined when amname is "btree",
  since that is the Postgres default and a contract index without an
  explicit type should match a default-method introspected index)
- options: pg_class.reloptions parsed from {key=value, ...} into
  Record<string, string> (Postgres returns reloption values as raw
  text regardless of the underlying scalar type)

The family verifier's indexExtrasMatch now compares contract options
to introspected options via String() coercion so a contract
`fillfactor: 70` matches an introspected `fillfactor: "70"` without
forcing a spurious DROP+CREATE.

Without this wiring the migration planner would treat any contract
index with type set as different from any introspected index on the
same columns — forcing DROP+CREATE on every plan even when the live
index already matches the contract.

Tests: four PGlite integration cases covering the matrix — default
btree (type/options unset), non-default gin (type set, options unset),
default btree with WITH options (type unset, options set), and gin
with WITH options (both set).
…clause

Hoist extras?.options into a local so the WITH-clause check uses
optional chaining instead of !-assertions, which biome rejects under
lint/style/noNonNullAssertion.
…x_mismatch planner branch

The issue-planner index_mismatch branch reconstructed the contract index
from issue.expected (column names only), so adding a typed index to an
existing table emitted CREATE INDEX without USING/WITH and rebuilt as a
default btree. Look up the contract index by columns and pass type,
options, and an explicit name through to CreateIndexCall.
…uild registry from packs in family validateContract

The internal validateSqlStorage previously short-circuited the type lookup
when no registry was supplied, and the SQL family runtime never passed
one. Net result: the CLI validateContract codepath silently accepted any
index type literal, defeating the design-time gate the registry is meant
to be (per ADR 210).

- Internal validateSqlStorage now takes a non-undefined IndexTypeRegistry.
- Public validateContract defaults a missing options.indexTypeRegistry to
  an empty registry, so unregistered type literals fail validation
  regardless of caller setup.
- createSqlFamilyInstance walks [adapter, target, ...extensions] for
  indexTypes entries via buildIndexTypeRegistry(), caches the registry
  once per family instance, and passes it to all four sqlValidateContract
  call sites (validateContract, verify, schemaVerify, sign).
…rant-opt-in, not framework-enforced

The previous wording implied validateSqlStorage validates options "in
strict mode", which read as a framework guarantee. arktype is
loose-by-default and the framework merely invokes whatever validator the
registrant constructed; strictness is a property of that validator. Make
the recommendation explicit without claiming the framework imposes it.
Two gaps in the PSL integration coverage for the index-type registry:
unregistered type (TS DSL covered both compile-time and runtime, PSL
only covered compile-time via parser; runtime side untested) and an
empty options literal (parser produces {}; pipeline through to the
validator was unasserted). Both authored as @@index attributes, lowered
through the real paradedb pack, and asserted at the runtime registry
boundary.
…== undefined

The contract-dsl `index()` factory and the emitter index serialization
used truthy checks for `name`, `type`, and `options`, while the rest of
the framework (validate.ts, verify-helpers.ts, op-factory-call.ts,
issue-planner.ts) reads these fields with `=== undefined`. Align both
authoring sites on the framework convention.
…pes to Record<never, never>

The bare model() import previously defaulted IndexTypes to a wildcard
(Record<string, { options: Record<string, unknown> }>), so any string
literal typechecked at constraints.index({ type, options }) — even when
no packs were attached and no index types were registered. Drop the
wildcard. With no attached packs, only the default-index form
constraints.index(cols.x) (no type/options) typechecks; type literals
are restricted to whatever attached packs registered.

The IndexInput discriminated union already had the right structure for
the empty case (keyof IndexTypes extends never → ConstraintOptions<Name>);
the wildcard was the inconsistency.

Three lowering/runtime tests authored typed indexes via the bare model()
to exercise IR pass-through. They now use createComposedAuthoringHelpers
with a small test pack (test/helpers/test-index-pack.ts) that registers
bm25 and hash with permissive option shapes. The paradedb narrowing test
flips: bare model() now @ts-expect-errors on type: "made-up" and accepts
the no-options default-index form.

contract-builder.normalization.test.ts: simplified the "extension index
config with expression fields" test to a single pass-through case using
the real { key_field: "id" } shape; the old nested-array fields[]
payload was a leftover from the deleted bm25Index() helper.
…ten bare index overloads

Three small cleanups enabled by the wildcard removal:

- composed-authoring-helpers.ts dropped its local copies of
  ExtractIndexTypesFromPack, AllPackIndexTypeLiterals, and
  MergedPackIndexTypes. They were near-duplicates of the canonical
  versions in contract-types.ts. MergeAllPackIndexTypes now reuses
  MergeExtensionIndexTypes directly.

- contract-types.ts MergeExtensionIndexTypes now clamps the value side
  with Extract<..., { readonly options: unknown }>. The clamp tells the
  type-checker the resulting map structurally extends IndexTypeMap, so
  MergeAllPackIndexTypes no longer needs the extends infer M extends
  IndexTypeMap ? M : Record<never, never> coercion at use sites.

- contract-dsl.ts bare index() overloads use ConstraintOptions<Name>
  directly instead of IndexInput<Name, Record<never, never>>. With no
  packs attached IndexInput already evaluates to ConstraintOptions, so
  using it directly is self-documenting.
…es IndexTypeRegistration directly

The previous design carried two parallel artifacts on every pack: the
runtime indexTypes entries array and a __indexTypes phantom typed map.
Both had to be kept in sync by hand, with no compile-time link between
them — adding .add(...) to the builder would update one and silently
leave the other behind.

Introduce a read-only IndexTypeRegistration<TMap> interface that exposes
just entries (runtime) and IndexTypes (phantom). IndexTypeBuilder
extends it. The pack stores the builder verbatim in its indexTypes
field; both halves flow from a single source of truth, and the
read-only interface prevents the pack from being misused as a mutable
registry.

Consumer-side adjustments:
- ExtractIndexTypesFromPack<P> reads P[indexTypes] typed as
  IndexTypeRegistration<infer M>.
- buildIndexTypeRegistry iterates descriptor.indexTypes.entries.
- __indexTypes? dropped from SqlContractResult (only tests read it;
  narrowing is verified end-to-end via paradedb-bm25-narrowing).
- ParadeDB descriptor-meta.ts collapsed from base+intersection to a
  plain as const literal.
- contract-builder.index-types.test.ts updated; the two
  SqlContractResult.__indexTypes tests removed (redundant with upstream
  MergeExtensionIndexTypes tests).

ADR 210 §3 and the matching/lookup/dispatch prose updated to drop the
"phantom-typed __indexTypes field" framing in favor of "single
registration value carrying both halves".
…Types; remove buildSqlSpec cast

The cast at buildSqlSpec(createSqlConstraintsDsl() as unknown as
SqlContext<Fields, IndexTypes>[constraints]) was bridging from a bare
DSL value to one with pack-aware narrowing. Both are the same runtime
function; only the type-level signature differs.

Make createConstraintsDsl and createSqlConstraintsDsl generic over
IndexTypes, with the inner index overloads using IndexInput<Name,
IndexTypes> directly. buildSqlSpec passes its IndexTypes generic to
createSqlConstraintsDsl<IndexTypes>(); no cast needed.

The implementation signatures inner options field was loosened from
Record<string, unknown> to unknown, because per-pack option shapes
(like { key_field: string }) are closed object types that arent
strictly assignable to Record<string, unknown>. A single property-
scoped as Record<string, unknown> cast at the spread point stores the
value into the IR.

Net: one as unknown as cast removed, one minimal as cast introduced at
the storage boundary.
…p dead branches

F08: paradedb-bm25-narrowing.test.ts gains two positive expectTypeOf
assertions on Doc.__indexTypes — bare model() resolves to Record<never,
never>, helpers-bound model() (with paradedb attached) carries the bm25
shape. The boundary is now locked at the type level rather than relying
on absence of TS errors.

F10: psl-index-type-options.integration.test.ts "rejects bm25 with bad
options" case tightened from .toThrow(/key_field|bm25/) to instanceof
ContractValidationError plus message-contains-both bm25 AND key_field.
Either-word match was loose enough to pass on incidental wording.

F14: dropped the hasExtras ternary in CreateIndexCall.toOp and the
two issue-planner.ts branches (missing_table, index_mismatch). Calling
createIndex(..., extras) with an empty extras object behaves the same
as omitting the arg, so the conditional was dead. Postgres tests still
155/155.
…r-property error positions

Collapsing constraints.index to a single multi-field overload makes
TypeScript pin error positions to the offending property (an unknown
options key, a wrong type literal, missing required option fields)
instead of reporting a generic "no overload matches this call" at the
call expression. The trade-off: callers always pass the field list as
an array, even for single-column indexes:

  constraints.index(cols.body, ...)    // before
  constraints.index([cols.body], ...)  // after

Migrated all in-repo callers (tests, integration test fixtures, CLI
test fixtures, e2e migrations) in lockstep. The paradedb narrowing test
now lands @ts-expect-error directives on the offending property line
inside the options literal, matching the new TS error positions.

Also fixed obsolete MySQL/MariaDB/MSSQL adapter mentions in ADR 210
and the PR description; only postgres and sqlite SQL adapters exist.
…owering, not at runtime

Index-type validation now runs inside buildSqlContractFromDefinition,
the shared lowering both TS (defineContract) and PSL
(interpretPslDocumentToSqlContract) authoring funnel through. Bad
types or option shapes throw at authoring time.

- packages/2-sql/1-core/contract/src/validators.ts: validateIndexTypes
  lives here next to validateStorageSemantics. Both validate the
  storage IR; build-contract.ts assertStorageSemantics calls them in
  one step.
- packages/2-sql/1-core/contract/src/validate.ts: drop validateIndexTypes
  (moved to validators.ts) and the indexTypeRegistry option from the
  public validateContract API. Runtime keeps structural / referential /
  semantic checks; no registry needed.
- packages/2-sql/2-authoring/contract-ts/src/build-contract.ts: build a
  per-contract IndexTypeRegistry from definition.target +
  definition.extensionPacks inline in assertStorageSemantics.
- packages/2-sql/9-family/src/core/control-instance.ts: strip the family
  registry build, the DescriptorWithIndexTypes interface, and the
  { indexTypeRegistry } argument at all four sqlValidateContract call
  sites.

Tests:
- validate.test.ts cases call validateIndexTypes directly from
  validators.ts.
- contract-builder.constraints.test.ts asserts defineContract throws
  at authoring time on an unregistered type.
- contract-psl/test/interpreter.test.ts attaches a permissive bm25
  test pack so the existing PSL parser tests can lower successfully.
- paradedb-bm25-narrowing.test.ts wraps the @ts-expect-error cases in
  expect(...).toThrow(...) since defineContract now throws synchronously.
- psl-index-type-options.integration.test.ts: rewrite negative cases
  to assert interpret() throws directly (validation happens inside
  the interpreter, not in a separate validateContract call).

Docs:
- ADR 210 §4 retitled "Validation seam at the ContractIR -> Contract
  boundary" and rewritten around the new seam.
- PR description: "Authoring-time validator" bullet replaces the old
  "Validator hook" + "Family runtime wiring" pair.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: e4c0cfa1-b3c9-4d36-ba48-03cae20f0a8f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 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/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: 2ef7bec

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.
@SevInf SevInf force-pushed the worktree/parade-db-ops branch from 6af7b34 to e5643ee Compare May 7, 2026 13:23
…onTypes

Drop matchOp/typmodCastOp helpers and inline all 11 entries in a record
annotated with `DescriptorMap<CT>`, a mapped type derived from
`QueryOperationTypes<CT>`. Renaming a method or changing a return codec in
the type module now fails typecheck inside `descriptor-meta.ts` instead of
silently drifting into a runtime mismatch.
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