Skip to content

feat(mongo-migration): add hand-authored migration authoring#344

Open
wmadden wants to merge 32 commits intomainfrom
mongo-manual-migrations
Open

feat(mongo-migration): add hand-authored migration authoring#344
wmadden wants to merge 32 commits intomainfrom
mongo-manual-migrations

Conversation

@wmadden
Copy link
Copy Markdown
Contributor

@wmadden wmadden commented Apr 15, 2026

closes TML-2244

Intent

Enable users to author MongoDB migrations by hand in TypeScript. A migration file exports a class, runs as a standalone script (tsx migration.ts), and produces ops.json and migration.json that the existing MongoMigrationRunner consumes unchanged. The pattern is designed to generalize to SQL targets later.

Change map

The story

  1. Factory functions for the five Mongo DDL operations. Each function (createIndex, dropIndex, createCollection, dropCollection, collMod) produces a MongoMigrationPlanOperation with correct prechecks, DDL commands, and postchecks — identical output to the existing planner.

  2. A target-agnostic Migration<TOperation> base class. Lives in @prisma-next/migration-tools (framework layer). Provides plan() (abstract), describe() (optional metadata for migration.json), and static run(import.meta.url) (self-execution with entrypoint detection, --dry-run, --help).

  3. A Mongo-specific MongoMigration alias in the family package. Fixes the type parameter to MongoMigrationPlanOperation. Exported from @prisma-next/family-mongo/migration alongside the re-exported factories and strategies — a single import point for users.

  4. Compound strategy functions. validatedCollection composes createCollection + createIndex calls into a reusable multi-operation helper, demonstrating the composition pattern users would follow.

  5. migration.json output. When describe() is implemented, Migration.run() writes a draft manifest (migrationId: null) alongside ops.json, with from/to hashes, kind, labels, and createdAt.

  6. Real-DB E2E tests. Integration tests exercise the full round-trip (factory → JSON.stringifydeserializeMongoOpsMongoMigrationRunner → verify DDL in MongoDB Memory Server) for all five operations and the compound strategy.

  7. Demo app migrations. Both mongo-demo and retail-store include a hand-authored .ts migration alongside their existing planner-generated migration, with tests that verify the migration imports, generates valid artifacts, and executes against a real DB.

Behavior changes & evidence

Compatibility / migration / risk

  • No breaking changes. The existing MongoMigrationPlanner and MongoMigrationRunner are untouched. Factory-produced operations are indistinguishable from planner-produced ones.
  • Spec deviation — import.meta.url vs import.meta: The spec says Migration.run(import.meta). The implementation uses Migration.run(import.meta.url) because tsx does not preserve import.meta properties when the object is passed as a function argument. This is a necessary adaptation for the dev toolchain.
  • Spec deviation — MongoMigration location: The spec says factory functions and Migration are co-located in @prisma-next/target-mongo/migration. The implementation moves MongoMigration to @prisma-next/family-mongo/migration (a higher layer) to better reflect the family-level concern, and uses the family package as the canonical user import. Factories remain in target-mongo; family-mongo re-exports them.
  • Beyond-spec — describe() and migration.json: The spec does not mention manifest output. The implementation adds an optional describe() method and migration.json writing, which is additive and opt-in.

Follow-ups / open questions

  • CLI integration (prisma migration new/plan/verify for Mongo) — explicitly deferred in spec
  • Scaffolding tooling (auto-generating migration.ts from planner output) — future work
  • Refactoring the planner to use factory functions internally — explicitly out of scope
  • The spec's acceptance criteria still reference Migration.run(import.meta) — the spec should be updated to reflect the import.meta.url decision

Non-goals / intentionally out of scope

  • Data transform support for Mongo migrations
  • Transaction support for Mongo migrations
  • Rewriting the Mongo planner to use factory functions internally

Summary by CodeRabbit

  • New Features

    • Migration authoring framework for TypeScript with migration factories (indexes, collections, validation) and a dry-run mode; new public migration entrypoints.
  • Examples

    • Added demo and retail-store migrations demonstrating index creation and product schema validation.
  • Documentation

    • READMEs updated with migration authoring and CLI usage.
  • Tests

    • Added unit, integration, and end-to-end tests validating migration generation and execution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 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 migration authoring framework and Mongo-targeted tooling: a generic Migration base with CLI/dry-run, MongoMigration subclass, migration factory functions (indexes, collections, validation), package exports, example migrations, and extensive tests (unit, integration, E2E) exercising serialization and execution against in-memory MongoDB.

Changes

Cohort / File(s) Summary
Migration Framework Core
packages/1-framework/3-tooling/migration/src/migration-base.ts, packages/1-framework/3-tooling/migration/src/exports/migration.ts, packages/1-framework/3-tooling/migration/package.json, packages/1-framework/3-tooling/migration/tsdown.config.ts
Adds exported Migration<TOperation> base with plan()/describe() contract and static run(import.meta.url) CLI that validates metadata and writes ops.json/migration.json (supports --dry-run/--help). Exposes new ./migration package subpath.
Migration Framework Tests
packages/1-framework/3-tooling/migration/test/migration-base.test.ts
Vitest tests for Migration base, CLI subprocess behavior (--dry-run, --help), metadata validation, error handling, and file output semantics.
Mongo Migration Abstractions
packages/2-mongo-family/9-family/src/core/mongo-migration.ts, packages/2-mongo-family/9-family/src/exports/migration.ts, packages/2-mongo-family/9-family/package.json, packages/2-mongo-family/9-family/tsdown.config.ts
Introduces MongoMigration extending Migration<MongoMigrationPlanOperation>, re-exports it as Migration, and registers ./migration export for the package.
Mongo Migration Factories
packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts, packages/3-mongo-target/1-mongo-target/src/exports/migration.ts, packages/3-mongo-target/1-mongo-target/package.json, packages/3-mongo-target/1-mongo-target/tsdown.config.ts
Implements migration factory functions (createIndex, dropIndex, createCollection, dropCollection, setValidation, validatedCollection) producing MongoMigrationPlanOperation objects with deterministic ids and precheck/execute/postcheck flows. Adds ./migration export and pathe devDependency.
Migration Factory Tests & E2E
packages/3-mongo-target/1-mongo-target/test/migration-factories.test.ts, packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts, test/integration/test/mongo/migration-authoring-e2e.test.ts
Unit/E2E tests validating factory output structure, discriminants, option pass-through, JSON round-trip, subprocess execution (tsx), dry-run behavior, and execution against in-memory MongoDB replica sets.
Examples: mongo-demo
examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.ts, examples/mongo-demo/migrations/20260415_add-posts-author-index/ops.json, examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.json, examples/mongo-demo/test/manual-migration.test.ts, examples/mongo-demo/package.json
Adds example migration that creates two posts indexes, accompanying ops.json/migration.json, a manual Vitest that runs the migration against in-memory MongoDB and checks indexes, and package deps (@prisma-next/target-mongo, pathe).
Examples: retail-store
examples/retail-store/migrations/20260415_add-product-validation/migration.ts, examples/retail-store/migrations/20260415_add-product-validation/ops.json, examples/retail-store/migrations/20260415_add-product-validation/migration.json, examples/retail-store/test/manual-migration.test.ts, examples/retail-store/package.json
Adds example migration adding JSON Schema validation and a compound index on products, with ops/manifest and a manual test validating validator and index; adds @prisma-next/target-mongo and pathe.
Package README & docs
packages/2-mongo-family/9-family/README.md, packages/3-mongo-target/1-mongo-target/README.md
Documents new ./migration entrypoints, migration authoring examples using Migration and factory functions, and lists available migration factories and usage notes.

