Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
40c9ff8
update emitter output fixtures and canonicalization for ADR 172
wmadden Mar 31, 2026
f6bf50a
update emitter to produce ADR 172 contract structure
wmadden Mar 31, 2026
1d06288
add roots field to test contracts and update emitter test assertions
wmadden Mar 31, 2026
60fa3a9
docs: add M3 (Mongo emitter hook), renumber milestones M3-M6
wmadden Apr 1, 2026
11bfe78
extract shared domain-level .d.ts generation utilities into framework…
wmadden Apr 1, 2026
358197d
scaffold Mongo emitter hook package and implement mongoTargetFamilyHook
wmadden Apr 1, 2026
fa3c22a
add M3 task 3.7: Mongo demo app wiring emitted contract through full …
wmadden Apr 1, 2026
4a1f8b4
add tests for mongoTargetFamilyHook validation and generation
wmadden Apr 1, 2026
7d6d803
add blog fixture IR and end-to-end test for Mongo emitter hook
wmadden Apr 1, 2026
6ef1321
fix type safety in Mongo emitter: bracket access and safe casts
wmadden Apr 1, 2026
fea3422
update docs: Mongo emitter README, framework emitter exports, M3 plan…
wmadden Apr 1, 2026
1df135c
split DomainRelation into DomainReferenceRelation | DomainEmbedRelati…
wmadden Apr 1, 2026
0f7bfb4
consolidate Mongo model types with framework domain types
wmadden Apr 1, 2026
b9f6dde
update Mongo contract schema for ADR 177
wmadden Apr 1, 2026
cda19e7
update Mongo storage validation for ADR 177
wmadden Apr 1, 2026
0501a3b
move ReferenceRelationKeys/EmbedRelationKeys to framework domain types
wmadden Apr 1, 2026
077e7ee
use owner-based embed detection in Mongo ORM buildLookupStages
wmadden Apr 1, 2026
887ba26
update all Mongo test fixtures for ADR 177
wmadden Apr 1, 2026
e70e01e
update mongo-core README to reflect ADR 177 type changes
wmadden Apr 1, 2026
6d54cde
scaffold examples/mongo-demo with ADR 177 contract artifacts
wmadden Apr 1, 2026
bb15518
add mongo-demo integration test with MongoMemoryReplSet
wmadden Apr 1, 2026
f41983f
use spinUpDbServer timeout for mongo-demo hooks
wmadden Apr 1, 2026
535fdfa
mark M3.7 complete in project plan
wmadden Apr 1, 2026
8d47d72
add mongodb to pnpm catalog for version consistency
wmadden Apr 1, 2026
6cceae5
convert mongo-demo to Vite React app with polymorphic task-tracker sc…
wmadden Apr 1, 2026
0019a84
fix seed data to use consistent string IDs for $lookup
wmadden Apr 1, 2026
7f568cc
thread contract-derived types through the entire demo app
wmadden Apr 1, 2026
1be8b9f
regenerate lockfile after rebase onto M2 base branch
wmadden Apr 1, 2026
a36d8a6
fix(canonicalization): preserve storage.collections entries in emitte…
wmadden Apr 1, 2026
c7dde2c
fix(mongo-core): accept emitter-produced contract JSON in validation
wmadden Apr 1, 2026
09a8dc0
feat(mongo-demo): wire demo through emitter pipeline (M3 task 3.8)
wmadden Apr 1, 2026
ae2b80c
docs: add M3 tasks 3.8 and 3.9 to project plan
wmadden Apr 1, 2026
44e21aa
fix(mongo-emitter): address M3 code review findings
wmadden Apr 1, 2026
937e1c2
docs: update M3 spec for emitter pipeline requirement
wmadden Apr 1, 2026
4dc42a9
fix: pass renderCtx to generateModelsType and fix ts-expect-error pla…
wmadden Apr 1, 2026
444496e
test(emitter): cover missing-property branches in generateModelRelati…
wmadden Apr 1, 2026
bbf9005
test(cli): cover graph renderer and mapper branch coverage gaps
wmadden Apr 1, 2026
b93b29c
fix(cli): use mutable Set to fix ReadonlySet typecheck error in test
wmadden Apr 1, 2026
4a3f1ca
harden mongo-demo tests: allSettled teardown, order-independent asser…
wmadden Apr 1, 2026
ce1724d
remove duplicate relations declarations from generated contract.d.ts
wmadden Apr 1, 2026
2cd24c3
use serializeValue for relation field names to escape special characters
wmadden Apr 1, 2026
6006ac6
validate embed relation owner identity in mongo storage validator
wmadden Apr 1, 2026
c31371e
strengthen mongo emitter validation: storage guard, reject missing ba…
wmadden Apr 1, 2026
544ec1d
docs: update M3 plan with codec contribution consolidation follow-up …
wmadden Apr 2, 2026
1200d5b
docs(review): update code review findings to reflect resolution status
wmadden Apr 2, 2026
1aa4ff5
test(emitter): add canonicalization tests for Mongo storage.collections
wmadden Apr 2, 2026
c5541fd
refactor(mongo-core): use Arktype native record syntax for contract s…
wmadden Apr 2, 2026
83626d2
fix(sql-orm-client): improve error messages in resolveIncludeRelation…
wmadden Apr 2, 2026
aa771ac
feat(mongo-core): add codec-types subpath export, eliminate demo stri…
wmadden Apr 2, 2026
b488985
docs(mongo-core): document codec-types subpath export in README
wmadden Apr 2, 2026
5f24935
Mark task complete
wmadden Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion architecture.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@
"layer": "core",
"plane": "shared"
},
{
"glob": "packages/2-mongo-family/3-tooling/emitter/**",
"domain": "mongo",
"layer": "tooling",
"plane": "migration"
},
{
"glob": "packages/2-mongo-family/4-orm/**",
"domain": "mongo",
Expand Down Expand Up @@ -322,6 +328,6 @@
"layerOrder": {
"framework": ["core", "authoring", "tooling", "runtime-executor"],
"sql": ["core", "authoring", "tooling", "lanes", "runtime", "adapters", "drivers"],
"mongo": ["core", "orm", "runtime"]
"mongo": ["core", "tooling", "orm", "runtime"]
}
}
4 changes: 4 additions & 0 deletions examples/mongo-demo/biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"extends": "//"
}
12 changes: 12 additions & 0 deletions examples/mongo-demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mongo Demo — Task Tracker</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/app/main.tsx"></script>
</body>
</html>
41 changes: 41 additions & 0 deletions examples/mongo-demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "mongo-demo",
"private": true,
"type": "module",
"engines": {
"node": ">=24"
},
"scripts": {
"emit": "tsx scripts/generate-contract.ts",
"dev:api": "tsx src/server.ts",
"dev": "vite",
"test": "vitest run",
"typecheck": "tsc --project tsconfig.json --noEmit"
},
"dependencies": {
"@prisma-next/adapter-mongo": "workspace:*",
"@prisma-next/contract": "workspace:*",
"@prisma-next/driver-mongo": "workspace:*",
"@prisma-next/mongo-core": "workspace:*",
"@prisma-next/mongo-orm": "workspace:*",
"@prisma-next/mongo-runtime": "workspace:*",
"mongodb": "catalog:",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@prisma-next/emitter": "workspace:*",
"@prisma-next/mongo-emitter": "workspace:*",
"@prisma-next/test-utils": "workspace:*",
"@prisma-next/tsconfig": "workspace:*",
"@types/node": "catalog:",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react-swc": "^4.2.3",
"mongodb-memory-server": "^10.4.0",
"tsx": "^4.19.2",
"typescript": "catalog:",
"vite": "catalog:",
"vitest": "catalog:"
}
}
84 changes: 84 additions & 0 deletions examples/mongo-demo/scripts/generate-contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import type { ContractIR } from '@prisma-next/contract/ir';
import type { TypesImportSpec } from '@prisma-next/contract/types';
import { emit } from '@prisma-next/emitter';
import { mongoTargetFamilyHook } from '@prisma-next/mongo-emitter';

