Summary
PSL contract authoring does not validate extension-registered index types when consumers use the standard @prisma-next/postgres/config defineConfig path. Authors see unregistered index type "<type>" even when the extension pack correctly registers the type via defineIndexTypes() and ADR 210.
This affects all extension indexTypes in the PSL lane, not a single extension:
- ParadeDB
@@index(..., type: "bm25", options: { key_field: "id" })
- prisma-ltree
@@index(..., type: "gist") (GiST for ltree / ltree[] columns)
The TypeScript lane works because defineContract({ extensionPacks: { … } }) carries full pack refs including indexTypes. The PSL lane fails because pack refs are never threaded into the PSL provider.
Root cause
Index-type validation runs at contract lowering from a per-contract registry built from attached packs' indexTypes (ADR 210).
TS lane: defineContract({ extensionPacks: { ltree } }) → registry includes gist. ✅
PSL lane:
@prisma-next/postgres defineConfig calls prismaContract(path, { output, target, createNamespace }) without composedExtensionPackRefs.
- CLI
executeContractEmit passes composedExtensionPacks: stack.extensionPacks.map(p => p.id) (IDs only) in ContractSourceContext — no field for pack refs.
- PSL interpreter
buildComposedExtensionPackRefs() substitutes stub refs (id/kind only, no indexTypes) when real refs are absent.
- Authoring
type: "gist" (or bm25) → unregistered index type.
The PSL provider already accepts composedExtensionPackRefs (packages/2-sql/2-authoring/contract-psl/src/provider.ts). Integration tests pass refs directly to interpretPslDocumentToSqlContract (e.g. test/integration/test/authoring/psl-index-type-options.integration.test.ts with ParadeDB) — bypassing defineConfig.
Reproduction
- Configure
@prisma-next/postgres/config with extensions: [paradedbControl] (or any pack with indexTypes).
- Author PSL:
model Doc {
id Int @id
body String
@@index([body], type: "bm25", options: { key_field: "id" })
}
- Run
prisma-next contract emit.
- Expected: GiST/BM25 index IR (same as TS lane).
- Actual:
unregistered index type "bm25".
Direct interpreter tests with composedExtensionPackRefs: [paradedbPack] pass; standard config emit does not.
Proposed fix
Wire extension pack refs (which carry indexTypes per ADR 210) into the PSL authoring path:
| File / area |
Change |
packages/3-extensions/postgres/src/config/define-config.ts |
Pass composedExtensionPackRefs: extensions into prismaContract(...) |
packages/1-framework/3-tooling/cli/src/control-api/operations/contract-emit.ts |
Pass stack.extensionPacks as composedExtensionPackRefs in sourceContext |
packages/1-framework/1-core/config/src/contract-source-types.ts |
Optionally extend ContractSourceContext with composedExtensionPackRefs |
packages/2-sql/2-authoring/contract-psl/src/provider.ts |
Prefer context.composedExtensionPackRefs when present, fall back to static options |
| Tests |
Extend packages/3-extensions/postgres/test/config/define-config.test.ts; add integration test via real config emit (not direct interpreter call) |
Do not add GiST/BM25 to core. Per ADR 210, index types belong on extension packs; the framework should thread pack refs so PSL validation sees them.
Acceptance criteria
- PSL config with
extensions: [ltreeControl] + @@index([path], type: "gist") emits GiST index IR (same as TS lane).
- ParadeDB PSL
@@index(..., type: "bm25", options: { key_field: "id" }) works through standard @prisma-next/postgres/config — not only via direct interpreter tests.
- No duplicate/conflicting registration when pack refs are threaded correctly.
Motivating consumers
- prisma-ltree — GiST indexes on
ltree columns (ADR-005, extension side complete on TS lane)
- ParadeDB extension — BM25 full-text indexes (already has direct interpreter tests; config path gap)
References
- ADR 210 — Index-type registry (
docs/architecture docs/adrs/ADR 210 - Index-type registry.md)
- PSL provider:
packages/2-sql/2-authoring/contract-psl/src/provider.ts (composedExtensionPackRefs option)
- Stub fallback:
packages/2-sql/2-authoring/contract-psl/src/interpreter.ts (buildComposedExtensionPackRefs)
- Postgres
defineConfig gap: packages/3-extensions/postgres/src/config/define-config.ts
- CLI emit:
packages/1-framework/3-tooling/cli/src/control-api/operations/contract-emit.ts
Summary
PSL contract authoring does not validate extension-registered index types when consumers use the standard
@prisma-next/postgres/configdefineConfigpath. Authors seeunregistered index type "<type>"even when the extension pack correctly registers the type viadefineIndexTypes()and ADR 210.This affects all extension
indexTypesin the PSL lane, not a single extension:@@index(..., type: "bm25", options: { key_field: "id" })@@index(..., type: "gist")(GiST forltree/ltree[]columns)The TypeScript lane works because
defineContract({ extensionPacks: { … } })carries full pack refs includingindexTypes. The PSL lane fails because pack refs are never threaded into the PSL provider.Root cause
Index-type validation runs at contract lowering from a per-contract registry built from attached packs'
indexTypes(ADR 210).TS lane:
defineContract({ extensionPacks: { ltree } })→ registry includesgist. ✅PSL lane:
@prisma-next/postgresdefineConfigcallsprismaContract(path, { output, target, createNamespace })withoutcomposedExtensionPackRefs.executeContractEmitpassescomposedExtensionPacks: stack.extensionPacks.map(p => p.id)(IDs only) inContractSourceContext— no field for pack refs.buildComposedExtensionPackRefs()substitutes stub refs (id/kind only, noindexTypes) when real refs are absent.type: "gist"(orbm25) →unregistered index type.The PSL provider already accepts
composedExtensionPackRefs(packages/2-sql/2-authoring/contract-psl/src/provider.ts). Integration tests pass refs directly tointerpretPslDocumentToSqlContract(e.g.test/integration/test/authoring/psl-index-type-options.integration.test.tswith ParadeDB) — bypassingdefineConfig.Reproduction
@prisma-next/postgres/configwithextensions: [paradedbControl](or any pack withindexTypes).prisma-next contract emit.unregistered index type "bm25".Direct interpreter tests with
composedExtensionPackRefs: [paradedbPack]pass; standard config emit does not.Proposed fix
Wire extension pack refs (which carry
indexTypesper ADR 210) into the PSL authoring path:packages/3-extensions/postgres/src/config/define-config.tscomposedExtensionPackRefs: extensionsintoprismaContract(...)packages/1-framework/3-tooling/cli/src/control-api/operations/contract-emit.tsstack.extensionPacksascomposedExtensionPackRefsinsourceContextpackages/1-framework/1-core/config/src/contract-source-types.tsContractSourceContextwithcomposedExtensionPackRefspackages/2-sql/2-authoring/contract-psl/src/provider.tscontext.composedExtensionPackRefswhen present, fall back to static optionspackages/3-extensions/postgres/test/config/define-config.test.ts; add integration test via real config emit (not direct interpreter call)Do not add GiST/BM25 to core. Per ADR 210, index types belong on extension packs; the framework should thread pack refs so PSL validation sees them.
Acceptance criteria
extensions: [ltreeControl]+@@index([path], type: "gist")emits GiST index IR (same as TS lane).@@index(..., type: "bm25", options: { key_field: "id" })works through standard@prisma-next/postgres/config— not only via direct interpreter tests.Motivating consumers
ltreecolumns (ADR-005, extension side complete on TS lane)References
docs/architecture docs/adrs/ADR 210 - Index-type registry.md)packages/2-sql/2-authoring/contract-psl/src/provider.ts(composedExtensionPackRefsoption)packages/2-sql/2-authoring/contract-psl/src/interpreter.ts(buildComposedExtensionPackRefs)defineConfiggap:packages/3-extensions/postgres/src/config/define-config.tspackages/1-framework/3-tooling/cli/src/control-api/operations/contract-emit.ts