Sequence Diagram(s)

sequenceDiagram
    actor Dev as Developer
    participant CLI as Migration.run()
    participant Module as Migration Module
    participant Schema as Metadata Validator
    participant FS as Filesystem

    Dev->>CLI: Run migration entrypoint (e.g., tsx migration.ts)
    CLI->>CLI: Check module is entrypoint
    alt not entrypoint
        CLI-->>Dev: no-op (import)
    else entrypoint
        CLI->>Module: Instantiate Migration
        Module-->>CLI: instance
        CLI->>Module: call plan()
        Module-->>CLI: operations[]
        CLI->>Schema: validate describe() metadata (if present)
        Schema-->>CLI: valid / error
        alt --dry-run
            CLI-->>Dev: print ops.json and migration.json to stdout
        else normal run
            CLI->>FS: write ops.json
            CLI->>FS: write migration.json (if metadata)
            FS-->>CLI: write confirmations
            CLI-->>Dev: success message
        end
    end
Loading
sequenceDiagram
    actor User as Runner Caller
    participant Runner as MongoMigrationRunner
    participant Driver as MongoControlDriver
    participant Mongo as MongoDB

    User->>Runner: execute(operations, policy)
    loop per operation
        Runner->>Driver: precheck (ListIndexes/ListCollections)
        Driver->>Mongo: query state
        Mongo-->>Driver: state
        Driver-->>Runner: precheck result
        alt precheck passes
            Runner->>Driver: execute command (CreateIndex/CreateCollection/CollMod/DropIndex/DropCollection)
            Driver->>Mongo: apply command
            Mongo-->>Driver: result
            Driver-->>Runner: execution result
            Runner->>Driver: postcheck (verify)
            Driver->>Mongo: verification
            Mongo-->>Driver: verification result
            Driver-->>Runner: postcheck result
        end
    end
    Runner-->>User: { ok, operationsExecuted }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped through plans and ops tonight,

indexes set just so and tight,
prechecks, posts, a dry-run cheer,
files written, tests appear —
the rabbit nods: migrations delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(mongo-migration): add hand-authored migration authoring' clearly and concisely summarizes the main feature added—enabling hand-authored MongoDB migrations. It follows conventional commit format and directly reflects the core objective documented in the PR.

✏️ 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 mongo-manual-migrations

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

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

🧹 Nitpick comments (6)
packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts (1)

1-6: Prefer pathe over node:path for path operations.

Per coding guidelines, use pathe instead of node:path for consistent cross-platform behavior.

♻️ Suggested change
 import { execFile } from 'node:child_process';
 import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
-import { join, resolve } from 'node:path';
+import { join, resolve } from 'pathe';
 import { promisify } from 'node:util';
 import { afterEach, beforeEach, describe, expect, it } from 'vitest';

Based on learnings: "In the prisma/prisma-next repo, prefer using pathe over Node's built-in node:path."

🤖 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/migration-e2e.test.ts` around
lines 1 - 6, Replace imports from 'node:path' with 'pathe' and update any path
function usage accordingly: change the import statement that currently brings in
join and resolve from 'node:path' to import them from 'pathe' instead, and
ensure any calls to join(...) and resolve(...) in this file (e.g., in
migration-e2e.test.ts) remain the same but now reference the pathe
implementations for consistent cross-platform behavior.
examples/retail-store/test/manual-migration.test.ts (1)

1-2: Prefer pathe over node:path for path operations.

Per coding guidelines, use pathe instead of node:path for consistent cross-platform behavior.

♻️ Suggested change
 import { readFileSync } from 'node:fs';
-import { resolve } from 'node:path';
+import { resolve } from 'pathe';

Based on learnings: "In the prisma/prisma-next repo, prefer using pathe over Node's built-in node:path."

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

In `@examples/retail-store/test/manual-migration.test.ts` around lines 1 - 2, The
import uses Node's built-in path module (import { resolve } from 'node:path')
which violates the guideline to use pathe; replace the node:path usage by
importing resolve from 'pathe' and update any references that call resolve
accordingly (keep readFileSync from 'node:fs' as-is) so path operations use
pathe's cross-platform implementation.
packages/3-mongo-target/1-mongo-target/test/migration-strategies.test.ts (1)

22-27: Consider using toMatchObject for related property assertions.

Multiple assertions on the same cmd object could be consolidated for cleaner test code.

♻️ Suggested refactor
-    expect(cmd).toBeInstanceOf(CreateCollectionCommand);
-    expect(cmd.validator).toEqual({ $jsonSchema: { required: ['email'] } });
-    expect(cmd.validationLevel).toBe('strict');
-    expect(cmd.validationAction).toBe('error');
+    expect(cmd).toBeInstanceOf(CreateCollectionCommand);
+    expect(cmd).toMatchObject({
+      validator: { $jsonSchema: { required: ['email'] } },
+      validationLevel: 'strict',
+      validationAction: 'error',
+    });
🤖 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/migration-strategies.test.ts`
around lines 22 - 27, The test currently makes multiple assertions on the same
CreateCollectionCommand instance (cmd)—replace the separate expect checks for
validator, validationLevel, and validationAction with a single consolidated
assertion using toMatchObject on cmd (or cmd as CreateCollectionCommand) to
assert the related properties together (e.g., expect(cmd).toMatchObject({
validator: { $jsonSchema: { required: ['email'] } }, validationLevel: 'strict',
validationAction: 'error' })), keeping the initial instance check
expect(cmd).toBeInstanceOf(CreateCollectionCommand) intact.
examples/mongo-demo/test/manual-migration.test.ts (2)

