Skip to content

Commit 4cc06e3

Browse files
Copilothotlong
andcommitted
Add fumadocs blog functionality with sample posts
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 97345d8 commit 4cc06e3

7 files changed

Lines changed: 353 additions & 2 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { blogSource } from '@/lib/source';
2+
import type { Metadata } from 'next';
3+
import { notFound } from 'next/navigation';
4+
import defaultMdxComponents from 'fumadocs-ui/mdx';
5+
import { HomeLayout } from 'fumadocs-ui/home-layout';
6+
import { baseOptions } from '@/app/layout.config';
7+
import Link from 'next/link';
8+
9+
export default async function BlogPostPage({
10+
params,
11+
}: {
12+
params: { slug?: string[] };
13+
}) {
14+
const page = blogSource.getPage(params.slug);
15+
if (!page) notFound();
16+
17+
const MDX = page.data.body;
18+
19+
return (
20+
<HomeLayout {...baseOptions}>
21+
<main className="container py-12 md:py-24">
22+
<article className="mx-auto max-w-4xl">
23+
<Link
24+
href="/blog"
25+
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground mb-8 transition-colors"
26+
>
27+
<svg
28+
xmlns="http://www.w3.org/2000/svg"
29+
width="16"
30+
height="16"
31+
viewBox="0 0 24 24"
32+
fill="none"
33+
stroke="currentColor"
34+
strokeWidth="2"
35+
strokeLinecap="round"
36+
strokeLinejoin="round"
37+
className="mr-2"
38+
>
39+
<polyline points="15 18 9 12 15 6" />
40+
</svg>
41+
Back to Blog
42+
</Link>
43+
44+
<header className="mb-8">
45+
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl mb-4">
46+
{page.data.title}
47+
</h1>
48+
49+
{page.data.description && (
50+
<p className="text-xl text-muted-foreground mb-4">
51+
{page.data.description}
52+
</p>
53+
)}
54+
55+
<div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground border-t pt-4">
56+
{page.data.date && (
57+
<time dateTime={page.data.date}>
58+
{new Date(page.data.date).toLocaleDateString('en-US', {
59+
year: 'numeric',
60+
month: 'long',
61+
day: 'numeric',
62+
})}
63+
</time>
64+
)}
65+
{page.data.authors && (
66+
<span>by {page.data.authors.join(', ')}</span>
67+
)}
68+
</div>
69+
70+
{page.data.tags && page.data.tags.length > 0 && (
71+
<div className="flex flex-wrap gap-2 mt-4">
72+
{page.data.tags.map((tag: string) => (
73+
<span
74+
key={tag}
75+
className="inline-flex items-center rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
76+
>
77+
{tag}
78+
</span>
79+
))}
80+
</div>
81+
)}
82+
</header>
83+
84+
<div className="prose prose-slate dark:prose-invert max-w-none">
85+
<MDX components={{ ...defaultMdxComponents }} />
86+
</div>
87+
88+
<footer className="mt-12 pt-8 border-t">
89+
<Link
90+
href="/blog"
91+
className="inline-flex items-center text-sm text-primary hover:underline"
92+
>
93+
<svg
94+
xmlns="http://www.w3.org/2000/svg"
95+
width="16"
96+
height="16"
97+
viewBox="0 0 24 24"
98+
fill="none"
99+
stroke="currentColor"
100+
strokeWidth="2"
101+
strokeLinecap="round"
102+
strokeLinejoin="round"
103+
className="mr-2"
104+
>
105+
<polyline points="15 18 9 12 15 6" />
106+
</svg>
107+
Back to Blog
108+
</Link>
109+
</footer>
110+
</article>
111+
</main>
112+
</HomeLayout>
113+
);
114+
}
115+
116+
export async function generateStaticParams() {
117+
return blogSource.generateParams();
118+
}
119+
120+
export function generateMetadata({ params }: { params: { slug?: string[] } }): Metadata {
121+
const page = blogSource.getPage(params.slug);
122+
if (!page) notFound();
123+
124+
return {
125+
title: page.data.title,
126+
description: page.data.description,
127+
};
128+
}