const blogIR: ContractIR = {
schemaVersion: '1',
targetFamily: 'mongo',
target: 'mongo',
roots: {
users: 'User',
posts: 'Post',
},
models: {
User: {
fields: {
_id: { codecId: 'mongo/objectId@1', nullable: false },
name: { codecId: 'mongo/string@1', nullable: false },
email: { codecId: 'mongo/string@1', nullable: false },
bio: { codecId: 'mongo/string@1', nullable: true },
},
relations: {
posts: {
to: 'Post',
cardinality: '1:N',
on: { localFields: ['_id'], targetFields: ['authorId'] },
},
},
storage: { collection: 'users' },
},
Post: {
fields: {
_id: { codecId: 'mongo/objectId@1', nullable: false },
title: { codecId: 'mongo/string@1', nullable: false },
content: { codecId: 'mongo/string@1', nullable: false },
authorId: { codecId: 'mongo/objectId@1', nullable: false },
createdAt: { codecId: 'mongo/date@1', nullable: false },
},
relations: {
author: {
to: 'User',
cardinality: 'N:1',
on: { localFields: ['authorId'], targetFields: ['_id'] },
},
},
storage: { collection: 'posts' },
},
},
relations: {},
storage: {
collections: {
users: {},
posts: {},
},
},
extensionPacks: {},
capabilities: {},
meta: {},
sources: {},
};

const codecTypeImports: TypesImportSpec[] = [
{
package: '@prisma-next/mongo-core/codec-types',
named: 'CodecTypes',
alias: 'MongoCodecTypes',
},
];