5-5: Unused import: Migration.

The Migration import is not used in this test file—the migration class is dynamically imported via import('../migrations/...') instead.

Suggested fix
-import { Migration } from '@prisma-next/family-mongo/migration';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/mongo-demo/test/manual-migration.test.ts` at line 5, The import
Migration from '@prisma-next/family-mongo/migration' in manual-migration.test.ts
is unused because the test loads migrations dynamically via
import('../migrations/...'); remove the unused import statement (the `Migration`
identifier) from that file to eliminate the dead import and ensure there are no
remaining references to `Migration` in the test.

1-2: Use pathe instead of node:path for cross-platform consistency.

Per repository guidelines, prefer pathe over node:path for path manipulation in TypeScript files.

Suggested fix
 import { readFileSync } from 'node:fs';
-import { resolve } from 'node:path';
+import { resolve } from 'pathe';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/mongo-demo/test/manual-migration.test.ts` around lines 1 - 2,
Replace the use of Node's path module with pathe for cross-platform consistency:
change the import of resolve from "node:path" to import { resolve } from "pathe"
(leave readFileSync from "node:fs" as-is) and update any uses of resolve in this
file (e.g., in manual-migration.test.ts) to rely on pathe's resolve.
packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts (1)

29-33: Remove unused collection parameter from keyFilter.

The collection parameter is declared but never used in the function body.

Suggested fix
-function keyFilter(collection: string, keys: ReadonlyArray<MongoIndexKey>) {
+function keyFilter(keys: ReadonlyArray<MongoIndexKey>) {
   return isTextIndex(keys)
     ? MongoFieldFilter.eq('key._fts', 'text')
     : MongoFieldFilter.eq('key', keysToKeySpec(keys));
 }

Then update the call sites at lines 41 and 84:

-  const filter = keyFilter(collection, keys);
+  const filter = keyFilter(keys);
🤖 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/src/core/migration-factories.ts`
around lines 29 - 33, The function keyFilter declares an unused parameter
"collection"; remove that parameter from the keyFilter signature and update its
body to only accept (keys: ReadonlyArray<MongoIndexKey>) same as currently used
logic, then update all call sites that pass a collection (the two places
invoking keyFilter in the same file) to call keyFilter(keys) instead of
keyFilter(collection, keys); ensure imports/types remain unchanged and run
tests/typecheck to verify no remaining references to the removed parameter
(function name: keyFilter; update the two call sites that currently pass
collection).
🤖 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/3-tooling/migration/src/migration-base.ts`:
- Around line 59-69: The help text in printHelp wrongly suggests running a
TypeScript migration directly with Node; update the Usage line in the
printHelp() function to show the supported runner instead (e.g. "Usage:
<migration-runner> <migration-file> [options]" or the actual CLI name your
project exposes) so --help points to a working invocation; edit the string in
printHelp() and keep the rest of the options text unchanged.
- Around line 72-80: Before calling buildManifest(), validate the object
returned by describe() against the MigrationMeta shape (the same Arktype used
elsewhere for plan()) and reject/throw if validation fails; specifically, call
the MigrationMeta validator (or the existing arktype schema used for plan()) on
the describe() result, ensure from/to are strings, kind is a valid kind, and
labels is an array, and only pass the validated/parsed value into
buildManifest(); if validation fails, surface a clear error so malformed JS/TS
migrations cannot produce an invalid migration.json.

In `@packages/1-framework/3-tooling/migration/test/migration-base.test.ts`:
- Around line 67-80: The generated fixture uses raw absolute paths for ESM
imports (migrationBasePath and the import string inside migrationScript), which
are invalid; convert the path to a file:// URL using pathToFileURL(...).href and
use that href in the import specifier. Specifically, update migrationBasePath to
be pathToFileURL(join(packageRoot, 'src/migration-base.ts')).href (ensure
pathToFileURL is imported from 'url'), and change the import line inside
migrationScript to import { Migration } from '<migrationBasePathHref>'; so the
generated fixture emits a proper file:// URL instead of a raw absolute path.

---

Nitpick comments:
In `@examples/mongo-demo/test/manual-migration.test.ts`:
- Line 5: The import Migration from '@prisma-next/family-mongo/migration' in
manual-migration.test.ts is unused because the test loads migrations dynamically
via import('../migrations/...'); remove the unused import statement (the
`Migration` identifier) from that file to eliminate the dead import and ensure
there are no remaining references to `Migration` in the test.
- Around line 1-2: Replace the use of Node's path module with pathe for
cross-platform consistency: change the import of resolve from "node:path" to
import { resolve } from "pathe" (leave readFileSync from "node:fs" as-is) and
update any uses of resolve in this file (e.g., in manual-migration.test.ts) to
rely on pathe's resolve.

In `@examples/retail-store/test/manual-migration.test.ts`:
- Around line 1-2: The import uses Node's built-in path module (import { resolve
} from 'node:path') which violates the guideline to use pathe; replace the
node:path usage by importing resolve from 'pathe' and update any references that
call resolve accordingly (keep readFileSync from 'node:fs' as-is) so path
operations use pathe's cross-platform implementation.

In `@packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts`:
- Around line 29-33: The function keyFilter declares an unused parameter
"collection"; remove that parameter from the keyFilter signature and update its
body to only accept (keys: ReadonlyArray<MongoIndexKey>) same as currently used
logic, then update all call sites that pass a collection (the two places
invoking keyFilter in the same file) to call keyFilter(keys) instead of
keyFilter(collection, keys); ensure imports/types remain unchanged and run
tests/typecheck to verify no remaining references to the removed parameter
(function name: keyFilter; update the two call sites that currently pass
collection).

