Skip to content

feat(target-postgres): class-flow migration authoring#359

Merged
wmadden merged 59 commits intomainfrom
tml-2286-backport-class-flow-migration-authoring-to-the-postgres
Apr 21, 2026
Merged

feat(target-postgres): class-flow migration authoring#359
wmadden merged 59 commits intomainfrom
tml-2286-backport-class-flow-migration-authoring-to-the-postgres

Conversation

@wmadden
Copy link
Copy Markdown
Contributor

@wmadden wmadden commented Apr 21, 2026

closes TML-2286

Intent

Bring the Postgres target up to the class-flow migration authoring model established for Mongo in ADR 193 / 194 / 195. The planner's output becomes a single thing — a TypeScriptRenderablePostgresMigration that carries readonly PostgresOpFactoryCall[] and exposes both views: get operations (runtime ops, via each call's toOp()) and renderTypeScript() (the migration.ts scaffold). Walk-schema (the planner behind db update) is retargeted to emit the new IR; Mongo is brought to structural parity so the shared TsExpression / jsonToTsSource machinery can live in one framework package.

No user-visible CLI change. The issue planner (migration plan), the CLI scaffolder, migrationStrategy, and Mongo's planner/CLI wiring are untouched — they remain scope for later phases.

Change map

  • New framework package — @prisma-next/ts-render. TsExpression abstract base + ImportRequirement + jsonToTsSource (pure JSON-to-TS printer). Single source of truth shared by both targets.
  • Family-SQL Migration alias. @prisma-next/family-sql/migration now re-exports SqlMigration<TDetails extends SqlPlanTargetDetails>. Authored migrations write class M extends Migration { … }; concrete target subclasses extend the same alias.
  • Pure Postgres op factories (Phase 0). operation-resolver.ts is sliced into context-free factories under packages/3-targets/3-targets/postgres/src/core/migrations/operations/{tables,columns,constraints,indexes,enums,dependencies,raw,data-transform}.ts. Each factory takes literal args and returns one SqlMigrationPlanOperation.
  • Postgres class-flow IR (Phase 1). One *Call class per factory (CreateTableCall, AddColumnCall, …, plus RawSqlCall, CreateExtensionCall, CreateSchemaCall, DataTransformCall) under op-factory-call.ts. Each class is frozen, carries its literal args, and implements toOp(), renderTypeScript(), and importRequirements() polymorphically.
  • Two renderers, one IR. renderOps(calls) = calls.map(c => c.toOp()); renderCallsToTypeScript(calls, meta) walks the list polymorphically and aggregates imports per-module. No visitor boilerplate.
  • Retargeted walk-schema planner. planSql accumulates a readonly PostgresOpFactoryCall[] and returns TypeScriptRenderablePostgresMigration. planner-reconciliation.ts emits call classes per SchemaIssue kind. Pre-materialized ops from codec hooks and component database dependencies are laundered at the boundary through a RawSqlCall wrapper so the public output stays uniform.
  • Mongo IR sync. Same TsExpression base, same polymorphic toOp(), no visitor. Byte-identical to pre-change rendered output.
  • Project docs. projects/postgres-class-flow-migrations/ carries the spec, plan, phase specs, a walk-schema audit (every walk-schema branch → SchemaIssue kind → absorbable-by-Phase-4 verdict), and the PR review artifacts.

Before / After (planner return shape)

Before:

// plan()
{ kind: 'success', plan: { operations, renderTypeScript() { throw errorPlanDoesNotSupportAuthoringSurface(...) } } }

// emptyMigration()
{ targetId: 'postgres', operations: [], renderTypeScript() { return renderDescriptorTypeScript(...) } }

After:

// plan()
{ kind: 'success', plan: new TypeScriptRenderablePostgresMigration(calls, meta) }

// emptyMigration()
new TypeScriptRenderablePostgresMigration([], { from, to })

// Both expose the same authoring surface.
// .operations calls toOp() on each call; .renderTypeScript() emits the class-flow scaffold.

Behavior changes & evidence

A. New @prisma-next/ts-render framework package

Pure JSON-to-TS printing + the TsExpression base. The printer accepts JSON-compatible values; non-JSON values (Date, Vector, BigInt) are a codec responsibility — the docstring explicitly names this as the scope boundary.

B. Framework exposes OpFactoryCall as the cross-target IR vocabulary

A three-member interface (factoryName, operationClass, label) any target's class-flow IR satisfies — no abstract base at framework level.

C. Postgres pure op factories

Context-free; one op per call; zero dependencies on @prisma-next/framework-components or @prisma-next/contract. operation-resolver.ts is now a thin materialization layer over these.

D. Postgres class-flow IR — one call class per factory

Every *Call class is frozen at construction, carries literal args, delegates to the matching pure factory in toOp(), and renders itself via jsonToTsSource. DataTransformCall is the planner-stub shape: its toOp() always throws PN-MIG-2001 (unfilled placeholder) because the planner can only stub, not lower.

E. TypeScriptRenderablePostgresMigration — planner-produced authoring surface

Holds PostgresOpFactoryCall[] + MigrationMeta; implements get operations, describe(), renderTypeScript(). Extends family-level SqlMigration<PostgresPlanTargetDetails>, implements MigrationPlanWithAuthoringSurface.

F. Walk-schema planner retargeting — uniform PostgresOpFactoryCall[] output

Every buildX method returns readonly PostgresOpFactoryCall[]. Pre-materialized ops from codec hooks, component database dependencies, and the atomic add-NOT-NULL-with-temporary-default recipe are wrapped in RawSqlCall at the planner boundary so the accumulator is pure. The external MigrationPlannerResult shape is unchanged; all prior planner integration/behavior/storage-types/reconciliation tests pass unmodified.

G. End-to-end authoring-surface invariant

A tsx-executing round-trip test renders the class-flow TypeScript, writes it to a tempdir, executes it, reads the resulting ops.json, and asserts it equals renderOps(calls). Also pins byte-preservation for RawSqlCall ops through the round trip.

H. Mongo structural parity

OpFactoryCallNode extends the shared TsExpression; each of the five concrete call classes implements toOp() / renderTypeScript() polymorphically. The old renderCallVisitor, collectFactoryNames, and OpFactoryCallVisitor are deleted. Existing Mongo snapshot / e2e tests are the byte-identity gate.

I. Family-SQL Migration alias

@prisma-next/family-sql/migration re-exports SqlMigration<TDetails extends SqlPlanTargetDetails>. Mirrors @prisma-next/family-mongo/migration. Concrete target migration classes extend this alias rather than @prisma-next/migration-tools/migration directly.

Compatibility / migration / risk

  • No CLI or user-API change. migration plan, migration new, the CLI scaffolder, migrationStrategy, and capability wiring are untouched. Walk-schema's external op output is preserved byte-for-byte (verified by unchanged integration/behavior tests).
  • DataTransformCall.toOp() throws by design. The planner only stubs data transforms; the user must fill the rendered migration.ts and re-emit. The walk-schema planner never emits DataTransformCall today, so the throw is reserved for the Phase 2 issue-planner integration.
  • RawSqlCall author-facing UX is verbose. Authors writing hand-coded raw SQL migrations must pass a full op shape (id, label, operationClass, target, precheck, execute, postcheck). A lighter rawSql({ sql, label, operationClass }) helper is tracked as a Phase 2/3 polish.

Follow-ups / open questions

  • Phase 2 — retarget the issue planner (migration plan) to emit class-flow IR. The walk-schema audit under projects/postgres-class-flow-migrations/assets/walk-schema-audit.md maps every walk-schema branch to a SchemaIssue kind and names the handful of issue-union extensions needed to absorb it.
  • Phase 3 — flip the CLI scaffolder to class-flow for migration new.
  • rawSql(...) author-facing helper (see compatibility note above).
  • Walk-schema audit minor corrections. Two rows describe liftOpToCall as if it dispatched on op shape (it always returns RawSqlCall), and one references a non-existent CreateTypeCall (should be CreateEnumTypeCall). Cosmetic; tracked in the re-review doc.

Non-goals / intentionally out of scope

  • Issue planner retarget (Phase 2).
  • CLI scaffolder flip (Phase 3).
  • Mongo planner / CLI changes (only the IR + renderer shape).
  • SQLite third-copy lift — @prisma-next/ts-render exists specifically so a future SQLite target reuses the same machinery without another copy landing.

Summary by CodeRabbit

  • New Features

    • Class-based TypeScript migration authoring for Postgres producing renderable migration files.
    • New TypeScript source-rendering utilities to emit literal TS from JSON-like inputs.
    • Expanded migration export surfaces to include migration entrypoints for SQL-family targets.
  • Refactoring

    • Planner pipeline unified to emit structured operation "calls" that lower to runtime operations and TypeScript.
  • Tests

    • Extensive unit and end-to-end tests for call IR, rendering, and round-trip execution.
  • Documentation

    • README for the TS rendering utilities.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

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 a class-flow migration IR (OpFactoryCall) and a small ts-render library; implements polymorphic op-factory call classes for Postgres and Mongo with toOp()/renderTypeScript()/importRequirements(); introduces pure SQL-family operation factories; refactors planners/reconciliation to emit call IR and deterministic TypeScript authoring.

