Skip to content

Commit 3f7517e

Browse files
authored
Merge pull request #142 from objectstack-ai/copilot/add-fumadocs-blog-feature
2 parents df4eabb + 67e0495 commit 3f7517e

File tree

6 files changed

+376
-2
lines changed

6 files changed

+376
-2
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
// Blog listing component
10+
function BlogListing() {
11+
const posts = blogSource.getPages();
12+
13+
return (
14+
<div className="mx-auto max-w-4xl">
15+
<div className="mb-12 text-center">
16+
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl mb-4">
17+
ObjectQL Blog
18+
</h1>
19+
<p className="text-lg text-muted-foreground">
20+
Updates, tutorials, and insights about the standard protocol for AI software generation
21+
</p>
22+
</div>
23+
24+
<div className="space-y-8">
25+
{posts
26+
.sort((a, b) => {
27+
const dateA = new Date(a.data.date || 0);
28+
const dateB = new Date(b.data.date || 0);
29+
return dateB.getTime() - dateA.getTime();
30+
})
31+
.map((post) => (
32+
<article
33+
key={post.url}
34+
className="group rounded-xl border bg-card p-6 transition-all hover:shadow-md"
35+
>
36+
<Link href={post.url} className="block">
37+
<div className="mb-2 flex items-center gap-4 text-sm text-muted-foreground">
38+
{post.data.date && (
39+
<time dateTime={post.data.date}>
40+
{new Date(post.data.date).toLocaleDateString('en-US', {
41+
year: 'numeric',
42+
month: 'long',
43+
day: 'numeric',
44+
})}
45+
</time>
46+
)}
47+
{post.data.authors && (
48+
<span>by {post.data.authors.join(', ')}</span>
49+
)}
50+
</div>
51+
52+
<h2 className="mb-2 text-2xl font-bold group-hover:text-primary transition-colors">
53+
{post.data.title}
54+
</h2>
55+
56+
{post.data.description && (
57+
<p className="text-muted-foreground mb-4">
58+
{post.data.description}
59+
</p>
60+
)}
61+
62+
{post.data.tags && post.data.tags.length > 0 && (
63+
<div className="flex flex-wrap gap-2">
64+
{post.data.tags.map((tag) => (
65+
<span
66+
key={tag}
67+
className="inline-flex items-center rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
68+
>
69+
{tag}
70+
</span>
71+
))}
72+
</div>
73+
)}
74+
</Link>
75+
</article>
76+
))}
77+
</div>
78+
79+
{posts.length === 0 && (
80+
<div className="text-center py-12">
81+
<p className="text-muted-foreground">No blog posts yet. Check back soon!</p>
82+
</div>
83+
)}
84+
</div>
85+
);
86+
}
87+
88+
// Blog post component
89+
function BlogPost({ page }: { page: ReturnType<typeof blogSource.getPage> }) {
90+
if (!page) return null;
91+
92+
const MDX = page.data.body;
93+
94+
return (
95+
<article className="mx-auto max-w-4xl">
96+
<Link
97+
href="/blog"
98+
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground mb-8 transition-colors"
99+
>
100+
<svg
101+
xmlns="http://www.w3.org/2000/svg"
102+
width="16"
103+
height="16"
104+
viewBox="0 0 24 24"
105+
fill="none"
106+
stroke="currentColor"
107+
strokeWidth="2"
108+
strokeLinecap="round"
109+
strokeLinejoin="round"
110+
className="mr-2"
111+
>
112+
<polyline points="15 18 9 12 15 6" />
113+
</svg>
114+
Back to Blog
115+
</Link>
116+
117+
<header className="mb-8">
118+
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl mb-4">
119+
{page.data.title}
120+
</h1>
121+
122+
{page.data.description && (
123+
<p className="text-xl text-muted-foreground mb-4">
124+
{page.data.description}
125+
</p>
126+
)}
127+
128+
<div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground border-t pt-4">
129+
{page.data.date && (
130+
<time dateTime={page.data.date}>
131+
{new Date(page.data.date).toLocaleDateString('en-US', {
132+
year: 'numeric',
133+
month: 'long',
134+
day: 'numeric',
135+
})}
136+
</time>
137+
)}
138+
{page.data.authors && (
139+
<span>by {page.data.authors.join(', ')}</span>
140+
)}
141+
</div>
142+
143+
{page.data.tags && page.data.tags.length > 0 && (
144+
<div className="flex flex-wrap gap-2 mt-4">
145+
{page.data.tags.map((tag) => (
146+
<span
147+
key={tag}
148+
className="inline-flex items-center rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
149+
>
150+
{tag}
151+
</span>
152+
))}
153+
</div>
154+
)}
155+
</header>
156+
157+
<div className="prose prose-slate dark:prose-invert max-w-none">
158+
<MDX components={{ ...defaultMdxComponents }} />
159+
</div>
160+
161+
<footer className="mt-12 pt-8 border-t">
162+
<Link
163+
href="/blog"
164+
className="inline-flex items-center text-sm text-primary hover:underline"
165+
>
166+
<svg
167+
xmlns="http://www.w3.org/2000/svg"
168+
width="16"
169+
height="16"
170+
viewBox="0 0 24 24"
171+
fill="none"
172+
stroke="currentColor"
173+
strokeWidth="2"
174+
strokeLinecap="round"
175+
strokeLinejoin="round"
176+
className="mr-2"
177+
>
178+
<polyline points="15 18 9 12 15 6" />
179+
</svg>
180+
Back to Blog
181+
</Link>
182+
</footer>
183+
</article>
184+
);
185+
}
186+
187+
export default async function BlogPage({
188+
params,
189+
}: {
190+
params: { slug?: string[] };
191+
}) {
192+
// If no slug, show blog listing
193+
if (!params.slug || params.slug.length === 0) {
194+
return (
195+
<HomeLayout {...baseOptions}>
196+
<main className="container py-12 md:py-24">
197+
<BlogListing />
198+
</main>
199+
</HomeLayout>
200+
);
201+
}
202+
203+
// Otherwise, show individual blog post
204+
const page = blogSource.getPage(params.slug);
205+
if (!page) notFound();
206+
207+
return (
208+
<HomeLayout {...baseOptions}>
209+
<main className="container py-12 md:py-24">
210+
<BlogPost page={page} />
211+
</main>
212+
</HomeLayout>
213+
);
214+
}
215+
216+
export async function generateStaticParams() {
217+
return blogSource.generateParams();
218+
}
219+
220+
export function generateMetadata({ params }: { params: { slug?: string[] } }): Metadata {
221+
// Default metadata for blog listing
222+
if (!params.slug || params.slug.length === 0) {
223+
return {
224+
title: 'Blog',
225+
description: 'Updates, tutorials, and insights about the standard protocol for AI software generation',
226+
};
227+
}
228+
229+
// Metadata for individual blog post
230+
const page = blogSource.getPage(params.slug);
231+
if (!page) notFound();
232+
233+
return {
234+
title: page.data.title,
235+
description: page.data.description,
236+
};
237+
}

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+
});

0 commit comments

Comments
 (0)