async function main() {
const result = await emit(blogIR, { outputDir: '.', codecTypeImports }, mongoTargetFamilyHook);

const srcDir = resolve(import.meta.dirname, '..', 'src');
writeFileSync(resolve(srcDir, 'contract.json'), result.contractJson + '\n');
writeFileSync(resolve(srcDir, 'contract.d.ts'), result.contractDts);
console.log('Generated contract.json and contract.d.ts in src/');
}

main().catch((err) => {
console.error('Failed to generate contract:', err);
process.exit(1);
});
104 changes: 104 additions & 0 deletions examples/mongo-demo/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useEffect, useState } from 'react';
import type { ApiPost, ApiUser } from '../types';
import { PostList } from './PostList';
import { UserList } from './UserList';

type Tab = 'posts' | 'users';

export function App() {
const [tab, setTab] = useState<Tab>('posts');
const [posts, setPosts] = useState<ApiPost[]>([]);
const [users, setUsers] = useState<ApiUser[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
async function load() {
try {
const [postsRes, usersRes] = await Promise.all([fetch('/api/posts'), fetch('/api/users')]);

if (!postsRes.ok || !usersRes.ok) {
throw new Error('API request failed');
}

setPosts((await postsRes.json()) as ApiPost[]);
setUsers((await usersRes.json()) as ApiUser[]);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}
load();
}, []);

if (loading) {
return (
<div className="container">
<div className="loading">Loading...</div>
</div>
);
}

if (error) {
return (
<div className="container">
<div className="error">
<h2>Error</h2>
<p>{error}</p>
<p className="hint">
Make sure the API server is running: <code>pnpm dev:api</code>
</p>
</div>
</div>
);
}

return (
<div className="container">
<header>
<h1>Blog</h1>
<p className="subtitle">Prisma Next — Mongo ORM demo with emitter-generated contract</p>
</header>

<nav className="tabs">
<button
type="button"
className={tab === 'posts' ? 'active' : ''}
onClick={() => setTab('posts')}
>
Posts ({posts.length})
</button>
<button
type="button"
className={tab === 'users' ? 'active' : ''}
onClick={() => setTab('users')}
>
Authors ({users.length})
</button>
</nav>

<main>{tab === 'posts' ? <PostList posts={posts} /> : <UserList users={users} />}</main>

<footer>
<div className="legend">
<h3>Features demonstrated</h3>
<ul>
<li>
<strong>Emitter pipeline</strong> — Contract artifacts generated by{' '}
<code>emit(ir, options, mongoTargetFamilyHook)</code>
</li>
<li>
<strong>Reference relations</strong> — Post.author resolves to User via{' '}
<code>$lookup</code> on <code>authorId</code>
</li>
<li>
<strong>Contract-first types</strong> — All types derived from the emitted{' '}
<code>contract.d.ts</code>
</li>
</ul>
</div>
</footer>
</div>
);
}
29 changes: 29 additions & 0 deletions examples/mongo-demo/src/app/PostList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { ApiPost } from '../types';

function PostCard({ post }: { post: ApiPost }) {
return (
<div className="card">
<div className="card-header">
<h3>{post.title}</h3>
</div>
<p className="card-content">{post.content}</p>
<div className="card-meta">
{post.author && <span className="badge badge-assignee">By {post.author.name}</span>}
<span className="badge">{new Date(post.createdAt).toLocaleDateString()}</span>
</div>
</div>
);
}

export function PostList({ posts }: { posts: ApiPost[] }) {
return (
<div className="post-list">
<h2>Posts ({posts.length})</h2>
<div className="cards">
{posts.map((post) => (
<PostCard key={post._id} post={post} />
))}
</div>
</div>
);
}
30 changes: 30 additions & 0 deletions examples/mongo-demo/src/app/UserList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { ApiUser } from '../types';

function UserCard({ user }: { user: ApiUser }) {
return (
<div className="card">
<div className="card-header">
<div className="avatar">{user.name.charAt(0)}</div>
<div>
<h3>{user.name}</h3>
<p className="email">{user.email}</p>
</div>
</div>
{user.bio && <p className="card-content">{user.bio}</p>}
{!user.bio && <p className="no-data">No bio</p>}
</div>
);
}

export function UserList({ users }: { users: ApiUser[] }) {
return (
<div className="user-list">
<h2>Authors ({users.length})</h2>
<div className="cards">
{users.map((user) => (
<UserCard key={user._id} user={user} />
))}
</div>
</div>
);
}
13 changes: 13 additions & 0 deletions examples/mongo-demo/src/app/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import './styles.css';

const el = document.getElementById('root');
if (!el) throw new Error('Missing #root element');

createRoot(el).render(
<StrictMode>
<App />
</StrictMode>,
);
Loading
Loading