Changes

Cohort / File(s) Summary
Framework contracts
packages/1-framework/1-core/framework-components/src/control-migration-types.ts, packages/1-framework/1-core/framework-components/src/exports/control.ts
Added exported OpFactoryCall interface (factoryName, operationClass, label) and re-exported it from control.
TS rendering core (new package)
packages/1-framework/1-core/ts-render/*
New ts-render package: TsExpression/ImportRequirement abstractions, jsonToTsSource printer, package manifest, configs, tests, and entry re-exports.
SQL-family base
packages/2-sql/9-family/src/..., packages/2-sql/9-family/package.json, tsdown.config.ts
Added SqlMigration base class, SqlPlanTargetDetails type, migration export entry, and package ./migration export.
Mongo target: call IR & rendering
packages/3-mongo-target/1-mongo-target/src/core/op-factory-call.ts, .../render-typescript.ts, .../render-ops.ts, .../exports/control.ts, package.json, test/*
Replaced visitor with polymorphic toOp()/renderTypeScript() on calls; renderer now aggregates importRequirements() and builds imports deterministically; removed visitor export; tests adjusted; added @prisma-next/ts-render dependency.
Postgres: op-factory call IR
packages/3-targets/3-targets/postgres/src/core/migrations/op-factory-call.ts
Large new IR: concrete *Call classes (tables, columns, constraints, indexes, enums, extensions, schema, RawSqlCall, DataTransformCall), each implements toOp(), renderTypeScript(), importRequirements(); union type and identityKeyFor.
Postgres: pure operation factories
packages/3-targets/3-targets/postgres/src/core/migrations/operations/*
New pure factories producing Op objects with structured precheck/execute/postcheck and targetDetails for columns, constraints, enums, indexes, tables, dependencies, raw, shared, and data-transform operations.
Postgres: rendering & authoring surface
packages/3-targets/3-targets/postgres/src/core/migrations/render-typescript.ts, .../render-ops.ts, .../planner-produced-postgres-migration.ts
Added renderCallsToTypeScript() that aggregates imports + class scaffold, renderOps() lowering calls via toOp(), and TypeScriptRenderablePostgresMigration exposing operations and renderTypeScript().
Postgres: planner & reconciliation refactor
packages/3-targets/3-targets/postgres/src/core/migrations/{planner,planner-reconciliation,operation-resolver}.ts
Planner now emits PostgresOpFactoryCall[] and returns TypeScriptRenderablePostgresMigration; reconciliation builds call IR and deduplicates by identityKeyFor; resolver delegates to per-kind pure factories.
Postgres exports & config
packages/3-targets/3-targets/postgres/src/exports/migration.ts, package.json, tsdown.config.ts
Added migration re-exports of factories; package adds ./migration subpath and workspace deps; tsdown entries updated to include new export.
Tests & snapshots
packages/3-targets/.../test/*, packages/3-mongo-target/.../test/*, packages/1-framework/1-core/ts-render/test/*, other test updates
Extensive tests added/updated for call classes, rendering, round-trip execution, reconciliation mapping, and jsonToTsSource; minor snapshot/timeout adjustments.
Build graph
tsconfig.base.json
Added project reference for the new ts-render package.

Sequence Diagram(s)

sequenceDiagram
    participant Planner
    participant Call as OpFactoryCall
    participant Renderer
    participant ToOp as toOp()
    participant RuntimeOp as SqlMigrationPlanOperation
    participant TS as TypeScriptOutput

    Planner->>Call: construct *Call(...) and append to calls[]
    Planner->>Renderer: renderCallsToTypeScript(calls, meta)
    loop per call
        Renderer->>Call: call.importRequirements()
        Renderer->>Call: call.renderTypeScript()
    end
    Renderer->>TS: emit imports + class scaffold + operations body
    Planner->>Call: renderOps(calls)
    loop per call
        Call->>ToOp: call.toOp()
        ToOp->>RuntimeOp: produce SqlMigrationPlanOperation
    end
    RuntimeOp-->>Planner: operations[]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I stitched small calls into a flow,

factories hum and TypeScript glow,
Planner hops, renderer sings,
ops become neat exported things,
carrots of code—ready to go!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.32% 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 and concisely describes the main change: introducing class-flow migration authoring for the Postgres target. It is specific and directly reflects the PR's primary objective.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2286-backport-class-flow-migration-authoring-to-the-postgres

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 Apr 21, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/middleware-telemetry

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/runtime-executor

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/runtime-executor@359

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 67d28f7

Comment thread packages/1-framework/1-core/framework-components/src/control-migration-types.ts Outdated
Comment thread packages/1-framework/1-core/framework-components/src/control-migration-types.ts Outdated
Comment thread packages/2-sql/9-family/src/core/sql-migration.ts
Comment thread packages/3-mongo-target/1-mongo-target/src/core/migration-ts-expression.ts Outdated
Comment thread packages/3-mongo-target/1-mongo-target/src/core/render-literal.ts Outdated
Comment thread packages/3-targets/3-targets/postgres/src/core/migrations/op-factories.ts Outdated
Comment thread packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts Outdated
Comment thread packages/3-targets/3-targets/postgres/src/core/migrations/render-ops.ts Outdated
@wmadden wmadden marked this pull request as ready for review April 21, 2026 14:33
wmadden added a commit that referenced this pull request Apr 21, 2026
…hifts

Updates the project spec, phase-1 spec, plan, and PR plan to match the
shipped code after the PR #359 review addressed F05/W02/W04/W05/W07/W10
and related findings. Preserves the original phase-1 design narrative
with a post-implementation addendum at the top that names the
divergences:

- OpFactoryCall.factory is now factoryName (W02)
- TsExpression / ImportRequirement / jsonToTsSource live in a shared
  @prisma-next/framework-ts-render package, consumed by both targets
  (W04+W05); per-target MigrationTsExpression duplicates are gone
- DataTransformCall accepts checkSlot/runSlot strings directly, renders
  () => placeholder("slot") at its call site, and always throws
  PN-MIG-2001 from toOp() (W07 + simplification)
- Lowering calls to runtime ops is polymorphic via abstract toOp(); the
  PostgresOpFactoryCallVisitor interface and renderOps visitor are gone
  (W10)
- Pure factories live under operations/{tables,columns,...}.ts (W06)
- Factory inventory gained rawSql, createExtension, createSchema plus a
  liftOpToCall dispatcher (F05/F06)

Adds a follow-up for the family-API retarget (Path A) that eliminates
the liftOpToCall bridge before SQLite lands, and consolidates the
cross-target renderable-migration follow-up around the remaining
target-specific TypeScriptRenderable*Migration concrete classes.
@wmadden wmadden force-pushed the tml-2286-backport-class-flow-migration-authoring-to-the-postgres branch from cd73e18 to e38c0c5 Compare April 21, 2026 14:36
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts (1)

300-318: ⚠️ Potential issue | 🟠 Major

The success path still drops the authoring IR.

materializePlanItems(items) lowers every PostgresOpFactoryCall into runtime ops, and the returned plan still implements renderTypeScript() by throwing. That means non-empty Postgres plans still cannot serialize back to migration.ts, because the only authoring representation is discarded before it reaches callers. The new TypeScriptRenderablePostgresMigration path never becomes reachable from plan().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts` around
lines 300 - 318, The plan currently calls materializePlanItems(items) which
discards the authoring IR and causes renderTypeScript() to throw; instead
preserve the original factory-call items and return a TypeScript-renderable plan
when authoring is possible: stop replacing items with
materializePlanItems(items) in createMigrationPlan, keep the original items (or
store both materialized ops and the original items) and implement
renderTypeScript() to delegate to the TypeScriptRenderablePostgresMigration path
(or construct/return a TypeScriptRenderablePostgresMigration using the original
PostgresOpFactoryCall items); update the plan returned from plan() /
createMigrationPlan to include that renderable behavior instead of throwing.
🧹 Nitpick comments (2)
packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts (1)

235-244: Use ifDefined() for conditional object spreads.

The conditional spread pattern on lines 242-243 should use ifDefined() per coding guidelines. The utility is already imported on line 22.

As per coding guidelines: "Use ifDefined() from @prisma-next/utils/defined for conditional object spreads instead of inline conditional spread patterns"

♻️ Proposed fix
   const spec: ForeignKeySpec = {
     name: fkName,
     columns: fk.columns,
     references: {
       table: fk.references.table,
       columns: fk.references.columns,
     },
-    ...(fk.onDelete !== undefined && { onDelete: fk.onDelete }),
-    ...(fk.onUpdate !== undefined && { onUpdate: fk.onUpdate }),
+    ...ifDefined('onDelete', fk.onDelete),
+    ...ifDefined('onUpdate', fk.onUpdate),
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`
around lines 235 - 244, The ForeignKeySpec object uses inline conditional
spreads for onDelete/onUpdate; replace those spreads with ifDefined() calls
(imported from `@prisma-next/utils/defined`) when building the spec in the
operation-resolver: update the construction of spec (const spec: ForeignKeySpec)
to include ...(ifDefined({ onDelete: fk.onDelete })) and ...(ifDefined({
onUpdate: fk.onUpdate })) instead of the current ...(fk.onDelete !== undefined
&& { onDelete: fk.onDelete }) / ...(fk.onUpdate !== undefined && { onUpdate:
fk.onUpdate }) patterns so the conditional properties are added via ifDefined().
packages/3-targets/3-targets/postgres/src/core/migrations/op-factories.ts (1)

9-10: Documentation inconsistency with actual imports.

The header comment states "factories never import @prisma-next/framework-components" but lines 17-20 do import DataTransformOperation and SerializedQueryPlan from that package. Consider updating the comment to reflect the actual (and necessary) dependency for createDataTransform.

Also applies to: 17-20

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-targets/3-targets/postgres/src/core/migrations/op-factories.ts`
around lines 9 - 10, The file header incorrectly claims factories never import
`@prisma-next/framework-components` while this file imports DataTransformOperation
and SerializedQueryPlan and uses createDataTransform; update the top-of-file
comment to reflect that Phase 0 factories may import those specific types
(DataTransformOperation, SerializedQueryPlan) or remove the blanket prohibition
and mention the allowed dependency for createDataTransform so the doc matches
the actual imports and usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`:
- Line 397: The import for qualifyName is placed after function definitions in
operation-resolver.ts; move the "qualifyName" import from
'@prisma-next/adapter-postgres/control' into the top import block with the other
imports (or remove it if qualifyName is already imported earlier), and ensure
any usages of qualifyName still resolve to the single top-level import so there
are no duplicate or late-scope imports referencing the function.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/render-typescript.ts`:
- Around line 12-15: The file render-typescript.ts under src/core imports
migration-plane symbols (detectScaffoldRuntime, shebangLineFor from
`@prisma-next/migration-tools/migration-ts`) which violates the core vs
migration-plane split; move the scaffold emitter logic out of src/core into a
migration-plane module (e.g., src/exports/control.ts or a new file under
src/exports) so core no longer imports migration-ts. Specifically, extract the
code in render-typescript.ts that uses detectScaffoldRuntime and shebangLineFor
(and any functions that call renderLiteral or PostgresOpFactoryCall rendering
tied to migration output) into the new migration-plane file, update imports
there to `@prisma-next/migration-tools/migration-ts`, and leave only plane-neutral
helpers in src/core (or replace the core file with a thin adapter that delegates
to the new export). Ensure package.json and any public export re-exports the new
emitter from src/exports/control.ts.

---

Outside diff comments:
In `@packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts`:
- Around line 300-318: The plan currently calls materializePlanItems(items)
which discards the authoring IR and causes renderTypeScript() to throw; instead
preserve the original factory-call items and return a TypeScript-renderable plan
when authoring is possible: stop replacing items with
materializePlanItems(items) in createMigrationPlan, keep the original items (or
store both materialized ops and the original items) and implement
renderTypeScript() to delegate to the TypeScriptRenderablePostgresMigration path
(or construct/return a TypeScriptRenderablePostgresMigration using the original
PostgresOpFactoryCall items); update the plan returned from plan() /
createMigrationPlan to include that renderable behavior instead of throwing.

---

Nitpick comments:
In `@packages/3-targets/3-targets/postgres/src/core/migrations/op-factories.ts`:
- Around line 9-10: The file header incorrectly claims factories never import
`@prisma-next/framework-components` while this file imports DataTransformOperation
and SerializedQueryPlan and uses createDataTransform; update the top-of-file
comment to reflect that Phase 0 factories may import those specific types
(DataTransformOperation, SerializedQueryPlan) or remove the blanket prohibition
and mention the allowed dependency for createDataTransform so the doc matches
the actual imports and usage.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`:
- Around line 235-244: The ForeignKeySpec object uses inline conditional spreads
for onDelete/onUpdate; replace those spreads with ifDefined() calls (imported
from `@prisma-next/utils/defined`) when building the spec in the
operation-resolver: update the construction of spec (const spec: ForeignKeySpec)
to include ...(ifDefined({ onDelete: fk.onDelete })) and ...(ifDefined({
onUpdate: fk.onUpdate })) instead of the current ...(fk.onDelete !== undefined
&& { onDelete: fk.onDelete }) / ...(fk.onUpdate !== undefined && { onUpdate:
fk.onUpdate }) patterns so the conditional properties are added via ifDefined().
🪄 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: cd022c4c-1dce-4215-a421-ae9b9f93c955

📥 Commits

Reviewing files that changed from the base of the PR and between 8770d1d and cd73e18.

⛔ Files ignored due to path filters (7)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/postgres-class-flow-migrations/plan.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/pr-plan.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/spec.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/specs/phase-0-factory-extraction.spec.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/specs/phase-1-walk-schema-class-flow.spec.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/specs/walk-schema-audit.spec.md is excluded by !projects/**
📒 Files selected for processing (25)
  • packages/1-framework/1-core/framework-components/src/control-migration-types.ts
  • packages/1-framework/1-core/framework-components/src/exports/control.ts
  • packages/2-sql/9-family/package.json
  • packages/2-sql/9-family/src/core/sql-migration.ts
  • packages/2-sql/9-family/src/exports/migration.ts
  • packages/2-sql/9-family/tsdown.config.ts
  • packages/3-mongo-target/1-mongo-target/src/core/migration-ts-expression.ts
  • packages/3-mongo-target/1-mongo-target/src/core/op-factory-call.ts
  • packages/3-mongo-target/1-mongo-target/src/core/render-literal.ts
  • packages/3-mongo-target/1-mongo-target/src/core/render-typescript.ts
  • packages/3-targets/3-targets/postgres/package.json
  • packages/3-targets/3-targets/postgres/src/core/migrations/migration-ts-expression.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/op-factories.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/op-factory-call.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/placeholder-expression.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-produced-postgres-migration.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-reconciliation.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/render-literal.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/render-ops.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/render-typescript.ts
  • packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner.reconciliation-unit.test.ts
  • test/e2e/framework/test/ddl.test.ts

Comment thread packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts Outdated
Comment thread packages/3-targets/3-targets/postgres/src/core/migrations/render-typescript.ts Outdated
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: 4

♻️ Duplicate comments (1)
packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts (1)

405-405: 🛠️ Refactor suggestion | 🟠 Major

Move import to top of file.

This import statement is positioned after function definitions. Move it to the import block at the top of the file with the other imports from @prisma-next/adapter-postgres/control (line 13).

♻️ Proposed fix
-import { qualifyName } from '@prisma-next/adapter-postgres/control';

Move to line 13 area and combine with existing import:

-import { createPostgresAdapter } from '@prisma-next/adapter-postgres/adapter';
+import { createPostgresAdapter, qualifyName } from '@prisma-next/adapter-postgres/adapter';

Or if qualifyName comes from a different subpath, add it near the existing imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`
at line 405, The import "qualifyName" is declared after function definitions;
move the import statement for qualifyName from
'@prisma-next/adapter-postgres/control' into the top import block with the other
imports from that same module (alongside the existing control imports) so all
imports are grouped together; update the existing import list to include
qualifyName rather than leaving a late import in the body.
🧹 Nitpick comments (4)
packages/2-sql/9-family/src/core/sql-migration.ts (1)

4-24: Consider trimming this long rationale block comment.

The class signature already conveys most of this behavior; moving detailed rationale to a short doc note would keep core source leaner.

As per coding guidelines: "Do not add comments if avoidable; prefer code which expresses its intent."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/9-family/src/core/sql-migration.ts` around lines 4 - 24,
Shorten the lengthy rationale comment above the SQL migration base class into a
concise doc note: remove the multi-paragraph historical rationale and keep a
one- or two-sentence summary that states the purpose of the class and its type
parameterization (mentioning SqlMigrationPlanOperation and
SqlPlanTargetDetails), and note that concrete subclasses like
TypeScriptRenderablePostgresMigration set their own targetId while familyId is
intentionally omitted; retain only the essential intent, moving the detailed
rationale out to external design docs if needed.
packages/3-mongo-target/1-mongo-target/test/op-factory-call.test.ts (1)

39-43: Use an object matcher for grouped call metadata assertions.

These are related fields on the same object (factoryName, operationClass, label), so a single toMatchObject assertion is preferred and easier to maintain. Please apply the same pattern to similar tests in this file.

Example refactor
-      expect(call.factoryName).toBe('createIndex');
-      expect(call.operationClass).toBe('additive');
-      expect(call.label).toBe('Create index on users (email:1)');
+      expect(call).toMatchObject({
+        factoryName: 'createIndex',
+        operationClass: 'additive',
+        label: 'Create index on users (email:1)',
+      });

As per coding guidelines: **/*.test.ts: “Prefer object matchers (toMatchObject) over multiple individual expect().toBe() calls when checking 2 or more related values in tests.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-mongo-target/1-mongo-target/test/op-factory-call.test.ts` around
lines 39 - 43, The assertions on the call object use multiple toBe checks for
related properties; replace them with a single object matcher: update the test
that inspects call (the variable named call) to assert Object.isFrozen(call)
remains and then use expect(call).toMatchObject({ factoryName: 'createIndex',
operationClass: 'additive', label: 'Create index on users (email:1)' }); apply
the same to other tests in this file that assert two or more related properties
on a single object so they use toMatchObject instead of multiple
expect(...).toBe calls.
packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts (1)

675-688: Use ifDefined() for conditional object spreads.

Lines 682-683 use inline conditional spread patterns. Per coding guidelines, prefer ifDefined() from @prisma-next/utils/defined for consistency.

♻️ Proposed fix
         const fkSpec: ForeignKeySpec = {
           name: fkName,
           columns: foreignKey.columns,
           references: {
             table: foreignKey.references.table,
             columns: foreignKey.references.columns,
           },
-          ...(foreignKey.onDelete !== undefined && { onDelete: foreignKey.onDelete }),
-          ...(foreignKey.onUpdate !== undefined && { onUpdate: foreignKey.onUpdate }),
+          ...ifDefined('onDelete', foreignKey.onDelete),
+          ...ifDefined('onUpdate', foreignKey.onUpdate),
         };

As per coding guidelines: "Use ifDefined() from @prisma-next/utils/defined for conditional object spreads instead of inline conditional spread patterns."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts` around
lines 675 - 688, The fkSpec object in function that builds foreign keys uses
inline conditional spreads for onDelete/onUpdate; replace those spreads with
calls to ifDefined() from `@prisma-next/utils/defined` (e.g., set properties via
...ifDefined({ onDelete: foreignKey.onDelete }) and ...ifDefined({ onUpdate:
foreignKey.onUpdate })) and add the necessary import for ifDefined; update the
construction of fkSpec (the variable named fkSpec and the new
AddForeignKeyCall(schemaName, tableName, fkSpec)) so it uses ifDefined() instead
of the inline ...(condition && {...}) patterns.
packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts (1)