In `@packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts`:
- Around line 1-6: Replace imports from 'node:path' with 'pathe' and update any
path function usage accordingly: change the import statement that currently
brings in join and resolve from 'node:path' to import them from 'pathe' instead,
and ensure any calls to join(...) and resolve(...) in this file (e.g., in
migration-e2e.test.ts) remain the same but now reference the pathe
implementations for consistent cross-platform behavior.

In `@packages/3-mongo-target/1-mongo-target/test/migration-strategies.test.ts`:
- Around line 22-27: The test currently makes multiple assertions on the same
CreateCollectionCommand instance (cmd)—replace the separate expect checks for
validator, validationLevel, and validationAction with a single consolidated
assertion using toMatchObject on cmd (or cmd as CreateCollectionCommand) to
assert the related properties together (e.g., expect(cmd).toMatchObject({
validator: { $jsonSchema: { required: ['email'] } }, validationLevel: 'strict',
validationAction: 'error' })), keeping the initial instance check
expect(cmd).toBeInstanceOf(CreateCollectionCommand) intact.
🪄 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: 1ddac469-852f-4497-b780-e6c2cc35bced

📥 Commits

Reviewing files that changed from the base of the PR and between 9a32955 and 2565623.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/mongo-migration-authoring/assets/migration-authoring-design.md is excluded by !projects/**
  • projects/mongo-migration-authoring/spec.md is excluded by !projects/**
📒 Files selected for processing (28)
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.json
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.ts
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/ops.json
  • examples/mongo-demo/test/manual-migration.test.ts
  • examples/retail-store/migrations/20260415_add-product-validation/migration.json
  • examples/retail-store/migrations/20260415_add-product-validation/migration.ts
  • examples/retail-store/migrations/20260415_add-product-validation/ops.json
  • examples/retail-store/test/manual-migration.test.ts
  • packages/1-framework/3-tooling/migration/package.json
  • packages/1-framework/3-tooling/migration/src/exports/migration.ts
  • packages/1-framework/3-tooling/migration/src/migration-base.ts
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
  • packages/1-framework/3-tooling/migration/tsdown.config.ts
  • packages/2-mongo-family/9-family/README.md
  • packages/2-mongo-family/9-family/package.json
  • packages/2-mongo-family/9-family/src/core/mongo-migration.ts
  • packages/2-mongo-family/9-family/src/exports/migration.ts
  • packages/2-mongo-family/9-family/tsdown.config.ts
  • packages/3-mongo-target/1-mongo-target/README.md
  • packages/3-mongo-target/1-mongo-target/package.json
  • packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts
  • packages/3-mongo-target/1-mongo-target/src/core/migration-strategies.ts
  • packages/3-mongo-target/1-mongo-target/src/exports/migration.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-factories.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-strategies.test.ts
  • packages/3-mongo-target/1-mongo-target/tsdown.config.ts
  • test/integration/test/mongo/migration-authoring-e2e.test.ts

Comment thread packages/1-framework/3-tooling/migration/src/migration-base.ts
Comment thread packages/1-framework/3-tooling/migration/src/migration-base.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.

♻️ Duplicate comments (2)
packages/1-framework/3-tooling/migration/src/migration-base.ts (2)

108-110: ⚠️ Potential issue | 🟠 Major

Validate describe() output before manifest creation.

Lines 108-110 trust runtime metadata shape. A JS migration (or leaked any) can emit malformed from/to/kind/labels into migration.json, which can fail downstream consumers.

🛡️ Proposed fix (Arktype runtime validation)
+import { type } from 'arktype';
 import { realpathSync, writeFileSync } from 'node:fs';
 import { fileURLToPath } from 'node:url';
 import { dirname, join } from 'pathe';

+const migrationMetaSchema = type({
+  from: 'string',
+  to: 'string',
+  'kind?': "'regular' | 'baseline'",
+  'labels?': 'string[]',
+});
+
 export interface MigrationMeta {
   readonly from: string;
   readonly to: string;
   readonly kind?: 'regular' | 'baseline';
   readonly labels?: readonly string[];
 }
@@
-  const meta: MigrationMeta | undefined =
-    typeof instance.describe === 'function' ? instance.describe() : undefined;
+  const rawMeta: unknown =
+    typeof instance.describe === 'function' ? instance.describe() : undefined;
+  let meta: MigrationMeta | undefined;
+  if (rawMeta !== undefined) {
+    const parsed = migrationMetaSchema(rawMeta);
+    if (parsed instanceof type.errors) {
+      throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);
+    }
+    meta = parsed;
+  }
   const manifest = meta ? buildManifest(meta) : undefined;
#!/bin/bash
set -euo pipefail

# Verify existing Arktype runtime-validation style used in this package/repo.
rg -n --type=ts -C2 "from 'arktype'|instanceof type\\.errors|\\.summary" packages/1-framework/3-tooling/migration

# Verify current unvalidated describe()->buildManifest path.
sed -n '100,116p' packages/1-framework/3-tooling/migration/src/migration-base.ts

As per coding guidelines: "Use Arktype instead of Zod for schema validation and type definitions" and "Use schema validators (Arktype) for structural validation of required fields, types, and shapes rather than redundant manual checks for these properties".

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

In `@packages/1-framework/3-tooling/migration/src/migration-base.ts` around lines
108 - 110, The code calls instance.describe() and passes its result straight to
buildManifest, risking malformed runtime data; add an Arktype runtime validator
for the MigrationMeta shape (e.g., create a migrationMetaValidator using arktype
matching MigrationMeta's required fields like from/to/kind/labels), validate the
output of instance.describe() before calling buildManifest, and only call
buildManifest with the validated value; on validation failure surface a clear
error (throw or processLogger.error + exit) and include the Arktype error
summary so downstream consumers never receive malformed manifests.

59-69: ⚠️ Potential issue | 🟡 Minor

Help output still points to a non-working TS-first invocation.

Line 62 still says node <migration-file>, which is misleading for .ts migration files in this workflow.

📝 Proposed fix
-      'Usage: node <migration-file> [options]',
+      'Usage: tsx <migration-file> [options]',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/3-tooling/migration/src/migration-base.ts` around lines
59 - 69, The help text in printHelp currently says "node <migration-file>" which
is misleading for the TS-first workflow; update the usage string inside the
printHelp function to reflect invoking TypeScript migrations (for example
"ts-node <migration-file.ts> [options]" or the project's actual TS runner), and
update the example and any related help lines to mention ".ts" so users see the
correct invocation for TypeScript migration files.
🤖 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/1-framework/3-tooling/migration/src/migration-base.ts`:
- Around line 108-110: The code calls instance.describe() and passes its result
straight to buildManifest, risking malformed runtime data; add an Arktype
runtime validator for the MigrationMeta shape (e.g., create a
migrationMetaValidator using arktype matching MigrationMeta's required fields
like from/to/kind/labels), validate the output of instance.describe() before
calling buildManifest, and only call buildManifest with the validated value; on
validation failure surface a clear error (throw or processLogger.error + exit)
and include the Arktype error summary so downstream consumers never receive
malformed manifests.
- Around line 59-69: The help text in printHelp currently says "node
<migration-file>" which is misleading for the TS-first workflow; update the
usage string inside the printHelp function to reflect invoking TypeScript
migrations (for example "ts-node <migration-file.ts> [options]" or the project's
actual TS runner), and update the example and any related help lines to mention
".ts" so users see the correct invocation for TypeScript migration files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 1cc53245-8274-4790-a923-a05284fd3b50

📥 Commits

Reviewing files that changed from the base of the PR and between 2565623 and 8ff3305.

📒 Files selected for processing (2)
  • packages/1-framework/3-tooling/migration/src/migration-base.ts
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts

Comment thread examples/mongo-demo/test/manual-migration.test.ts Outdated
Comment thread examples/mongo-demo/test/manual-migration.test.ts
Comment thread packages/1-framework/3-tooling/migration/src/migration-base.ts Outdated
Comment thread packages/1-framework/3-tooling/migration/src/migration-base.ts Outdated
Comment thread packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts Outdated
Comment thread packages/3-mongo-target/1-mongo-target/src/core/migration-strategies.ts Outdated
Comment thread packages/3-mongo-target/1-mongo-target/README.md 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: 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/3-mongo-target/1-mongo-target/src/core/migration-factories.ts`:
- Around line 60-64: The CreateIndexCommand construction silently drops an
explicit false for options.unique because the code uses `options?.unique ||
undefined`; replace that with a nullish check or conditional spread so false is
preserved (e.g. use `options?.unique ?? undefined` or spread
`...(options?.unique !== undefined ? { unique: options.unique } : {})`) when
building the command arguments for CreateIndexCommand (refer to the
`CreateIndexCommand`, `options`, `unique`, and `name` symbols).
🪄 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: 1248d5da-58a9-40e8-a35c-88b3842f4d24

📥 Commits

Reviewing files that changed from the base of the PR and between 8ff3305 and 494465a.

⛔ Files ignored due to path filters (5)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/mongo-migration-authoring/plan.md is excluded by !projects/**
  • projects/mongo-migration-authoring/spec.md is excluded by !projects/**
  • projects/mongo-migration-authoring/specs/migration-subsystem-refactor.spec.md is excluded by !projects/**
  • projects/mongo-migration-authoring/specs/planner-dual-output.spec.md is excluded by !projects/**
📒 Files selected for processing (18)
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.ts
  • examples/mongo-demo/package.json
  • examples/mongo-demo/test/manual-migration.test.ts
  • examples/retail-store/migrations/20260415_add-product-validation/migration.json
  • examples/retail-store/migrations/20260415_add-product-validation/migration.ts
  • examples/retail-store/migrations/20260415_add-product-validation/ops.json
  • examples/retail-store/package.json
  • examples/retail-store/test/manual-migration.test.ts
  • packages/1-framework/3-tooling/migration/src/migration-base.ts
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
  • packages/2-mongo-family/9-family/src/exports/migration.ts
  • packages/3-mongo-target/1-mongo-target/README.md
  • packages/3-mongo-target/1-mongo-target/package.json
  • packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts
  • packages/3-mongo-target/1-mongo-target/src/exports/migration.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-factories.test.ts
  • test/integration/test/mongo/migration-authoring-e2e.test.ts
✅ Files skipped from review due to trivial changes (8)
  • examples/retail-store/migrations/20260415_add-product-validation/migration.json
  • examples/retail-store/migrations/20260415_add-product-validation/ops.json
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.ts
  • packages/3-mongo-target/1-mongo-target/README.md
  • examples/mongo-demo/package.json
  • examples/mongo-demo/test/manual-migration.test.ts
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-factories.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/2-mongo-family/9-family/src/exports/migration.ts
  • packages/3-mongo-target/1-mongo-target/package.json
  • packages/3-mongo-target/1-mongo-target/src/exports/migration.ts
  • examples/retail-store/migrations/20260415_add-product-validation/migration.ts
  • packages/1-framework/3-tooling/migration/src/migration-base.ts

@wmadden wmadden force-pushed the mongo-manual-migrations branch from d796c2f to c52bcc9 Compare April 15, 2026 19:01
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 15, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/middleware-telemetry

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

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

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

@prisma-next/runtime-executor

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-pipeline-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-pipeline-builder@344

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: eabd838

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.

🧹 Nitpick comments (1)
packages/1-framework/3-tooling/migration/src/migration-base.ts (1)

114-114: Redundant type check for describe method.

Since describe() is defined on the abstract Migration base class (returning undefined by default), typeof instance.describe === 'function' will always be true. The check is defensive but unnecessary given the class hierarchy.

This is a minor nit and safe to keep for robustness if desired.

♻️ Optional simplification
   let manifest: Record<string, unknown> | undefined;
-  if (typeof instance.describe === 'function') {
-    const rawMeta: unknown = instance.describe();
-    if (rawMeta !== undefined) {
-      const parsed = MigrationMetaSchema(rawMeta);
-      if (parsed instanceof type.errors) {
-        throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);
-      }
-      manifest = buildManifest(parsed);
+  const rawMeta: unknown = instance.describe();
+  if (rawMeta !== undefined) {
+    const parsed = MigrationMetaSchema(rawMeta);
+    if (parsed instanceof type.errors) {
+      throw new Error(`describe() returned invalid metadata: ${parsed.summary}`);
     }
+    manifest = buildManifest(parsed);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/3-tooling/migration/src/migration-base.ts` at line 114,
The typeof check around instance.describe is redundant because the abstract
Migration class defines describe (returns undefined by default); remove the
conditional guard and call instance.describe() directly where it's used
(reference the Migration base class and the instance.describe method in
migration-base.ts) so the code simplifies to a straight invocation while
preserving behavior; alternatively, if you want to keep defensive checks,
replace the typeof guard with a comment explaining why it remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/1-framework/3-tooling/migration/src/migration-base.ts`:
- Line 114: The typeof check around instance.describe is redundant because the
abstract Migration class defines describe (returns undefined by default); remove
the conditional guard and call instance.describe() directly where it's used
(reference the Migration base class and the instance.describe method in
migration-base.ts) so the code simplifies to a straight invocation while
preserving behavior; alternatively, if you want to keep defensive checks,
replace the typeof guard with a comment explaining why it remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 8de708a8-f386-485c-9825-62ff6e1e7db6

📥 Commits

Reviewing files that changed from the base of the PR and between 494465a and d796c2f.

📒 Files selected for processing (5)
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.ts
  • examples/retail-store/migrations/20260415_add-product-validation/migration.ts
  • packages/1-framework/3-tooling/migration/src/migration-base.ts
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/retail-store/migrations/20260415_add-product-validation/migration.ts
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/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: 2

🧹 Nitpick comments (4)
test/integration/test/mongo/migration-authoring-e2e.test.ts (2)

203-208: Use object matchers for related field checks.

These assertions check related fields one-by-one; prefer a single object matcher for clearer intent and guideline consistency.

Example refactor pattern
-        for (let i = 0; i < original.length; i++) {
-          expect(deserialized[i]!.id).toBe(original[i]!.id);
-          expect(deserialized[i]!.label).toBe(original[i]!.label);
-          expect(deserialized[i]!.operationClass).toBe(original[i]!.operationClass);
-        }
+        for (let i = 0; i < original.length; i++) {
+          expect(deserialized[i]).toMatchObject({
+            id: original[i]!.id,
+            label: original[i]!.label,
+            operationClass: original[i]!.operationClass,
+          });
+        }

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".

Also applies to: 230-232, 255-257

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

In `@test/integration/test/mongo/migration-authoring-e2e.test.ts` around lines 203
- 208, The loop at lines checking deserialized[i] against original[i] should
replace multiple per-field expects with a single object matcher: use
expect(deserialized[i]).toMatchObject({ id: original[i].id, label:
original[i].label, operationClass: original[i].operationClass }) inside the same
loop (and similarly refactor the other occurrences noted around the file at the
blocks currently at 230-232 and 255-257) so tests assert related fields together
using toMatchObject.

299-350: Reuse runOps path in lifecycle steps to avoid drift and strengthen assertions.

Step 2/3 duplicate runner wiring and only assert result.ok. Reusing a single helper path (with optional origin/destination hashes) will reduce duplication and let you assert operationsExecuted consistently.

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

In `@test/integration/test/mongo/migration-authoring-e2e.test.ts` around lines 299
- 350, The test duplicates MongoMigrationRunner wiring for step2/step3 and only
asserts result.ok; refactor to reuse the existing runOps helper (or create one
if missing) to execute migrations with optional origin/destination hashes so
both steps call runOps(serializedOps, { originHash?, destinationHash? }) instead
of repeating create/close/controlDriver/runner.execute logic; then assert not
only result.ok but also the runner's operationsExecuted (or the helper's
returned operationsExecuted) for each step to ensure operations were applied and
avoid lifecycle drift—target symbols: runOps, MongoMigrationRunner,
runner.execute, operationsExecuted, serialized2/serialized3.
examples/mongo-demo/test/manual-migration.test.ts (2)

54-56: Consolidate related operation assertions into one object matcher.

These are related checks on the same structure; prefer a single matcher to align with repo test style.

♻️ Proposed refactor
       const instance = new AddPostsAuthorIndex();
       const ops = instance.plan();
       expect(ops).toHaveLength(2);
-      expect(ops[0].id).toBe('index.posts.create(authorId:1)');
-      expect(ops[1].id).toBe('index.posts.create(createdAt:-1,authorId:1)');
+      expect(ops).toMatchObject([
+        { id: 'index.posts.create(authorId:1)' },
+        { id: 'index.posts.create(createdAt:-1,authorId:1)' },
+      ]);

As per coding guidelines: examples/**/test/**/*.{ts,js}: 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 `@examples/mongo-demo/test/manual-migration.test.ts` around lines 54 - 56, The
test currently asserts related properties of the same ops array using multiple
expect(...).toBe calls; replace those with a single object matcher: assert ops
length is 2 and use toMatchObject (or toEqual with partial objects) on ops to
verify both entries at once (e.g., match ops[0].id and ops[1].id) so the
assertions consolidate into one object matcher for the ops array; update
assertions referencing ops, ops[0].id and ops[1].id accordingly.