apps/site/app/blog/page.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { blogSource } from '@/lib/source';
2+
import Link from 'next/link';
3+
import { HomeLayout } from 'fumadocs-ui/home-layout';
4+
import { baseOptions } from '@/app/layout.config';
5+
6+
export default function BlogPage() {
7+
const posts = blogSource.getPages();
8+
9+
return (
10+
<HomeLayout {...baseOptions}>
11+
<main className="container py-12 md:py-24">
12+
<div className="mx-auto max-w-4xl">
13+
<div className="mb-12 text-center">
14+
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl mb-4">
15+
ObjectQL Blog
16+
</h1>
17+
<p className="text-lg text-muted-foreground">
18+
Updates, tutorials, and insights about the standard protocol for AI software generation
19+
</p>
20+
</div>
21+
22+
<div className="space-y-8">
23+
{posts
24+
.sort((a, b) => {
25+
const dateA = new Date(a.data.date || 0);
26+
const dateB = new Date(b.data.date || 0);
27+
return dateB.getTime() - dateA.getTime();
28+
})
29+
.map((post) => (
30+
<article
31+
key={post.url}
32+
className="group rounded-xl border bg-card p-6 transition-all hover:shadow-md"
33+
>
34+
<Link href={post.url} className="block">
35+
<div className="mb-2 flex items-center gap-4 text-sm text-muted-foreground">
36+
{post.data.date && (
37+
<time dateTime={post.data.date}>
38+
{new Date(post.data.date).toLocaleDateString('en-US', {
39+
year: 'numeric',
40+
month: 'long',
41+
day: 'numeric',
42+
})}
43+
</time>
44+
)}
45+
{post.data.authors && (
46+
<span>by {post.data.authors.join(', ')}</span>
47+
)}
48+
</div>
49+
50+
<h2 className="mb-2 text-2xl font-bold group-hover:text-primary transition-colors">
51+
{post.data.title}
52+
</h2>
53+
54+
{post.data.description && (
55+
<p className="text-muted-foreground mb-4">
56+
{post.data.description}
57+
</p>
58+
)}
59+
60+
{post.data.tags && post.data.tags.length > 0 && (
61+
<div className="flex flex-wrap gap-2">
62+
{post.data.tags.map((tag: string) => (
63+
<span
64+
key={tag}
65+
className="inline-flex items-center rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
66+
>
67+
{tag}
68+
</span>
69+
))}
70+
</div>
71+
)}
72+
</Link>
73+
</article>
74+
))}
75+
</div>
76+
77+
{posts.length === 0 && (
78+
<div className="text-center py-12">
79+
<p className="text-muted-foreground">No blog posts yet. Check back soon!</p>
80+
</div>
81+
)}
82+
</div>
83+
</main>
84+
</HomeLayout>
85+
);
86+
}

apps/site/app/layout.config.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export const baseOptions: Omit<DocsLayoutProps, 'tree'> = {
1111
url: '/docs',
1212
active: 'nested-url',
1313
},
14+
{
15+
text: 'Blog',
16+
url: '/blog',
17+
active: 'nested-url',
18+
},
1419
],
1520
githubUrl: 'https://github.com/objectstack-ai/objectql',
1621
// Enable sidebar search
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
title: Understanding ObjectQL's Protocol-Driven Architecture
3+
description: A deep dive into how ObjectQL separates intent from implementation through its protocol-driven design.
4+
date: 2026-01-19
5+
authors:
6+
- ObjectQL Team
7+
tags:
8+
- architecture
9+
- technical
10+
- protocol
11+
---
12+
13+
# Understanding ObjectQL's Protocol-Driven Architecture
14+
15+
One of ObjectQL's core principles is being **Protocol-Driven, Not Code-Driven**. But what does this mean in practice?
16+
17+
## The Problem with Traditional ORMs
18+
19+
Traditional ORMs mix two concerns:
20+
21+
1. **What** you want (the business logic)
22+
2. **How** to achieve it (the implementation details)
23+
24+
This creates several issues:
25+
26+
- Tight coupling to specific databases
27+
- Difficulty in testing and mocking
28+
- Hard to optimize queries
29+
- Challenging for AI to generate correctly
30+
31+
## The ObjectQL Approach
32+
33+
ObjectQL decouples these concerns through a three-layer architecture:
34+
35+
### 1. The Intent Layer (YAML Schema)
36+
37+
```yaml
38+
name: project
39+
fields:
40+
name:
41+
type: text
42+
required: true
43+
status:
44+
type: select
45+
options: [planning, active, completed]
46+
```
47+
48+
This layer is **database-agnostic**. It describes what you want, not how to achieve it.
49+
50+
### 2. The Compilation Layer
51+
52+
The ObjectQL engine acts as a **compiler**, not a runtime wrapper:
53+
54+
- Validates schema integrity
55+
- Injects permission checks
56+
- Optimizes query patterns
57+
- Generates type definitions
58+
59+
### 3. The Driver Layer
60+
61+
Drivers translate the compiled AST into database-specific operations:
62+
63+
- `@objectql/driver-sql` → PostgreSQL, MySQL
64+
- `@objectql/driver-mongo` → MongoDB
65+
- `@objectql/driver-sdk` → Remote HTTP APIs
66+
67+
## Benefits for AI Code Generation
68+
69+
This architecture makes ObjectQL ideal for AI-driven development:
70+
71+
✅ **Hallucination-Free**: YAML schemas are validated before compilation
72+
✅ **Type-Safe**: All operations are strongly typed
73+
✅ **Testable**: Mock drivers for testing without a real database
74+
✅ **Portable**: Change databases without rewriting business logic
75+
76+
## Learn More
77+
78+
Want to dive deeper? Check out:
79+
80+
- [Architecture Specification](/docs/reference/spec/architecture)
81+
- [Core Concepts](/docs/modeling/concepts)
82+
- [Driver Development Guide](/docs/reference/api/drivers)

apps/site/content/blog/welcome.mdx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Welcome to ObjectQL Blog
3+
description: Introducing the ObjectQL blog - your source for updates, tutorials, and insights about the standard protocol for AI software generation.
4+
date: 2026-01-20
5+
authors:
6+
- ObjectQL Team
7+
tags:
8+
- announcement
9+
- getting-started
10+
---
11+
12+
# Welcome to the ObjectQL Blog
13+
14+
We're excited to launch the ObjectQL blog! This is your go-to resource for:
15+
16+
## What You'll Find Here
17+
18+
- **Product Updates**: Stay informed about new features, improvements, and releases
19+
- **Technical Deep Dives**: Learn about the architecture and design decisions behind ObjectQL
20+
- **Best Practices**: Discover how to get the most out of ObjectQL in your projects
21+
- **Community Stories**: Hear from developers building with ObjectQL
22+
23+
## Why ObjectQL?
24+
25+
ObjectQL represents a fundamental shift in how we approach software development. Instead of writing boilerplate code, you define **intent** through declarative schemas. The framework handles:
26+
27+
- ✅ Type-safe database schemas
28+
- ✅ Validated CRUD operations
29+
- ✅ Permission enforcement
30+
- ✅ API endpoint generation
31+
32+
## Getting Started
33+
34+
If you're new to ObjectQL, check out our [Getting Started Guide](/docs/getting-started) to begin your journey.
35+
36+
## Stay Connected
37+
38+
Follow our blog for regular updates, and don't hesitate to [contribute on GitHub](https://github.com/objectstack-ai/objectql) or join our community discussions.
39+
40+
Happy coding! 🚀

apps/site/lib/source.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import { docs, meta } from '@/.source';
1+
import { docs, meta, blog } from '@/.source';
22
import { createMDXSource } from 'fumadocs-mdx';
33
import { loader } from 'fumadocs-core/source';
44

55
export const source = loader({
66
baseUrl: '/docs',
77
source: createMDXSource(docs, meta),
88
});
9+
10+
export const blogSource = loader({
11+
baseUrl: '/blog',
12+
source: createMDXSource(blog, []),
13+
});

apps/site/source.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
1+
import { defineDocs, defineConfig, defineCollections } from 'fumadocs-mdx/config';
22
import { rehypeCode } from 'fumadocs-core/mdx-plugins';
33
import remarkGfm from 'remark-gfm';
44
import remarkMath from 'remark-math';
@@ -10,6 +10,11 @@ export const { docs, meta } = defineDocs({
1010
dir: 'content/docs',
1111
});
1212

13+
export const blog = defineCollections({
14+
dir: 'content/blog',
15+
type: 'doc',
16+
});
17+
1318
export default defineConfig({
1419
mdxOptions: {
1520
remarkPlugins: [

0 commit comments

Comments
 (0)