62-103: Consider adding timeout for tsx execution tests.

These tests execute generated TypeScript via tsx, which could be slow depending on the environment. The repo's coding guidelines recommend using timeout helpers from @prisma-next/test-utils for TypeScript-related operations.

♻️ Suggested addition
+import { timeouts } from '@prisma-next/test-utils';
+
 describe('TypeScriptRenderablePostgresMigration round-trip', () => {
   // ...
-  it('renders TS that re-parses to operations matching renderOps(calls) exactly', async () => {
+  it('renders TS that re-parses to operations matching renderOps(calls) exactly', { timeout: timeouts.typeScriptCompilation }, async () => {

Based on learnings: "In prisma/prisma-next, when writing tests that run TypeScript compilation (e.g., via tsc --noEmit), avoid hardcoded timeout numbers... use prisma-next/test-utils timeout helpers."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts`
around lines 62 - 103, The tsx execution in the test (call using tsxPath with
execFileAsync) can hang in slow CI; wrap the execFileAsync invocation that runs
join(tmpDir, 'migration.ts') (and any readFile of ops.json) with the repo's
TypeScript timeout helper from `@prisma-next/test-utils` (or use its provided
run-with-timeout utility) and import that helper at the top of the test file;
specifically update the section that constructs the migration via
TypeScriptRenderablePostgresMigration, writes migration.ts (writeFile) and calls
execFileAsync(tsxPath, [...]) to use the timeout helper so the test fails fast
instead of hanging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/1-framework/1-core/ts-render/src/index.ts`:
- Around line 1-2: The current src/index.ts is a re-export-only barrel
(exporting JsonObject, JsonValue, jsonToTsSource from json-to-ts-source and
ImportRequirement, TsExpression from ts-expression); move these exports into an
explicit API entrypoint (e.g., create src/exports/index.ts) and relocate the two
export lines there, then remove or turn src/index.ts into a non-barrel file;
alternatively update consumers to import directly from './json-to-ts-source' and
'./ts-expression' and remove the re-exporting symbols (JsonObject, JsonValue,
jsonToTsSource, ImportRequirement, TsExpression) from src/index.ts so the repo
rule is satisfied.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts`:
- Around line 138-176: Both setNotNull and dropNotNull currently return the same
id (`alterNullability.${tableName}.${columnName}`) causing collisions; update
the id string in the setNotNull and dropNotNull functions to be unique (e.g.
include the operation name) such as
`alterNullability.setNotNull.${tableName}.${columnName}` for the function that
sets NOT NULL and `alterNullability.dropNotNull.${tableName}.${columnName}` for
the function that drops NOT NULL so downstream consumers that key by id won’t
collide.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operations/enums.ts`:
- Around line 91-106: renameType only prechecks that the source type exists and
misses a precheck that the destination name is free; add a precheck step to the
precheck array for renameType that ensures the target type (toName) does NOT
already exist (e.g., add step(`ensure type "${toName}" does not exist`, /*
negated check of */ enumTypeExistsCheck(schemaName, toName)) or use an existing
enumTypeNotExistsCheck helper), so the operation will fail fast in precheck
rather than during execute; place this new step alongside the existing source
existence precheck and keep the same step/enumType* helper usage patterns.

In
`@packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts`:
- Around line 92-96: The test contains a no-op assertion
`expect(stdout).toContain('')` that always passes; update the test around the
execFileAsync call to either remove that assertion or replace it with a
meaningful check: for example assert `stdout` is empty with
`expect(stdout).toBe('')` if the command should produce no output, or assert it
contains a specific expected string produced by running `${join(tmpDir,
'migration.ts')}` (referencing `execFileAsync`, `tsxPath`, `tmpDir`, and
`stdout`) to verify the migration output. Ensure the new assertion matches the
intended behavior of the executed script.

---

Duplicate comments:
In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`:
- Line 405: The import "qualifyName" is declared after function definitions;
move the import statement for qualifyName from
'@prisma-next/adapter-postgres/control' into the top import block with the other
imports from that same module (alongside the existing control imports) so all
imports are grouped together; update the existing import list to include
qualifyName rather than leaving a late import in the body.

---

Nitpick comments:
In `@packages/2-sql/9-family/src/core/sql-migration.ts`:
- Around line 4-24: Shorten the lengthy rationale comment above the SQL
migration base class into a concise doc note: remove the multi-paragraph
historical rationale and keep a one- or two-sentence summary that states the
purpose of the class and its type parameterization (mentioning
SqlMigrationPlanOperation and SqlPlanTargetDetails), and note that concrete
subclasses like TypeScriptRenderablePostgresMigration set their own targetId
while familyId is intentionally omitted; retain only the essential intent,
moving the detailed rationale out to external design docs if needed.

In `@packages/3-mongo-target/1-mongo-target/test/op-factory-call.test.ts`:
- Around line 39-43: The assertions on the call object use multiple toBe checks
for related properties; replace them with a single object matcher: update the
test that inspects call (the variable named call) to assert
Object.isFrozen(call) remains and then use expect(call).toMatchObject({
factoryName: 'createIndex', operationClass: 'additive', label: 'Create index on
users (email:1)' }); apply the same to other tests in this file that assert two
or more related properties on a single object so they use toMatchObject instead
of multiple expect(...).toBe calls.

In `@packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts`:
- Around line 675-688: The fkSpec object in function that builds foreign keys
uses inline conditional spreads for onDelete/onUpdate; replace those spreads
with calls to ifDefined() from `@prisma-next/utils/defined` (e.g., set properties
via ...ifDefined({ onDelete: foreignKey.onDelete }) and ...ifDefined({ onUpdate:
foreignKey.onUpdate })) and add the necessary import for ifDefined; update the
construction of fkSpec (the variable named fkSpec and the new
AddForeignKeyCall(schemaName, tableName, fkSpec)) so it uses ifDefined() instead
of the inline ...(condition && {...}) patterns.

In
`@packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts`:
- Around line 62-103: The tsx execution in the test (call using tsxPath with
execFileAsync) can hang in slow CI; wrap the execFileAsync invocation that runs
join(tmpDir, 'migration.ts') (and any readFile of ops.json) with the repo's
TypeScript timeout helper from `@prisma-next/test-utils` (or use its provided
run-with-timeout utility) and import that helper at the top of the test file;
specifically update the section that constructs the migration via
TypeScriptRenderablePostgresMigration, writes migration.ts (writeFile) and calls
execFileAsync(tsxPath, [...]) to use the timeout helper so the test fails fast
instead of hanging.
🪄 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: 66c7253e-b160-4f79-abf2-5b1e64d94dec

📥 Commits

Reviewing files that changed from the base of the PR and between cd73e18 and e38c0c5.

⛔ Files ignored due to path filters (8)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/postgres-class-flow-migrations/assets/walk-schema-audit.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/plan.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/pr-plan.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/spec.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/specs/phase-0-factory-extraction.spec.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/specs/phase-1-walk-schema-class-flow.spec.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/specs/walk-schema-audit.spec.md is excluded by !projects/**
📒 Files selected for processing (49)
  • packages/1-framework/1-core/framework-components/src/control-migration-types.ts
  • packages/1-framework/1-core/framework-components/src/exports/control.ts
  • packages/1-framework/1-core/ts-render/package.json
  • packages/1-framework/1-core/ts-render/src/index.ts
  • packages/1-framework/1-core/ts-render/src/json-to-ts-source.ts
  • packages/1-framework/1-core/ts-render/src/ts-expression.ts
  • packages/1-framework/1-core/ts-render/tsconfig.json
  • packages/1-framework/1-core/ts-render/tsdown.config.ts
  • packages/1-framework/3-tooling/cli/test/control-api/client.test.ts
  • packages/2-sql/9-family/package.json
  • packages/2-sql/9-family/src/core/migrations/types.ts
  • packages/2-sql/9-family/src/core/sql-migration.ts
  • packages/2-sql/9-family/src/exports/control.ts
  • packages/2-sql/9-family/src/exports/migration.ts
  • packages/2-sql/9-family/tsdown.config.ts
  • packages/3-mongo-target/1-mongo-target/package.json
  • packages/3-mongo-target/1-mongo-target/src/core/op-factory-call.ts
  • packages/3-mongo-target/1-mongo-target/src/core/render-ops.ts
  • packages/3-mongo-target/1-mongo-target/src/core/render-typescript.ts
  • packages/3-mongo-target/1-mongo-target/src/exports/control.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-planner.test.ts
  • packages/3-mongo-target/1-mongo-target/test/op-factory-call.test.ts
  • packages/3-mongo-target/1-mongo-target/test/render-typescript.test.ts
  • packages/3-targets/3-targets/postgres/package.json
  • packages/3-targets/3-targets/postgres/src/core/migrations/op-factory-call.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/constraints.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/data-transform.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/dependencies.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/enums.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/indexes.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/raw.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/shared.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/tables.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-produced-postgres-migration.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-reconciliation.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/render-ops.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/render-typescript.ts
  • packages/3-targets/3-targets/postgres/src/exports/migration.ts
  • packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner.authoring-surface.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner.reconciliation-unit.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner.reconciliation.issue-to-call.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts
  • packages/3-targets/3-targets/postgres/tsdown.config.ts
  • test/e2e/framework/test/ddl.test.ts
  • tsconfig.base.json
✅ Files skipped from review due to trivial changes (12)
  • packages/2-sql/9-family/src/exports/migration.ts
  • packages/2-sql/9-family/tsdown.config.ts
  • packages/1-framework/1-core/ts-render/tsconfig.json
  • packages/3-mongo-target/1-mongo-target/package.json
  • packages/2-sql/9-family/src/core/migrations/types.ts
  • tsconfig.base.json
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/raw.ts
  • test/e2e/framework/test/ddl.test.ts
  • packages/1-framework/1-core/ts-render/package.json
  • packages/3-targets/3-targets/postgres/src/exports/migration.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/data-transform.ts
  • packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/3-targets/3-targets/postgres/package.json
  • packages/1-framework/1-core/framework-components/src/exports/control.ts
  • packages/2-sql/9-family/package.json
  • packages/1-framework/1-core/framework-components/src/control-migration-types.ts
  • packages/3-mongo-target/1-mongo-target/src/core/op-factory-call.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner.reconciliation-unit.test.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-produced-postgres-migration.ts
  • packages/3-mongo-target/1-mongo-target/src/core/render-typescript.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/render-typescript.ts

Comment thread packages/1-framework/1-core/ts-render/src/index.ts
Comment thread packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts Outdated
@wmadden wmadden changed the title Tml 2286 backport class flow migration authoring to the postgres feat(target-postgres): class-flow migration authoring Apr 21, 2026
wmadden added a commit that referenced this pull request Apr 21, 2026
R1: correct walk-schema-audit.md — liftOpToCall unconditionally wraps as
RawSqlCall today (not dispatched), and storage-type enum classes are
CreateEnumTypeCall / AddEnumValuesCall / DropEnumTypeCall / RenameTypeCall
(no CreateTypeCall). Frame structured dispatch as a Phase 4 target.

R3: file the ergonomic rawSql(...) helper under plan.md§"Known follow-ups"
(Phase 2/3 authoring-surface polish).
wmadden added a commit that referenced this pull request Apr 21, 2026
Address PR #359 re-review R2: the `unknown` input type on `jsonToTsSource`
is a documented ergonomic concession (so structural types whose fields
happen to be JSON-compatible can be passed without an index signature).
Bound the escape hatch with explicit tests asserting that Symbol, BigInt,
and function values throw at runtime — scope-creep guard per the module
docstring.
wmadden added a commit that referenced this pull request Apr 21, 2026
R4: tighten TypeScriptRenderablePostgresMigration docstring to name the
`operations`-vs-`renderTypeScript()` asymmetry explicitly — callers who
know a plan may carry stubs must not enumerate `operations` before the
user has filled them; render to migration.ts and re-load the edited
migration instead. Walk-schema never emits stubs today, so this is
invisible until the Phase 2 issue-planner integration lands.

R5: add a round-trip case for the empty calls list — render →
`tsx`-execute → read `ops.json` → `expect([])`. Pins "the empty scaffold
executes cleanly" as an invariant, not just "the empty scaffold parses
cleanly".
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.

♻️ Duplicate comments (1)
packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts (1)

96-96: ⚠️ Potential issue | 🟡 Minor

Replace the no-op stdout assertion with a meaningful check.

expect(stdout).toContain('') always passes, so it doesn’t verify execution output.

Suggested change
-    expect(stdout).toContain('');
+    expect(stdout).toBe('');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts`
at line 96, The current no-op assertion expect(stdout).toContain('') should be
replaced with a real check: assert that stdout is not empty and includes the
expected TypeScript output marker; for example replace it by something like
expect(stdout).not.toBe('') and expect(stdout).toContain('export') (reference
the stdout variable in this test to locate and update the assertion in
render-typescript.roundtrip.test.ts).
🧹 Nitpick comments (2)
packages/1-framework/1-core/ts-render/test/json-to-ts-source.test.ts (2)

32-42: Consider table-driving unsupported-type throw cases

These assertions are correct; parameterizing them would reduce duplication and keep future additions simpler.

Optional refactor
-    it('throws on Symbol', () => {
-      expect(() => jsonToTsSource(Symbol('s'))).toThrowError(/unsupported value type "symbol"/);
-    });
-
-    it('throws on BigInt', () => {
-      expect(() => jsonToTsSource(BigInt(1))).toThrowError(/unsupported value type "bigint"/);
-    });
-
-    it('throws on function', () => {
-      expect(() => jsonToTsSource(() => 1)).toThrowError(/unsupported value type "function"/);
-    });
+    it.each([
+      ['symbol', Symbol('s')],
+      ['bigint', BigInt(1)],
+      ['function', () => 1],
+    ] as const)('throws on %s', (typeName, value) => {
+      expect(() => jsonToTsSource(value)).toThrowError(
+        new RegExp(`unsupported value type "${typeName}"`),
+      );
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/1-core/ts-render/test/json-to-ts-source.test.ts` around
lines 32 - 42, The three duplicate tests asserting jsonToTsSource throws for
unsupported types should be table-driven: create an array of cases like [{name:
'Symbol', value: Symbol('s'), regex: /unsupported value type "symbol"/}, ...]
and iterate (e.g., cases.forEach or test.each) to generate the it() assertions
dynamically, referencing jsonToTsSource and each case's value and regex so
future unsupported-type tests can be added by appending to the cases array.

21-23: Add explicit coverage for undefined object-property elision

jsonToTsSource drops object entries where value is undefined; this behavior is important and worth pinning with a focused assertion to prevent regressions.

Proposed test addition
   it('renders objects and quotes non-identifier keys', () => {
     expect(jsonToTsSource({ a: 1, 'weird-key': 2 })).toBe('{ a: 1, "weird-key": 2 }');
   });
+
+  it('omits undefined object properties', () => {
+    expect(jsonToTsSource({ a: undefined })).toBe('{}');
+    expect(jsonToTsSource({ a: 1, b: undefined })).toBe('{ a: 1 }');
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/1-core/ts-render/test/json-to-ts-source.test.ts` around
lines 21 - 23, Add a focused test for jsonToTsSource that asserts object
properties with value undefined are elided from the output (e.g.,
expect(jsonToTsSource({ a: 1, b: undefined })).toBe('{ a: 1 }')); place it near
the existing "renders objects and quotes non-identifier keys" test in
json-to-ts-source.test.ts so the behavior is pinned and will catch regressions
in the jsonToTsSource function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts`:
- Line 96: The current no-op assertion expect(stdout).toContain('') should be
replaced with a real check: assert that stdout is not empty and includes the
expected TypeScript output marker; for example replace it by something like
expect(stdout).not.toBe('') and expect(stdout).toContain('export') (reference
the stdout variable in this test to locate and update the assertion in
render-typescript.roundtrip.test.ts).

---

Nitpick comments:
In `@packages/1-framework/1-core/ts-render/test/json-to-ts-source.test.ts`:
- Around line 32-42: The three duplicate tests asserting jsonToTsSource throws
for unsupported types should be table-driven: create an array of cases like
[{name: 'Symbol', value: Symbol('s'), regex: /unsupported value type "symbol"/},
...] and iterate (e.g., cases.forEach or test.each) to generate the it()
assertions dynamically, referencing jsonToTsSource and each case's value and
regex so future unsupported-type tests can be added by appending to the cases
array.
- Around line 21-23: Add a focused test for jsonToTsSource that asserts object
properties with value undefined are elided from the output (e.g.,
expect(jsonToTsSource({ a: 1, b: undefined })).toBe('{ a: 1 }')); place it near
the existing "renders objects and quotes non-identifier keys" test in
json-to-ts-source.test.ts so the behavior is pinned and will catch regressions
in the jsonToTsSource function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: eac0074e-fd87-4791-88ea-cf9dc2bf3614

📥 Commits

Reviewing files that changed from the base of the PR and between e38c0c5 and ae82f5a.

⛔ Files ignored due to path filters (2)
  • projects/postgres-class-flow-migrations/assets/walk-schema-audit.md is excluded by !projects/**
  • projects/postgres-class-flow-migrations/plan.md is excluded by !projects/**
📒 Files selected for processing (3)
  • packages/1-framework/1-core/ts-render/test/json-to-ts-source.test.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-produced-postgres-migration.ts
  • packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-produced-postgres-migration.ts

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/1-framework/1-core/ts-render/README.md`:
- Around line 20-22: The fenced code block containing "jsValue  → 
codec.encodeJson  →  JsonValue  →  jsonToTsSource  →  TS source text" is
unlabeled and triggers markdownlint MD040; update that fence to include a
language identifier by changing the opening ``` to ```text so the block becomes
a plain-text fenced block (e.g., replace the existing ``` with ```text
immediately before the line starting with "jsValue  → ...").
🪄 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: 48722c71-27a7-4464-a1b6-827584ae87f3

📥 Commits

Reviewing files that changed from the base of the PR and between ae82f5a and 42d5c67.

📒 Files selected for processing (5)
  • packages/1-framework/1-core/ts-render/README.md
  • packages/1-framework/1-core/ts-render/biome.jsonc
  • packages/1-framework/1-core/ts-render/package.json
  • packages/1-framework/1-core/ts-render/tsconfig.prod.json
  • packages/1-framework/1-core/ts-render/vitest.config.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/1-framework/1-core/ts-render/biome.jsonc
  • packages/1-framework/1-core/ts-render/tsconfig.prod.json
  • packages/1-framework/1-core/ts-render/package.json

Comment thread packages/1-framework/1-core/ts-render/README.md Outdated
@wmadden
Copy link
Copy Markdown
Contributor Author

wmadden commented Apr 21, 2026

Addressing CodeRabbit review #4148577109 (A19a–A19c). All three findings are resolved on the current branch:

  • A19a (planner authoring-IR dropped on success path): addressed in-tree; the planner now returns a TypeScriptRenderablePostgresMigration that carries the PostgresOpFactoryCall[] class-flow IR and exposes both operations and renderTypeScript() from the same IR. Landed in 4770815 and 47ec371.
  • A19b (use `ifDefined` for fk `onDelete`/`onUpdate` in operation-resolver.ts): duplicate of A10 — landed in 1508cde.
  • A19c (stale op-factories header): resolved by the op-factories.ts split in 50f572a; the DataTransformOperation / SerializedQueryPlan imports now live in `operations/data-transform.ts` with no conflicting header.

@wmadden
Copy link
Copy Markdown
Contributor Author

wmadden commented Apr 21, 2026

Addressing CodeRabbit review #4148637450 (A20a–A20e):

  • A20a (hoist `qualifyName` import in operation-resolver.ts): duplicate of A11 — landed in 14194bcb6.
  • A20b (trim the rationale block above `SqlMigration`): declining. The comment answers reviewer questions from the self-review (A04) about why the SQL family omits `familyId`; the guidance carves out non-obvious intent, which this block documents. Keeping as-is.
  • A20c (collapse grouped `.toBe` into `toMatchObject` in mongo op-factory-call.test.ts): landed in 384fa18.
  • A20d (use `ifDefined` for fk `onDelete`/`onUpdate` in planner.ts): landed in 1dd5679.
  • A20e (timeout wrapper around tsx invocation in render-typescript.roundtrip.test.ts): declining. The suggested helper targets `tsc --noEmit`-style tests; this test shells out to tsx to execute a one-shot script and has not flaked. Will revisit if it becomes flaky.

@wmadden
Copy link
Copy Markdown
Contributor Author

wmadden commented Apr 21, 2026

Addressing CodeRabbit review #4148695243 (A21a–A21c):

  • A21a (replace `expect(stdout).toContain('')` in render-typescript.roundtrip.test.ts:96): duplicate of A18 — landed in 6a89fe2.
  • A21b (fold the three `throws on Symbol/BigInt/function` tests into `it.each`): declining. The named `it()` blocks were added in R2 (6454f97) as greppable regression anchors for each non-JSON escape hatch; converting them to `it.each` would trade that discoverability for a one-line savings per case. Keeping as-is.
  • A21c (pin that `jsonToTsSource` drops object entries with `undefined` values): landed in 169f501.

@wmadden
Copy link
Copy Markdown
Contributor Author

wmadden commented Apr 21, 2026

Addressing CodeRabbit review #4148769335: the single finding (markdownlint MD040 on the unlabelled fenced block in packages/1-framework/1-core/ts-render/README.md:20) is fixed in 2c3449e — the fence now reads ```text.

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)
packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts (2)

456-461: Use ifDefined() for the conditional spread helper.

The inline ...(table !== undefined ? { table } : {}) pattern is against the repo helper convention, and the same pattern repeats below for details. Please switch both sites to ifDefined() for consistency with the rest of the codebase.

As per coding guidelines, {packages,examples,test}/**/*.{ts,tsx}: "Use ifDefined() from @prisma-next/utils/defined for conditional object spreads instead of inline conditional spread patterns".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts`
around lines 456 - 461, Replace the inline conditional spread in the
schemaObject helper and the similar conditional spread for details with the
repository helper ifDefined from `@prisma-next/utils/defined`; specifically, in
the schemaObject arrow function (symbol: schemaObject) replace ...(table !==
undefined ? { table } : {}) with the ifDefined(table, t => ({ table: t }))
usage, and likewise update the details construction to use ifDefined(...)
instead of the inline conditional spread so both sites follow the repo
convention.

49-623: Split this suite by concern.

This file is now 600+ lines and covers four separate behaviors, so failures will get harder to navigate as it grows. Please split it into op-factory-call.{basic,rendering,render-ops,migration}.test.ts-style files so each stays readable and independently runnable.

As per coding guidelines, **/*.test.ts: "Keep test files under 500 lines..." and "Split test files when they exceed 500 lines, contain multiple distinct concerns, or have multiple top-level describe blocks that can be split by functionality".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts`
around lines 49 - 623, The test file is too large and mixes multiple concerns;
split the suite into separate test files so each focuses on one area: (1)
construction/parity tests around CreateTableCall, DataTransformCall, etc. (look
for tests referencing CreateTableCall, DataTransformCall and toOp()), (2)
renderTypeScript and importRequirements coverage (tests calling
renderTypeScript() and importRequirements()), (3) renderOps behavior (the large
suite asserting id/operationClass/details for many calls and tests for
RawSqlCall and PN-MIG-2001), and (4) the TypeScriptRenderablePostgresMigration
tests (tests constructing TypeScriptRenderablePostgresMigration and asserting
targetId, destination, describe, renderTypeScript()). Move each group into its
own test file, keep the original describe/it blocks intact, and adjust
imports/exports so each file imports only the symbols it needs (CreateTableCall,
renderCallsToTypeScript, renderOps, TypeScriptRenderablePostgresMigration,
RawSqlCall, DataTransformCall, META, etc.).
packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts (1)

257-262: Misleading underscore prefix on used parameter.

The _ctx parameter has an underscore prefix (conventionally indicating an unused parameter), but _ctx.schemaName is actually used on line 261. Remove the underscore to avoid confusion.

♻️ Proposed fix
 function resolveDropConstraint(
   desc: DropConstraintDescriptor,
-  _ctx: OperationResolverContext,
+  ctx: OperationResolverContext,
 ): ResolvedOp {
-  return createDropConstraint(_ctx.schemaName, desc.table, desc.constraintName);
+  return createDropConstraint(ctx.schemaName, desc.table, desc.constraintName);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`
around lines 257 - 262, The parameter name for resolveDropConstraint currently
uses a misleading underscore prefix (_ctx) but is actually read (using
_ctx.schemaName); rename the parameter to ctx (or another non-underscore name)
in the resolveDropConstraint function signature and update all uses inside that
function (e.g., replace _ctx.schemaName with ctx.schemaName) so the parameter
correctly reflects that it is used; ensure the function signature still accepts
the same type (OperationResolverContext) and that
createDropConstraint(ctx.schemaName, desc.table, desc.constraintName) is called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`:
- Around line 110-121: The toColumnSpec function currently calls
buildColumnTypeSql without passing storageTypes; update the toColumnSpec
signature to accept storageTypes: Record<string, StorageTypeInstance> and call
buildColumnTypeSql(column, codecHooks, storageTypes), keep building defaultSql
and nullable as before; then update all callers of toColumnSpec (e.g., the
places in operation-resolver/planner that construct column specs) to pass
ctx.toContract.storage.types ?? {} when invoking toColumnSpec so typeRef
resolution works for custom storage types.

In
`@packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts`:
- Around line 352-379: Replace the loose import assertion with an
exact-duplication check so the test verifies there is exactly one merged
`@prisma-next/target-postgres/migration` import line produced by
renderCallsToTypeScript(META); locate the test block (op-factory-call.test.ts)
and change the expectation that currently checks for the merged import with
toContain(...) into a single-occurrence assertion (e.g., assert the rendered
source contains the exact merged import string and that its occurrence count is
1 using a regex match against the import text) so any additional stray imports
cause the test to fail.

---

Nitpick comments:
In
`@packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts`:
- Around line 257-262: The parameter name for resolveDropConstraint currently
uses a misleading underscore prefix (_ctx) but is actually read (using
_ctx.schemaName); rename the parameter to ctx (or another non-underscore name)
in the resolveDropConstraint function signature and update all uses inside that
function (e.g., replace _ctx.schemaName with ctx.schemaName) so the parameter
correctly reflects that it is used; ensure the function signature still accepts
the same type (OperationResolverContext) and that
createDropConstraint(ctx.schemaName, desc.table, desc.constraintName) is called.

In
`@packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts`:
- Around line 456-461: Replace the inline conditional spread in the schemaObject
helper and the similar conditional spread for details with the repository helper
ifDefined from `@prisma-next/utils/defined`; specifically, in the schemaObject
arrow function (symbol: schemaObject) replace ...(table !== undefined ? { table
} : {}) with the ifDefined(table, t => ({ table: t })) usage, and likewise
update the details construction to use ifDefined(...) instead of the inline
conditional spread so both sites follow the repo convention.
- Around line 49-623: The test file is too large and mixes multiple concerns;
split the suite into separate test files so each focuses on one area: (1)
construction/parity tests around CreateTableCall, DataTransformCall, etc. (look
for tests referencing CreateTableCall, DataTransformCall and toOp()), (2)
renderTypeScript and importRequirements coverage (tests calling
renderTypeScript() and importRequirements()), (3) renderOps behavior (the large
suite asserting id/operationClass/details for many calls and tests for
RawSqlCall and PN-MIG-2001), and (4) the TypeScriptRenderablePostgresMigration
tests (tests constructing TypeScriptRenderablePostgresMigration and asserting
targetId, destination, describe, renderTypeScript()). Move each group into its
own test file, keep the original describe/it blocks intact, and adjust
imports/exports so each file imports only the symbols it needs (CreateTableCall,
renderCallsToTypeScript, renderOps, TypeScriptRenderablePostgresMigration,
RawSqlCall, DataTransformCall, META, etc.).
🪄 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: 2f5813d9-2fbd-468a-9094-ffe5cc446e94

📥 Commits

Reviewing files that changed from the base of the PR and between 42d5c67 and 2c3449e.

📒 Files selected for processing (11)
  • packages/1-framework/1-core/ts-render/README.md
  • packages/1-framework/1-core/ts-render/test/json-to-ts-source.test.ts
  • packages/3-mongo-target/1-mongo-target/test/op-factory-call.test.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/enums.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner.ts
  • packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner.reconciliation-unit.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner.reconciliation.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/1-framework/1-core/ts-render/test/json-to-ts-source.test.ts
  • packages/3-mongo-target/1-mongo-target/test/op-factory-call.test.ts
  • packages/3-targets/3-targets/postgres/test/migrations/render-typescript.roundtrip.test.ts
  • packages/1-framework/1-core/ts-render/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/3-targets/3-targets/postgres/test/migrations/planner.reconciliation-unit.test.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/enums.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/operations/columns.ts

wmadden added 9 commits April 21, 2026 18:25
…tical goal in favor of runner post-apply verify
…ionTsExpression AST nodes

Replace the placeholder-closure-with-hidden-symbol design with a proper
Postgres-internal AST hierarchy:

- MigrationTsExpression (abstract, package-private): renderTypeScript() +
  importRequirements(). Root of everything the TypeScript renderer walks.
- PostgresOpFactoryCallNode extends MigrationTsExpression and implements
  OpFactoryCall; adds visitor accept() for renderOps.
- PlaceholderExpression extends MigrationTsExpression; renders as
  () => placeholder("slot") and declares the placeholder import.

DataTransformCall holds check/run as MigrationTsExpression (broad, to leave
room for future variants like a ClosureExpression). renderCallsToTypeScript
becomes polymorphic (not a visitor), walking nodes via renderTypeScript() /
importRequirements() and deduplicating imports. renderOps stays a visitor;
its dataTransform case routes check/run through a bodyToClosure() helper
that returns () => placeholder(slot) for PlaceholderExpression.

Option B for placeholder-bearing plans: migration plan writes migration.ts
only. ops.json and migration.json are re-produced when the user runs the
filled-in migration.ts directly. Captured as new R2.11 in spec.md and as
CLI catch behavior in plan.md Phase 3.

Phase 0 shrinks accordingly - no placeholderClosure helpers are added to
@prisma-next/errors/migration; Phase 0 is pure factory extraction only.
… hierarchy in Phase 1

Mongo and Postgres ship the same IR shape. Phase 1 now adds a
Mongo-internal MigrationTsExpression (sibling of the Postgres one),
makes OpFactoryCallNode extend it, gives each concrete call class
polymorphic renderTypeScript() + importRequirements() methods, and
rewrites renderCallsToTypeScript as a polymorphic walk. Byte-identical
Mongo migration.ts output; existing snapshot tests are the regression
gate. No Mongo planner / factory / CLI changes. Cross-target lift of
MigrationTsExpression + ImportRequirement + TypeScriptRenderableMigration
remains a known follow-up; Phase 1 sync keeps the two hierarchies
byte-compatible at lift time.
… coherence

After the recent MigrationTsExpression + Option B placeholder redesign
and the Mongo IR sync addition, several stale references remained across
the project docs. Reconcile them:

- plan.md: fix the end-state directory diagram so TypeScriptRenderable-
  PostgresMigration lives in planner-produced-postgres-migration.ts
  (mirroring the Phase 1 spec), and note planner.ts becomes a thin shell
  post-Phase 4.
- plan.md: expand the Mongo IR sync scope to mention the render-literal.ts
  helper extraction (matching the Phase 1 spec), and correct the deleted-
  helpers list (renderCallVisitor + collectFactoryNames; buildImports stays).
- plan.md, pr-plan.md: point the walk-schema audit location at
  projects/postgres-class-flow-migrations/assets/walk-schema-audit.md
  in all places (audit spec is the authoritative source).
- pr-plan.md: drop the claim that PR 1 introduces MigrationPlanWithAuthoring-
  Surface. That interface already exists from ADR 194 / the Mongo work; only
  OpFactoryCall is new.
- spec.md: tighten the Step 3 acceptance criterion so renderOps and
  renderCallsToTypeScript are described as package-internal (not exported
  from the user-facing /migration subpath, which continues to expose only
  the pure factories per R3.1).
- spec.md: rewrite the Type-safe-exhaustiveness non-functional requirement
  so it correctly describes the two dispatch mechanisms (renderOps visitor
  + polymorphic MigrationTsExpression methods) instead of claiming both
  renderers use a visitor.
- phase-1-walk-schema-class-flow.spec.md: stop mislabeling the walk-schema
  audit as "the Phase 0 audit" - it is produced as the first research task
  of Phase 1.
…-SQL Migration alias

Phase 1 foundation (PR1): first small, independent slice that unblocks
subsequent IR work in both Postgres and Mongo.

- framework-components/control: add OpFactoryCall interface and
  re-export it alongside the other migration types. Interface-only;
  no abstract base at framework or family level.
- target-mongo: OpFactoryCallNode now implements the framework
  OpFactoryCall so consumers can depend on the framework interface
  uniformly.
- family-sql: add SqlMigration<TDetails> alias (extends
  Migration<SqlMigrationPlanOperation<TDetails>>) and expose it via a
  new /migration subpath barrel, mirroring family-mongo's layout. Adds
  @prisma-next/migration-tools as a dependency and wires the new
  entry through tsdown + package.json exports.

All existing target-mongo tests pass unchanged.
wmadden added 16 commits April 21, 2026 18:26
R1: correct walk-schema-audit.md — liftOpToCall unconditionally wraps as
RawSqlCall today (not dispatched), and storage-type enum classes are
CreateEnumTypeCall / AddEnumValuesCall / DropEnumTypeCall / RenameTypeCall
(no CreateTypeCall). Frame structured dispatch as a Phase 4 target.

R3: file the ergonomic rawSql(...) helper under plan.md§"Known follow-ups"
(Phase 2/3 authoring-surface polish).
Address PR #359 re-review R2: the `unknown` input type on `jsonToTsSource`
is a documented ergonomic concession (so structural types whose fields
happen to be JSON-compatible can be passed without an index signature).
Bound the escape hatch with explicit tests asserting that Symbol, BigInt,
and function values throw at runtime — scope-creep guard per the module
docstring.
R4: tighten TypeScriptRenderablePostgresMigration docstring to name the
`operations`-vs-`renderTypeScript()` asymmetry explicitly — callers who
know a plan may carry stubs must not enumerate `operations` before the
user has filled them; render to migration.ts and re-load the edited
migration instead. Walk-schema never emits stubs today, so this is
invisible until the Phase 2 issue-planner integration lands.

R5: add a round-trip case for the empty calls list — render →
`tsx`-execute → read `ops.json` → `expect([])`. Pins "the empty scaffold
executes cleanly" as an invariant, not just "the empty scaffold parses
cleanly".
Add tsconfig.prod.json, biome.jsonc, and vitest.config.ts to align
@prisma-next/ts-render with the 1-core sibling package layout. The
shared @prisma-next/tsdown preset expects tsconfig.prod.json — the
CI build was failing with "ERROR tsconfig tsconfig.prod.json doesnt
exist" (locally: WARN) and exiting non-zero despite the rollup build
succeeding.
Fill the remaining gaps against packages/1-framework/1-core/errors and
/operations:

- Add README.md (internal-package boilerplate plus a short overview of
  TsExpression, jsonToTsSource, and the codec → TS pipeline that
  @prisma-next/ts-render is the second stage of).
- Add a `test:coverage` script; drop `--passWithNoTests` from `test`
  now that the package ships real unit coverage.

File-set now matches the other core framework utility packages exactly
(biome.jsonc, package.json, README.md, tsconfig.json, tsconfig.prod.json,
tsdown.config.ts, vitest.config.ts).
…unreachable ?? fallback in mongo render-typescript

Adds json-to-ts-source tests for the two multi-line branches (arrays
and objects exceeding 80 chars on a single line) and for the
`__proto__` key quoting branch in renderKey, bringing
json-to-ts-source.ts to 100% coverage.

Also replaces an unreachable `?? []` fallback on a Map.get() in
render-typescript with a non-null assertion: we iterate the map keys
immediately before, so the branch can never be taken. Keeps
1-mongo-target at 100% branch coverage per the
prefer-assertions-over-defensive-checks rule.
…eachable fallback

biome forbids non-null assertions, and the previous `?? []` fallback on
`symbolsByModule.get()` was unreachable (we iterated the map keys
immediately before). Iterate the Map directly with destructuring
instead — insertion order is stable, and no factory ever produces more
than one non-base module anyway, so sort ordering was never exercised.

Keeps 1-mongo-target at 100% branch/statement/function coverage and
clears the biome `noNonNullAssertion` lint warning.
Every test in sqlite-driver.test.ts creates a real SQLite database file
and issues multiple queries against it, which can exceed 100ms of cold
I/O on a loaded CI runner. Intermittent timeouts surfaced as:

    Error: Test timed out in 200ms.

`timeouts.default` (100ms × multiplier) is the wrong budget for
disk-backed DB work. Switch `testTimeout` and `hookTimeout` to
`timeouts.databaseOperation` (5s × multiplier), which is what the
test-utils API explicitly defines for database queries, setup, and
teardown. Tests still complete in tens of milliseconds locally.
@wmadden wmadden force-pushed the tml-2286-backport-class-flow-migration-authoring-to-the-postgres branch from e22820d to 975e63c Compare April 21, 2026 16:31
wmadden added 3 commits April 21, 2026 18:45
…ry-call.test.ts

The deduplicates+sorts-imports test used toContain() which only proved the
merged import line existed, not that it was the only one. Filter for lines
matching @prisma-next/target-postgres/migration and assert equality against
a single-element array so a regression that emits a stray duplicate import
would fail the test.
… op-factory-call.test.ts

Replace inline `...(x !== undefined ? { x } : {})` patterns in the
schemaObject helper and the renderOps expectation loop with
`ifDefined` from @prisma-next/utils/defined, per use-if-defined.mdc.
…t (it is used)

The _ prefix convention signals an unused parameter, but resolveDropConstraint
reads ctx.schemaName. Rename the parameter + usage site to match the
convention used elsewhere in this file.
@wmadden
Copy link
Copy Markdown
Contributor Author

wmadden commented Apr 21, 2026

Addressed review #359 (review) (A06a): replaced the two inline conditional spread sites in packages/3-targets/3-targets/postgres/test/migrations/op-factory-call.test.ts (the schemaObject helper's optional table and the renderOps expectation loop's optional details) with ifDefined() from @prisma-next/utils/defined, per .cursor/rules/use-if-defined.mdc. Done in a7bdf6b.

@wmadden
Copy link
Copy Markdown
Contributor Author

wmadden commented Apr 21, 2026

Addressed review #359 (review) (A06c): renamed _ctx -> ctx in resolveDropConstraint (packages/3-targets/3-targets/postgres/src/core/migrations/operation-resolver.ts) since the parameter is actually read at ctx.schemaName. Other resolve* functions keep _ctx only where it is genuinely unused. Done in 67d28f7.

@wmadden wmadden merged commit c94a730 into main Apr 21, 2026
16 checks passed
wmadden added a commit that referenced this pull request Apr 21, 2026
…hifts

Updates the project spec, phase-1 spec, plan, and PR plan to match the
shipped code after the PR #359 review addressed F05/W02/W04/W05/W07/W10
and related findings. Preserves the original phase-1 design narrative
with a post-implementation addendum at the top that names the
divergences:

- OpFactoryCall.factory is now factoryName (W02)
- TsExpression / ImportRequirement / jsonToTsSource live in a shared
  @prisma-next/framework-ts-render package, consumed by both targets
  (W04+W05); per-target MigrationTsExpression duplicates are gone
- DataTransformCall accepts checkSlot/runSlot strings directly, renders
  () => placeholder("slot") at its call site, and always throws
  PN-MIG-2001 from toOp() (W07 + simplification)
- Lowering calls to runtime ops is polymorphic via abstract toOp(); the
  PostgresOpFactoryCallVisitor interface and renderOps visitor are gone
  (W10)
- Pure factories live under operations/{tables,columns,...}.ts (W06)
- Factory inventory gained rawSql, createExtension, createSchema plus a
  liftOpToCall dispatcher (F05/F06)

Adds a follow-up for the family-API retarget (Path A) that eliminates
the liftOpToCall bridge before SQLite lands, and consolidates the
cross-target renderable-migration follow-up around the remaining
target-specific TypeScriptRenderable*Migration concrete classes.
wmadden added a commit that referenced this pull request Apr 21, 2026
R1: correct walk-schema-audit.md — liftOpToCall unconditionally wraps as
RawSqlCall today (not dispatched), and storage-type enum classes are
CreateEnumTypeCall / AddEnumValuesCall / DropEnumTypeCall / RenameTypeCall
(no CreateTypeCall). Frame structured dispatch as a Phase 4 target.

R3: file the ergonomic rawSql(...) helper under plan.md§"Known follow-ups"
(Phase 2/3 authoring-surface polish).
@wmadden wmadden deleted the tml-2286-backport-class-flow-migration-authoring-to-the-postgres branch April 21, 2026 17:31
wmadden added a commit that referenced this pull request Apr 21, 2026
Address PR #359 re-review R2: the `unknown` input type on `jsonToTsSource`
is a documented ergonomic concession (so structural types whose fields
happen to be JSON-compatible can be passed without an index signature).
Bound the escape hatch with explicit tests asserting that Symbol, BigInt,
and function values throw at runtime — scope-creep guard per the module
docstring.
wmadden added a commit that referenced this pull request Apr 21, 2026
R4: tighten TypeScriptRenderablePostgresMigration docstring to name the
`operations`-vs-`renderTypeScript()` asymmetry explicitly — callers who
know a plan may carry stubs must not enumerate `operations` before the
user has filled them; render to migration.ts and re-load the edited
migration instead. Walk-schema never emits stubs today, so this is
invisible until the Phase 2 issue-planner integration lands.

R5: add a round-trip case for the empty calls list — render →
`tsx`-execute → read `ops.json` → `expect([])`. Pins "the empty scaffold
executes cleanly" as an invariant, not just "the empty scaffold parses
cleanly".
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.

2 participants