62-67: Use a single manifest shape assertion instead of field-by-field toBe checks.

These fields are one related payload; use toMatchObject with string matchers for the formatted fields.

♻️ Proposed refactor
-      expect(manifest.migrationId).toBeNull();
-      expect(manifest.kind).toBe('regular');
-      expect(manifest.labels).toEqual(['add-posts-author-index']);
-      expect(manifest.from).toMatch(/^sha256:/);
-      expect(manifest.to).toMatch(/^sha256:/);
-      expect(manifest.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
+      expect(manifest).toMatchObject({
+        migrationId: null,
+        kind: 'regular',
+        labels: ['add-posts-author-index'],
+        from: expect.stringMatching(/^sha256:/),
+        to: expect.stringMatching(/^sha256:/),
+        createdAt: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/),
+      });

As per coding guidelines: examples/**/test/**/*.{ts,js}: 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 `@examples/mongo-demo/test/manual-migration.test.ts` around lines 62 - 67,
Replace the multiple field-by-field assertions on the manifest with a single
object-shape assertion: use expect(manifest).toMatchObject({...}) to assert
migrationId, kind, labels exactly and use expect.stringMatching(/regex/) for
formatted strings (for example use expect.stringMatching(/^sha256:/) for from
and to and expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/) for createdAt); update
the test in manual-migration.test.ts to assert the whole manifest shape in one
toMatchObject call rather than separate expect(...).toBe() assertions.
🤖 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/2-mongo-family/9-family/package.json`:
- Line 50: The package export adds a plane-specific subpath "./migration" which
violates the multi-plane subpath contract; update the package.json exports by
removing the "./migration" subpath and expose that same module under the
migration/control plane subpath (use "./control" as the export key) so the
existing target module (the current "./dist/migration.mjs" artifact) is
reachable via "./control"; ensure no duplicate plane subpaths remain and that
any consumers referencing "./migration" are updated to use "./control".

In `@test/integration/test/mongo/migration-authoring-e2e.test.ts`:
- Around line 50-56: The teardown's single try block can skip replSet?.stop() if
client?.close() throws; update the afterAll teardown so both resources are
always attempted independently (e.g., call client?.close() in its own try/catch
and replSet?.stop() in a separate try/catch, or use a try/finally where close is
attempted then replSet.stop() in finally). Locate the afterAll block with calls
to client?.close() and replSet?.stop() and ensure errors from one do not prevent
the other from running, logging or ignoring each error separately.

---

Nitpick comments:
In `@examples/mongo-demo/test/manual-migration.test.ts`:
- Around line 54-56: The test currently asserts related properties of the same
ops array using multiple expect(...).toBe calls; replace those with a single
object matcher: assert ops length is 2 and use toMatchObject (or toEqual with
partial objects) on ops to verify both entries at once (e.g., match ops[0].id
and ops[1].id) so the assertions consolidate into one object matcher for the ops
array; update assertions referencing ops, ops[0].id and ops[1].id accordingly.
- Around line 62-67: Replace the multiple field-by-field assertions on the
manifest with a single object-shape assertion: use
expect(manifest).toMatchObject({...}) to assert migrationId, kind, labels
exactly and use expect.stringMatching(/regex/) for formatted strings (for
example use expect.stringMatching(/^sha256:/) for from and to and
expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/) for createdAt); update the test in
manual-migration.test.ts to assert the whole manifest shape in one toMatchObject
call rather than separate expect(...).toBe() assertions.

In `@test/integration/test/mongo/migration-authoring-e2e.test.ts`:
- Around line 203-208: The loop at lines checking deserialized[i] against
original[i] should replace multiple per-field expects with a single object
matcher: use expect(deserialized[i]).toMatchObject({ id: original[i].id, label:
original[i].label, operationClass: original[i].operationClass }) inside the same
loop (and similarly refactor the other occurrences noted around the file at the
blocks currently at 230-232 and 255-257) so tests assert related fields together
using toMatchObject.
- Around line 299-350: The test duplicates MongoMigrationRunner wiring for
step2/step3 and only asserts result.ok; refactor to reuse the existing runOps
helper (or create one if missing) to execute migrations with optional
origin/destination hashes so both steps call runOps(serializedOps, {
originHash?, destinationHash? }) instead of repeating
create/close/controlDriver/runner.execute logic; then assert not only result.ok
but also the runner's operationsExecuted (or the helper's returned
operationsExecuted) for each step to ensure operations were applied and avoid
lifecycle drift—target symbols: runOps, MongoMigrationRunner, runner.execute,
operationsExecuted, serialized2/serialized3.
🪄 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: ed4fff7d-9d4c-4a29-b2ae-cff615ab1b94

📥 Commits

Reviewing files that changed from the base of the PR and between d796c2f and aa44917.

⛔ Files ignored due to path filters (6)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/mongo-migration-authoring/assets/migration-authoring-design.md is excluded by !projects/**
  • projects/mongo-migration-authoring/plan.md is excluded by !projects/**
  • projects/mongo-migration-authoring/spec.md is excluded by !projects/**
  • projects/mongo-migration-authoring/specs/migration-subsystem-refactor.spec.md is excluded by !projects/**
  • projects/mongo-migration-authoring/specs/planner-dual-output.spec.md is excluded by !projects/**
📒 Files selected for processing (28)
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.json
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.ts
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/ops.json
  • examples/mongo-demo/package.json
  • examples/mongo-demo/test/manual-migration.test.ts
  • examples/retail-store/migrations/20260415_add-product-validation/migration.json
  • examples/retail-store/migrations/20260415_add-product-validation/migration.ts
  • examples/retail-store/migrations/20260415_add-product-validation/ops.json
  • examples/retail-store/package.json
  • examples/retail-store/test/manual-migration.test.ts
  • packages/1-framework/3-tooling/migration/package.json
  • packages/1-framework/3-tooling/migration/src/exports/migration.ts
  • packages/1-framework/3-tooling/migration/src/migration-base.ts
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
  • packages/1-framework/3-tooling/migration/tsdown.config.ts
  • packages/2-mongo-family/9-family/README.md
  • packages/2-mongo-family/9-family/package.json
  • packages/2-mongo-family/9-family/src/core/mongo-migration.ts
  • packages/2-mongo-family/9-family/src/exports/migration.ts
  • packages/2-mongo-family/9-family/tsdown.config.ts
  • packages/3-mongo-target/1-mongo-target/README.md
  • packages/3-mongo-target/1-mongo-target/package.json
  • packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts
  • packages/3-mongo-target/1-mongo-target/src/exports/migration.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-factories.test.ts
  • packages/3-mongo-target/1-mongo-target/tsdown.config.ts
  • test/integration/test/mongo/migration-authoring-e2e.test.ts
✅ Files skipped from review due to trivial changes (17)
  • packages/1-framework/3-tooling/migration/tsdown.config.ts
  • examples/retail-store/package.json
  • packages/2-mongo-family/9-family/src/exports/migration.ts
  • packages/2-mongo-family/9-family/tsdown.config.ts
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.json
  • packages/3-mongo-target/1-mongo-target/src/exports/migration.ts
  • packages/3-mongo-target/1-mongo-target/tsdown.config.ts
  • packages/2-mongo-family/9-family/src/core/mongo-migration.ts
  • examples/retail-store/migrations/20260415_add-product-validation/migration.json
  • packages/3-mongo-target/1-mongo-target/README.md
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/ops.json
  • examples/mongo-demo/package.json
  • examples/retail-store/migrations/20260415_add-product-validation/ops.json
  • examples/retail-store/migrations/20260415_add-product-validation/migration.ts
  • packages/1-framework/3-tooling/migration/src/exports/migration.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-factories.test.ts
  • packages/1-framework/3-tooling/migration/src/migration-base.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/1-framework/3-tooling/migration/package.json
  • packages/3-mongo-target/1-mongo-target/package.json
  • examples/mongo-demo/migrations/20260415_add-posts-author-index/migration.ts
  • examples/retail-store/test/manual-migration.test.ts
  • packages/2-mongo-family/9-family/README.md
  • packages/1-framework/3-tooling/migration/test/migration-base.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts
  • packages/3-mongo-target/1-mongo-target/src/core/migration-factories.ts

Comment thread packages/2-mongo-family/9-family/package.json
Comment thread test/integration/test/mongo/migration-authoring-e2e.test.ts Outdated
wmadden added 21 commits April 16, 2026 07:05
Scaffold project for adding a TypeScript migration authoring surface
to the Mongo target. Factory functions produce operations directly,
migration files are self-executing via a Migration base class, and
strategies are plain function composition over the factories.
Five factory functions (createIndex, dropIndex, createCollection,
dropCollection, collMod) that produce MongoMigrationPlanOperation
objects identical to what the planner generates. Extracted from the
planner's inline logic, reusing existing AST primitives and helpers.

Exported from @prisma-next/target-mongo/migration.
Generic Migration<TOperation> with abstract plan() and static run()
for self-executing migration files. Entrypoint detection uses
import.meta.url (not import.meta object) for tsx compatibility.
Supports --dry-run and --help flags.

Exported from @prisma-next/migration-tools/migration.
MongoMigration extends Migration<MongoMigrationPlanOperation> so users
get a single import for both Migration and factory functions from
@prisma-next/target-mongo/migration.
validatedCollection composes createCollection (with validator) +
createIndex calls into a flat operation list. E2E tests verify the
full pipeline: write migration.ts, run with tsx, validate ops.json
structure, --dry-run output, and strategy composition.
Add migration entrypoint, usage example, factory and strategy
reference to the package README.
Migration files can now implement describe() to provide metadata
(from, to, kind, labels). When present, Migration.run() writes both
ops.json and migration.json. The manifest is a draft (migrationId: null)
with createdAt auto-set. Dry-run prints both files to stdout.
MongoMigration is a family-level concern (not target-specific).
Canonical import is now @prisma-next/family-mongo/migration which
re-exports factories and strategies from @prisma-next/target-mongo/migration.
Covers all five factory functions (createCollection, createIndex,
dropIndex, dropCollection, collMod) and the validatedCollection strategy.
Verifies the full round-trip: factory → JSON.stringify →
deserializeMongoOps → MongoMigrationRunner → verify DDL in MongoDB.
Also tests a multi-step create → modify → drop lifecycle.
Canonical import for migration authoring is now
@prisma-next/family-mongo/migration. Updated both target-mongo and
family-mongo READMEs with correct import paths and describe() example.
Demonstrates the migration authoring workflow: a TypeScript migration
file using factory functions and describe() to produce both ops.json
and migration.json. Test verifies the migration imports, runs against
a real MongoDB, and produces valid artifacts.
Demonstrates collMod (JSON Schema validation on products) and compound
index creation. Test verifies the migration can be imported, run against
a real MongoDB, and produces valid artifacts.
…hods

noImplicitOverride is enabled in the shared tsconfig base, requiring
the override keyword on methods that override base class members.
Two task specs under the mongo-migration-authoring project:

1. migration-subsystem-refactor: move planner, runner, serializer, and
   supporting modules from adapter-mongo to target-mongo; refactor
   runner to accept abstract visitor interfaces instead of Db.

2. planner-dual-output: introduce OpFactoryCall intermediate
   representation, with renderers for raw operations (existing
   behavior) and TypeScript migration files.
- Replace node:path with pathe in E2E and demo test files (F01, F05)
- Remove unused collection param from keyFilter helper (F02)
- Add optional operationClass override to collMod factory (F03)
- Remove factory re-exports from family-mongo/migration (F06)
- Split demo migration imports: Migration from family-mongo, factories from target-mongo
- Migration.run(import.meta) -> Migration.run(import.meta.url)
- Document describe() and migration.json as optional features
- Document collMod operationClass override parameter
…strategies

- Remove thin collMod wrapper; add setValidation factory with schema-level API
- Move validatedCollection from separate strategies file into factories
- Delete migration-strategies.ts and its test file
- Update README to remove atomic/non-atomic differentiation
- Update demo migrations and all tests to use new API
…tion

- Rewrite doc comment to clearly explain entrypoint guard behavior
- Rename executeMigration to serializeMigration (it serializes, not executes)
- Update help text from node to tsx
- Validate describe() return with arktype before building manifest
- Replace dynamic await import() with static imports for migration classes
- Load migration.json from disk to verify structure
- Remove unused Migration import from mongo-demo test
wmadden added 10 commits April 16, 2026 07:05
…d of re-importing

Eliminates the dynamic import() in serializeMigration by accepting the
migration class directly. This makes the dependency graph static and
the serialization fully synchronous.

API change: Migration.run(import.meta.url, MyMigration)
Use Result.failure (not .error) and cast CollectionInfo to
Record<string, unknown> for options access.
Spec and plan for adding data transform support to MongoDB migrations.
Users author transforms using existing query builders (mongoRaw,
mongoPipeline), which produce MongoQueryPlan ASTs from a scaffolded
contract. Plans serialize to ops.json as JSON using the same kind-based
pattern as DDL commands and execute at apply time via MongoAdapter.
…rity

Restructure to lead with the problem and a grounding example,
build the narrative progressively (authoring → serialization →
execution), move decisions before implementation details, and
push alternatives considered to the end.
Replace || with ?? to avoid coercing false to undefined.
Ensures replSet.stop() runs even if client.close() throws.
@wmadden wmadden force-pushed the mongo-manual-migrations branch from 3db0eed to 4ecabe1 Compare April 16, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant