Skip to content

Mongo ORM: extendable Collection class + collections registry (ADR 175)#936

Open
ankur-arch wants to merge 5 commits into
mainfrom
feat/mongo-orm-extendable-collection
Open

Mongo ORM: extendable Collection class + collections registry (ADR 175)#936
ankur-arch wants to merge 5 commits into
mainfrom
feat/mongo-orm-extendable-collection

Conversation

@ankur-arch

@ankur-arch ankur-arch commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What this ships

You can now write custom repository classes for the Mongo ORM, exactly like the SQL ORM (ADR 175):

import { Collection } from '@prisma-next/mongo-orm'; // also re-exported from @prisma-next/mongo/runtime

class UserRepository extends Collection<Contract, 'User'> {
  byEmail(email: string) { return this.where({ email }); }
  newestFirst()          { return this.orderBy({ _id: -1 }); }
}

const db = mongo({ contract, url, collections: { User: UserRepository } });

await db.orm.users.byEmail('alice@example.com').newestFirst().take(10).all();

Before this PR, MongoCollection was interface-only — extends Collection was impossible on Mongo. This was reported on Discord ([NEXT], 2026-07-08); follow-up to #934.

The changes

  • Collection is exported and extendable from @prisma-next/mongo-orm (and @prisma-next/mongo/runtime). It is the same chaining implementation that already backed the interface — no behavior change.
  • mongoOrm({ collections }) and mongo({ collections }) register subclasses by model name. Registered roots are typed as your subclass; everything else keeps the base type. Existing call sites compile unchanged.
  • Chains keep your subclass, at runtime and in the types: where, select, orderBy, take, and skip return this, so users.byEmail(x).take(1).newestFirst() typechecks. include() and variant() change the collection's generic parameters, so they intentionally widen to the base type (documented on the class).
  • The registry only accepts real Collection subclasses — enforced at the type level (a brand on the class) and at runtime (a clear error if a non-Collection class is registered from untyped code).

Review feedback addressed (CodeRabbit + ponytail pass)

  • Fluent methods no longer widen to the base type mid-chain; a type test covers custom → base → custom chaining.
  • All production as unknown as casts in the touched files replaced with blindCast + reason.
  • AnyMongoCollectionClass tightened from => object to the branded instance shape.
  • Custom construction centralized in one helper with an instanceof guard and clear error message.
  • ADR 175 no longer claims the Mongo custom-collection example is "not yet implemented"; its example now shows the shipped API.
  • Minimalism pass: deleted the redundant constructor type alias; #cloneWithVariant now delegates to #clone. Skipped: the SQL-style callback where-DSL on Mongo and subclass typing through include()/variant() — add when the cross-family where-DSL generalizes (ADR 175's extraction step).

Testing

  • 12 unit tests + 6 type tests in mongo-orm (registry wiring, plan parity with the base collection, subclass preservation, brand rejection, runtime guard).
  • Facade tests: mongo({ collections }) forwarding (unit) and a full round-trip against a real in-memory replica set (e2e).
  • Full pnpm test:packages sweep (926/928 files; the 2 failures are machine-local flakes that reproduce on clean main and pass in isolation), mongo + emit integration suites, mongo-demo example suite, pnpm lint:deps, typecheck/lint on both packages.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a model-keyed collections registry to the Mongo ORM (mongoOrm) and mongo facade, enabling custom Collection subclasses per model.
    • Custom collections are exposed on the root client and preserve fluent chaining, including domain helpers.
  • Bug Fixes
    • Chained operations now consistently retain the registered subclass type (including variant() and other query-chain methods).
    • Invalid registry entries that don’t extend Collection now throw a clear runtime error.
  • Documentation
    • Updated the Mongo architecture/ADR to clarify pipeline behavior and custom-collection rollout.
  • Tests
    • Added typing checks plus runtime and e2e coverage for registration, chaining, and error handling.

The Mongo ORM's chaining implementation was locked behind the
MongoCollection interface and createMongoCollection factory, so users
could not do what the SQL ORM supports:

  class UserRepository extends Collection<Contract, 'User'> {
    byEmail(email: string) { return this.where({ email }); }
  }

The implementation class is now the exported, extendable `Collection`
(mirroring @prisma-next/sql-orm-client). Chaining methods clone through
this.constructor so a chain started from a subclass stays that subclass.
mongoOrm() and the mongo() facade accept a `collections` registry keyed
by model name; registered roots surface the subclass (typed via
InstanceType), unregistered roots keep the base collection.

The shared-interface extraction across families stays future work per
ADR 175's spike-then-extract plan; the ADR carries a status note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ankur Datta <64993082+ankur-arch@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 35004b3d-0838-4c9e-9731-983b30680380

📥 Commits

Reviewing files that changed from the base of the PR and between 254a589 and 6ca6542.

📒 Files selected for processing (1)
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md
✅ Files skipped from review due to trivial changes (1)
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md

📝 Walkthrough

Walkthrough

Adds a subclassable Mongo ORM Collection, maps registered custom collections onto ORM roots, forwards a collections registry through mongo(), and updates tests plus documentation for the shipped custom-collection flow.

Changes

Custom Collection subclassing support

Layer / File(s) Summary
Collection subclassing
packages/2-mongo-family/5-query-builders/orm/src/collection.ts, packages/2-mongo-family/5-query-builders/orm/src/exports/index.ts
Exports Collection, updates fluent methods and cloning to preserve subclass identity, and re-exports the collection surface publicly.
mongoOrm collections registry
packages/2-mongo-family/5-query-builders/orm/src/mongo-orm.ts
Adds a collections registry, custom-collection type mapping, runtime instantiation of registered subclasses, and validation that registered classes extend Collection.
mongo extension wiring
packages/3-extensions/mongo/src/runtime/mongo.ts, packages/3-extensions/mongo/src/exports/runtime.ts
Threads Collections through MongoClient and mongo() options, forwards collections into mongoOrm, and re-exports Mongo ORM collection types and value.
Tests and documentation
packages/2-mongo-family/5-query-builders/orm/test/custom-collection.test-d.ts, packages/2-mongo-family/5-query-builders/orm/test/custom-collection.test.ts, packages/3-extensions/mongo/test/mongo.e2e.test.ts, packages/3-extensions/mongo/test/mongo.test.ts, docs/architecture docs/adrs/ADR 175 - Shared ORM Collection interface.md, skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md
Adds typing, runtime, and e2e coverage for custom collection registration, chaining, variant behavior, pipeline stages, forwarding, and the updated ADR example/status text.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant mongo
  participant mongoOrm
  participant Collection
  participant MongoQueryExecutor
  mongo->>mongoOrm: mongoOrm(contract, executor, collections)
  mongoOrm->>Collection: instantiate registered subclass
  Collection->>MongoQueryExecutor: run chained query
  mongoOrm-->>mongo: MongoClient with custom roots
Loading

Suggested reviewers: wmadden

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the exported subclassable Collection class and collections registry added in this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mongo-orm-extendable-collection
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/mongo-orm-extendable-collection

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

@pkg-pr-new

pkg-pr-new Bot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 93f2d5f

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 153.18 KB (0%)
postgres / emit 128.51 KB (0%)
mongo / no-emit 98.27 KB (-0.1% 🔽)
mongo / emit 89.31 KB (-0.09% 🔽)
cf-worker / no-emit 180.2 KB (0%)
cf-worker / emit 153.16 KB (0%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/3-extensions/mongo/src/runtime/mongo.ts (1)

34-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated Collections constraint into a shared alias.

The clause Collections extends Partial<Record<string, AnyMongoCollectionClass>> = Record<never, never> is duplicated 7 times in this file. A shared type alias would reduce drift risk if the constraint ever changes.

♻️ Proposed refactor
+export type MongoCollectionsMap = Partial<Record<string, AnyMongoCollectionClass>>;
+
 export interface MongoClient<
   TContract extends MongoContractWithTypeMaps<MongoContract, AnyMongoTypeMaps>,
-  Collections extends Partial<Record<string, AnyMongoCollectionClass>> = Record<never, never>,
+  Collections extends MongoCollectionsMap = Record<never, never>,
 > {

(and apply the same substitution at the other six occurrences)

Also applies to: 67-67, 83-83, 99-99, 127-127, 131-131, 137-137

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/3-extensions/mongo/src/runtime/mongo.ts` at line 34, The
`Collections` generic constraint is repeated across several declarations in
`mongo.ts`, so extract `Collections extends Partial<Record<string,
AnyMongoCollectionClass>> = Record<never, never>` into a shared type alias and
replace all seven occurrences with that alias. Update the affected generic
signatures in the relevant Mongo types/functions so the constraint is defined
once and reused consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/architecture` docs/adrs/ADR 175 - Shared ORM Collection interface.md:
- Line 5: The Mongo ORM example text in the ADR is stale and conflicts with the
updated status note; refresh the example heading and surrounding wording in the
ADR section that references the Mongo ORM target so it reflects that custom
collections are already implemented. Use the nearby Mongo example and the
shared-interface “spike then extract” language as anchors, and make sure the
wording is internally consistent with the `mongoOrm({ collections })`, `mongo({
collections })`, and extendable `Collection` behavior now described.

In `@packages/2-mongo-family/5-query-builders/orm/src/collection.ts`:
- Line 301: The include refinement in Collection should not use a bare
production cast; replace the existing `as unknown as Collection<...>` in the
include-cloning path with `blindCast<...>('include() refines the TIncludes
phantom type after cloning with the added include expression')` so the intent is
explicit and the no-bare-casts rule stays enforceable.
- Around line 830-839: The fluent query methods on Collection are widening
custom subclasses back to MongoCollection, which drops subclass-specific methods
after chaining. Update the return typing for the fluent builders in Collection
so methods like where, select, take, skip, orderBy, and include preserve the
current subtype via this or a self-type, and use `#clone` as the place to ensure
subclass instances are returned. Add a d.ts test covering a custom repository
subclass (for example UserRepository with a byName method) chained through
multiple query steps to verify the subclass type is retained.

In `@packages/2-mongo-family/5-query-builders/orm/src/mongo-orm.ts`:
- Line 77: The final return in mongo-orm.ts still uses a bare type assertion;
replace the `client as MongoOrmClient<TContract, Collections>` cast with the
existing `blindCast` helper to keep production code consistent. Update the
return in the `createMongoOrm` flow so the `MongoOrmClient` value is wrapped via
`blindCast` instead of using `as`, and keep the already imported `blindCast`
symbol as the location anchor.

---

Nitpick comments:
In `@packages/3-extensions/mongo/src/runtime/mongo.ts`:
- Line 34: The `Collections` generic constraint is repeated across several
declarations in `mongo.ts`, so extract `Collections extends
Partial<Record<string, AnyMongoCollectionClass>> = Record<never, never>` into a
shared type alias and replace all seven occurrences with that alias. Update the
affected generic signatures in the relevant Mongo types/functions so the
constraint is defined once and reused consistently.
🪄 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: 6ec48a0c-9547-414e-99dc-c670fd24bffd

📥 Commits

Reviewing files that changed from the base of the PR and between f44cbcb and 0d16b5f.

📒 Files selected for processing (10)
  • docs/architecture docs/adrs/ADR 175 - Shared ORM Collection interface.md
  • packages/2-mongo-family/5-query-builders/orm/src/collection.ts
  • packages/2-mongo-family/5-query-builders/orm/src/exports/index.ts
  • packages/2-mongo-family/5-query-builders/orm/src/mongo-orm.ts
  • packages/2-mongo-family/5-query-builders/orm/test/custom-collection.test-d.ts
  • packages/2-mongo-family/5-query-builders/orm/test/custom-collection.test.ts
  • packages/3-extensions/mongo/src/exports/runtime.ts
  • packages/3-extensions/mongo/src/runtime/mongo.ts
  • packages/3-extensions/mongo/test/mongo.e2e.test.ts
  • packages/3-extensions/mongo/test/mongo.test.ts

Comment thread packages/2-mongo-family/5-query-builders/orm/src/collection.ts Outdated
Comment thread packages/2-mongo-family/5-query-builders/orm/src/collection.ts
Comment thread packages/2-mongo-family/5-query-builders/orm/src/mongo-orm.ts Outdated
ankur-arch and others added 2 commits July 9, 2026 16:13
- where/select/orderBy/take/skip return `this` on both the MongoCollection
  interface and the Collection class, so custom -> base -> custom chains
  (repo.byName(x).take(1).newestFirst()) typecheck; include()/variant()
  re-parameterize generics and deliberately widen to the base type.
- AnyMongoCollectionClass now requires the Collection type-level brand
  (string-key brand, mirroring ENUM_TYPE_HANDLE_BRAND), so only real
  Collection subclasses can be registered.
- Custom construction is centralized in instantiateCustomCollection with
  an instanceof guard that throws a clear error for non-Collection classes.
- Remaining production `as unknown as` casts replaced with blindCast +
  reason.
- ADR 175 example no longer claims the Mongo side is unimplemented and
  shows the shipped object-form where/orderBy API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ankur Datta <64993082+ankur-arch@users.noreply.github.com>
…ngo collections surface

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ankur Datta <64993082+ankur-arch@users.noreply.github.com>
@ankur-arch

Copy link
Copy Markdown
Contributor Author

Ponytail review (lazy-senior-dev ladder over the final diff):

  • Necessity — every addition maps to a reported user need or a review item; no speculative surface. The one new abstraction (instantiateCustomCollection) was reviewer-requested and is 20 lines.
  • Reuse — clone-through-this.constructor, the string-key brand, and blindCast all follow existing repo patterns (SQL Collection, ENUM_TYPE_HANDLE_BRAND, no-bare-casts).
  • Deletion over addition — the redundant MongoCollectionConstructor alias is gone, #cloneWithVariant now delegates to #clone, and the this-typed interface signatures are shorter than what they replaced. Repo-wide cast ratchet went down (lint:casts delta −7).
  • Deliberate shortcuts, markedinclude()/variant() widen the static type back to the base collection (documented on the class and interface); the SQL-style callback where-DSL is not ported.

→ skipped: callback where-DSL on Mongo, subclass typing through include()/variant(), cross-family interface extraction — add when ADR 175's where-DSL generalization lands.

ankur-arch and others added 2 commits July 9, 2026 17:06
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ankur Datta <64993082+ankur-arch@users.noreply.github.com>
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