Skip to content

tml-2956: PSL attribute-spec kit + @relation migration (1/3)#891

Open
SevInf wants to merge 26 commits into
mainfrom
tml-2956-typed-attribute-parsers
Open

tml-2956: PSL attribute-spec kit + @relation migration (1/3)#891
SevInf wants to merge 26 commits into
mainfrom
tml-2956-typed-attribute-parsers

Conversation

@SevInf

@SevInf SevInf commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What this is

Slice 1 of 3 implementing ADR 231 — Declarative attribute specifications, scoped to the family interpreters. It stands up the declarative-attribute engine in psl-parser and proves it end-to-end by migrating the SQL @relation attribute off its hand-written parser.

Language-server integration (completion, go-to-definition over attribute arguments) is the follow-up this unblocks — and the reason the work lands under the Language Tools Support Prisma Next PSL project — but is not in scope here.

Why

Each family interpreter validated attribute arguments with hand-written code that pulled raw text out of the AST and re-checked its shape — split(',') on field lists, ad-hoc quoted-string unwrapping, per-attribute unknown-argument rejection — duplicated across the SQL and Mongo interpreters. The output type drifted freely from the validation, and no other consumer (the language server) could read what an attribute accepts. ADR 231 replaces that with one declarative AttributeSpec per attribute, read by interpretAttribute, with the output type inferred from the spec.

What's in this slice

  • The engine + core types in psl-parserArgType, AttributeSpec, optional/fieldAttribute, interpretAttribute, InferAttr, InterpretCtx — consuming the parser's ExpressionAst CST directly.
  • The combinators @relation needsstr, enumOf, fieldRef(scope), list({ nonEmpty, unique }), plus a small identifierName leaf for bare-identifier enum values.
  • SQL @relation migrated to a declarative sqlRelation spec lowered through interpretAttribute; the hand-written parseRelationAttribute is deleted.

Key decisions

  • Combinators consume ExpressionAst directly — no new intermediate argument representation. The CST already carries native literals and real spans.
  • The kit lives in psl-parser, which already owns ExpressionAst and the SymbolTable. No new package.
  • Diagnostic-code parity via InterpretCtx.diagnosticCode — the engine threads the spec's code (here PSL_INVALID_RELATION_ATTRIBUTE) to every leaf, so codes stay byte-identical.
  • onDelete/onUpdate keep normalizeReferentialAction as validator — they parse to the raw identifier name (no parse-time set check), preserving the existing PSL_UNSUPPORTED_REFERENTIAL_ACTION code + span. enumOf is intentionally not used for them.
  • Parity bar (this project): contract output and diagnostic codes are byte-identical; spans are no-coarser (the kit's per-argument anchoring narrows three @relation leaf spans — strictly more precise, exercised by no fixture); messages may adopt the kit's phrasing; malformed inputs legacy tolerated by coincidence (a quoted onDelete: "Cascade") are now rejected. The span + message + stricter-rejection relaxations were explicitly ratified.

Deliberately out of scope

Other SQL attributes (@id, @unique, @@index, @default, @map, @@control, @@discriminator, @@base) → slice 2. All Mongo attributes → slice 3. The rest of ADR 231's combinator alphabet (int, bool, json, map, record, entityRef, codecRef, oneOf, funcCall, modelAttribute, blockAttribute) is added as later slices need it. @db.* native types and the TypeScript builder surface are out of the whole project.

Validation

  • psl-parser: 504 tests pass (engine + combinators + type-level inference); typecheck + lint clean.
  • @prisma-next/sql-contract-psl: 271 tests pass — the @relation relations + diagnostics suites pass with zero test edits.
  • pnpm fixtures:check clean (no contract/migration drift); cross-package pnpm typecheck 143/143.

Refs: TML-2956 · ADR 231 (#889).

Summary by CodeRabbit

  • New Features
    • Added new PSL attribute-spec combinators (string, identifier, lists with nonEmpty/unique, optional values, and oneOf alternatives), plus field-level helpers and public exports to support authoring/interpretation.
    • Relation attribute parsing now uses these declarative attribute specs for consistent handling.
  • Bug Fixes
    • Improved span-aware, code-specific diagnostics for invalid, missing, and duplicate relation/attribute arguments.
    • Simplified referential action handling with clearer, unified error reporting.
  • Tests
    • Added/updated type-level and runtime test coverage for attribute combinators and relation interpretation.
  • Documentation
    • Updated the ADR to reflect oneOf(identifier(...)) usage.

SevInf added 7 commits June 29, 2026 18:01
…nference

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Add diagnosticCode to InterpretCtx and have interpretAttribute populate
a leaf-facing context from the spec before each leaf parse, so a leaf
emits with the attribute code rather than a hard-coded generic.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Author the domain combinators @relation needs as ArgTypes over the
expression AST, beside the attribute-spec engine, and export them from
the package surface. enumOf types a homogeneous or mixed string/number
set via a const tuple and a membership type-guard (no arktype: the value
is already a parsed primitive and the check is a trivial closed set).
fieldRef returns the bare name and carries scope metadata, deliberately
leaving field-existence validation to the downstream interpreter. list
lifts an element combinator over an array literal with nonEmpty/unique.
Leaves emit through a shared helper stamped with ctx.diagnosticCode.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…fine specs

The SQL @relation spec parses bare-identifier referential actions
(onDelete: Cascade) whose set is validated downstream, so it needs a
leaf that reads an IdentifierAst to its name with no parse-time check —
fieldRef minus the resolution scope. Add identifierName() and export it.

InferAttr previously constrained its parameter to AttributeSpec<unknown>,
but Out sits contravariantly in AttributeSpec.refine, so any refine-
carrying spec is not assignable to AttributeSpec<unknown> and the
constraint rejected it. Drop the constraint; inference still recovers Out
precisely. Adds a type-test for a refine-carrying spec.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ttribute spec

Replace the hand-written parseRelationAttribute with a sqlRelation
AttributeSpec interpreted through the kit. The engine binds the
positional-or-named name, fields/references field lists, map, and the
bare-identifier onDelete/onUpdate; a refine holds the fields/references
both-or-neither rule. Referential-action set validation stays downstream
in normalizeReferentialAction (unchanged), preserving its code and span.

interpretRelationAttribute assembles the InterpretCtx from interpreter
state (declaring model, field, symbol table, source file) and maps the
inferred output onto ParsedRelationAttribute. The three @relation call
sites in interpreter.ts route through it; BuildModelNodeInput gains the
source file and symbol table to assemble the context.

Diagnostic codes and spans stay byte-identical for every @relation error
path: the relations, many-to-many, and diagnostics suites pass unchanged
and fixtures:check is clean. parseRelationAttribute is deleted; the
helpers it used remain (other callers).

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…n, review, briefs)

Drive project/slice paper trail for the attribute-spec-kit slice: project spec + plan, slice spec + dispatch plan, dispatch briefs (D2/D3), and the code-review log. Transient project artifacts (removed at project close); no source references them.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf SevInf requested a review from a team as a code owner June 30, 2026 07:21
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds attribute-spec parsing primitives and type inference utilities, re-exports them from psl-parser, migrates SQL @relation parsing to the declarative parser, and adds new utility types plus their public export surface in the foundation utils package.

Changes

Attribute-spec framework and relation migration

Layer / File(s) Summary
Core type contracts
packages/1-framework/2-authoring/psl-parser/src/attribute-spec/types.ts, packages/1-framework/2-authoring/psl-parser/src/attribute-spec/optional.ts
Defines the attribute-spec type system, interpretation context, optional parameter helper, and output inference utilities.
Leaf combinators
packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/diagnostic.ts, .../str.ts, .../identifier.ts, .../one-of.ts, .../field-ref.ts, .../list.ts
Implements the diagnostic helper and the leaf combinators for strings, identifiers, alternatives, field references, and lists.
fieldAttribute and interpretAttribute
packages/1-framework/2-authoring/psl-parser/src/attribute-spec/field-attribute.ts, packages/1-framework/2-authoring/psl-parser/src/attribute-spec/interpret.ts
Adds the field-level attribute builder and the interpreter that parses positional and named arguments, applies defaults, and runs refinement.
Public API exports and parser tests
packages/1-framework/2-authoring/psl-parser/src/exports/index.ts, packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test.ts, packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test-d.ts, packages/1-framework/2-authoring/psl-parser/test/attribute-spec.test.ts, packages/1-framework/2-authoring/psl-parser/test/attribute-spec.test-d.ts
Re-exports the new parser API and adds runtime and type-level tests for combinators, attribute interpretation, and inference.
@relation migration to declarative spec
packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts, packages/2-sql/2-authoring/contract-psl/src/interpreter.ts, packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.test.ts, docs/architecture docs/adrs/ADR 231 - Declarative attribute specifications.md
Replaces hand-written relation parsing with a declarative spec, updates interpreter call sites and diagnostics, and adjusts tests and ADR text.

Foundation utility type exports

Layer / File(s) Summary
Utility types and export wiring
packages/1-framework/0-foundation/utils/src/types.ts, packages/1-framework/0-foundation/utils/src/exports/types.ts, packages/1-framework/0-foundation/utils/package.json, packages/1-framework/0-foundation/utils/tsdown.config.ts, packages/1-framework/0-foundation/utils/test/types.test-d.ts
Defines Simplify and UnionToIntersection, re-exports them through the package entrypoint, and adds type tests plus build entry wiring.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • wmadden
  • jkomyno

Poem

🐇 I hopped through types and leaves today,
With oneOf lighting the parser’s way.
fieldRef and list went thump in line,
And @relation now parses just fine.
The moon says “nice,” the carrots agree,
This rabbit’s code sings: declarative and free ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.28% 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 title clearly summarizes the main change: the PSL attribute-spec kit and the @relation migration slice.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2956-typed-attribute-parsers

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.

@pkg-pr-new

pkg-pr-new Bot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@891

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@891

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@891

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@891

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@891

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 48bcda1

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.35 KB (0%)
postgres / emit 147.55 KB (0%)
mongo / no-emit 79.86 KB (0%)
mongo / emit 72.68 KB (0%)
cf-worker / no-emit 188.11 KB (0%)
cf-worker / emit 173.57 KB (0%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@packages/1-framework/2-authoring/psl-parser/src/attribute-spec/interpret.ts`:
- Around line 40-56: The named-argument loop in interpret should reject
duplicate keys instead of skipping them in silence. Update the duplicate check
around namedSeen in the attribute-spec parsing logic so that a repeated key
emits a structural diagnostic for the attribute and does not proceed as a
successful parse. Use the existing diagnostic pattern in interpret, and keep the
behavior aligned with the unknown-argument handling for spec.name and namedArgs.

In `@packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts`:
- Around line 141-142: The relation list schema still permits duplicate column
names, which can let repeated FK entries flow into foreign key resolution.
Update the `fields` and `references` definitions in `psl-relation-resolution` to
enforce uniqueness by adding the list-level uniqueness constraint alongside the
existing `nonEmpty` validation. Keep the change localized to the relation
parsing schema so `foreignKeyNodes` only receives deduplicated names.
🪄 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: 32be81a6-69d0-4823-9438-44961008ee55

📥 Commits

Reviewing files that changed from the base of the PR and between eba2814 and de79343.

⛔ Files ignored due to path filters (6)
  • projects/typed-attribute-parsers/plan.md is excluded by !projects/**
  • projects/typed-attribute-parsers/slices/attribute-spec-kit/dispatches/02-combinators.md is excluded by !projects/**
  • projects/typed-attribute-parsers/slices/attribute-spec-kit/dispatches/03-migrate-relation.md is excluded by !projects/**
  • projects/typed-attribute-parsers/slices/attribute-spec-kit/plan.md is excluded by !projects/**
  • projects/typed-attribute-parsers/slices/attribute-spec-kit/spec.md is excluded by !projects/**
  • projects/typed-attribute-parsers/spec.md is excluded by !projects/**
📒 Files selected for processing (17)
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/diagnostic.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/enum-of.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/field-ref.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/identifier-name.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/list.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/str.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/field-attribute.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/interpret.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/optional.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/types.ts
  • packages/1-framework/2-authoring/psl-parser/src/exports/index.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test-d.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec.test-d.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec.test.ts
  • packages/2-sql/2-authoring/contract-psl/src/interpreter.ts
  • packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts

Comment thread packages/1-framework/2-authoring/psl-parser/src/attribute-spec/interpret.ts Outdated
Comment thread packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts Outdated
* action routed to its normaliser — so a parse-time check would emit a second
* diagnostic for the same fault. Like `fieldRef` minus the resolution scope.
*/
export function identifierName(): ArgType<string> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why do we need this? Shouldn't referential action be useable via enumOf already?

if (!(arg instanceof ArrayLiteralAst)) {
return notOk([leafDiagnostic(ctx, arg, `Expected a list of ${of.label}`)]);
}
const elements = [...arg.elements()];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why are we copying the list?

const hasNamed = namedParsed.has(key);
if (
hasPositional &&
hasNamed &&

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just don't allow duplicate arguments, it does not matter if their value is the same

if (result.ok) namedParsed.set(key, result.value);
}

const positionalSeen = new Set<string>();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is way to complicated for what it is supposed to be doing? Why are we doing one pass over positional args, one pass over named arguments, then merging the two in a separate pass? Just produce a single map in one pass and report duplicates as you go.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is not addressed. Parsing is still done in 3 passes. With a separate maps for positional and named arguments, each bracnh handling it's duplication code, plus merging the 2 handling merge as well.

I want both positional and named passes to write intou output directly without intermediate maps and merge step. Named arg can conflict with positional arg key and it should produce an error.

Comment on lines +8 to +14
type Simplify<T> = { [K in keyof T]: T[K] } & {};

type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am pretty sure we already have these helpers defined in several places. Reuse them

readonly key: string;
readonly type: Param<T>;
/** A trailing rest slot that consumes every remaining positional argument. */
readonly variadic?: boolean;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do we really have variadic positional arguments anywhere?

* default when the argument is absent; without one, an absent argument leaves
* the output property unset.
*/
export function optional<T>(type: ArgType<T>, ...rest: [defaultValue: T] | []): OptionalParam<T> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why isn't this an ArgType on it's own?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this was not addressed

* The engine aggregates every error path's diagnostics; like the previous
* first-error parser, the caller skips the field when this returns `undefined`.
*/
export function interpretRelationAttribute(input: {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The only thing this function does is renaming map to constraintName? Remove it and use interpretAttribute result directly. Same is true for mongo code.

SevInf added 6 commits June 30, 2026 09:37
A named key supplied twice, or a key supplied both positionally and by
name, is now always a structural error — previously a duplicate named key
was silently dropped and a positional/named alias only conflicted when the
values differed. Drop the now-dead argValuesEqual/isPlainRecord helpers.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
list() spread arg.elements() into a second array only to read its length
for the nonEmpty check. Iterate the generator once and track a count
instead; behaviour is identical.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…eferences

Mark the @relation fields and references lists unique:true so a repeated
FK column name is rejected at parse and can never reach foreignKeyNodes.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Extend enumOf to accept a bare identifier matching a string member, then
route @relation onDelete/onUpdate through enumOf instead of the bespoke
identifierName leaf. A bad action now errors at parse with the attribute
code (PSL_INVALID_RELATION_ATTRIBUTE) rather than downstream
PSL_UNSUPPORTED_REFERENTIAL_ACTION; normalizeReferentialAction becomes a
pure token-to-action mapper. Delete identifierName and its export.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
fieldRef now resolves a field name against its scoped model — self against
the declaring model, referenced against the relation target — and emits the
field-existence diagnostic at parse with the attribute code. A referenced
target out of scope (cross-space) is still carried through unchecked. For
@relation this short-circuits the parse before the downstream column
resolution, so a missing field yields exactly one diagnostic; the
downstream mapping is retained for column lookup.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…rity flags

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts (1)

128-129: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve duplicate relation-list behavior.

Adding unique: true rejects duplicate names in fields/references, which tightens behavior relative to the legacy relation parser; the downstream test at packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.test.ts Lines 389-417 now codifies that stricter behavior. Remove the uniqueness constraint here.

Proposed adjustment
-    fields: optional(list(fieldRef('self'), { nonEmpty: true, unique: true })),
-    references: optional(list(fieldRef('referenced'), { nonEmpty: true, unique: true })),
+    fields: optional(list(fieldRef('self'), { nonEmpty: true })),
+    references: optional(list(fieldRef('referenced'), { nonEmpty: true })),
🤖 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 `@packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts`
around lines 128 - 129, The relation-list validation in psl-relation-resolution
should preserve the legacy ability to repeat names, so remove the unique: true
constraint from both fields and references in the relation parser schema. Update
the schema definition around the fields/references list handling in
psl-relation-resolution so it only enforces nonEmpty where needed, and keep the
existing relation resolution flow unchanged. Use the existing identifiers
optional(list(...)), fieldRef('self'), and fieldRef('referenced') to locate the
exact validation rules to adjust.
🤖 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 `@packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts`:
- Around line 87-88: The referential-action validation in
normalizeReferentialAction is now bypassed by enumOf, causing unsupported
onDelete/onUpdate values to surface as PSL_INVALID_RELATION_ATTRIBUTE instead of
the legacy unsupported-action diagnostic. Restore the old diagnostic parity by
moving the unsupported-value check into the downstream normalizer path used by
normalizeReferentialAction, or add a per-argument diagnostic override in the
relation attribute parsing flow so the specific unsupported referential-action
code is preserved.

---

Duplicate comments:
In `@packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts`:
- Around line 128-129: The relation-list validation in psl-relation-resolution
should preserve the legacy ability to repeat names, so remove the unique: true
constraint from both fields and references in the relation parser schema. Update
the schema definition around the fields/references list handling in
psl-relation-resolution so it only enforces nonEmpty where needed, and keep the
existing relation resolution flow unchanged. Use the existing identifiers
optional(list(...)), fieldRef('self'), and fieldRef('referenced') to locate the
exact validation rules to adjust.
🪄 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: f5ca7493-831e-4d57-aaed-4fb80a8a2ce5

📥 Commits

Reviewing files that changed from the base of the PR and between de79343 and 7da937f.

⛔ Files ignored due to path filters (2)
  • projects/typed-attribute-parsers/plan.md is excluded by !projects/**
  • projects/typed-attribute-parsers/slices/attribute-spec-kit/dispatches/04-address-review-r1.md is excluded by !projects/**
📒 Files selected for processing (10)
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/enum-of.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/field-ref.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/list.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/interpret.ts
  • packages/1-framework/2-authoring/psl-parser/src/exports/index.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec.test.ts
  • packages/2-sql/2-authoring/contract-psl/src/interpreter.ts
  • packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts
  • packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.test.ts
💤 Files with no reviewable changes (1)
  • packages/1-framework/2-authoring/psl-parser/src/exports/index.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/field-ref.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/enum-of.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/list.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec.test.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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
`@packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/one-of.ts`:
- Around line 16-19: The oneOf() combinator currently accepts zero alternatives,
which allows construction of an impossible ArgType<never> that only fails at
runtime. Update the oneOf function signature in one-of.ts to require a non-empty
rest tuple for alts, so callers must pass at least one ArgType; keep the
existing label-building logic in oneOf and ensure the type still resolves
through OutOf<Alts[number]> for valid non-empty inputs.
🪄 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: 8f4b5a02-bb68-4926-8296-3e5eee0629d8

📥 Commits

Reviewing files that changed from the base of the PR and between 7da937f and a9106ed.

⛔ Files ignored due to path filters (2)
  • projects/typed-attribute-parsers/plan.md is excluded by !projects/**
  • projects/typed-attribute-parsers/slices/attribute-spec-kit/dispatches/06-oneof-identifier.md is excluded by !projects/**
📒 Files selected for processing (6)
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/identifier.ts
  • packages/1-framework/2-authoring/psl-parser/src/attribute-spec/combinators/one-of.ts
  • packages/1-framework/2-authoring/psl-parser/src/exports/index.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test-d.ts
  • packages/1-framework/2-authoring/psl-parser/test/attribute-spec-combinators.test.ts
  • packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts

…literals

enumOf no longer accepts a quoted string as a member. A string member is matched only when written as a bare identifier (e.g. Cascade); a quoted argument is rejected with the leaf diagnostic. Number members continue to match number literals.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
SevInf added 12 commits June 30, 2026 16:48
… string-set follow-up

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Introduce oneOf(...alts) (ordered try-each, union output type, single
aggregate diagnostic on total failure) and identifier(name) (pinned
bare-identifier matcher with a literal output type). Rewire the relation
attribute onDelete/onUpdate from enumOf to oneOf(identifier(...)),
leaving the referential-action output union and normalizeReferentialAction
mapping unchanged. Delete enumOf now that its behaviour is composed from
the two primitives.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ag ADR 231 reconcile at close-out

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Replace the bespoke enumOf combinator throughout ADR 231 with the
shipped composition: enums are oneOf over per-member matchers --
identifier(name) for bare identifiers, pinned str(value)/num(value)
for quoted-string/number members. Updates the relation code sample,
the at-a-glance and language-server narratives, the scalars kit
description, and the ADR 224 reference line; the rejected-alternative
entry now records the dedicated enum leaf as the rejected option in
favour of oneOf composition (principle #4). InferAttr union types are
unchanged.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Add a shared @prisma-next/utils/types module and consume it from the
attribute-spec types instead of redefining the helpers locally.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Type the rest parameter as a non-empty tuple so oneOf() with no
alternatives is a compile error.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
No attribute uses a variadic positional slot. Drop PositionalParam.variadic,
the variadic branch in the output-type mapping, and the engine handling. YAGNI.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Replace OptionalParam with OptionalArgType extending ArgType, collapsing
the Param union to ArgType. optional() now spreads the wrapped type and
adds the optionality marker; the engine detects optionality from the
marker and parses the param directly.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Replace the positional/named/resolve three-pass engine with one pass over
the source arguments that builds the output map and reports duplicates,
unknowns, and excess positionals inline, then applies optional defaults and
missing-required diagnostics. Preserves diagnostic codes and keeps each
error path’s span no coarser than before.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Drop the ParsedRelationAttribute rename wrapper that mapped name->relationName
and map->constraintName. interpretRelationAttribute now returns the spec
output (SqlRelationOutput); the interpreter reads name and map directly.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ification)

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf SevInf force-pushed the tml-2956-typed-attribute-parsers branch from b4613a1 to 48bcda1 Compare June 30, 2026 16:48
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