Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
237 changes: 237 additions & 0 deletions apps/site/app/blog/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import { blogSource } from '@/lib/source';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { HomeLayout } from 'fumadocs-ui/home-layout';
import { baseOptions } from '@/app/layout.config';
import Link from 'next/link';

// Blog listing component
function BlogListing() {
const posts = blogSource.getPages();

return (
<div className="mx-auto max-w-4xl">
<div className="mb-12 text-center">
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl mb-4">
ObjectQL Blog
</h1>
<p className="text-lg text-muted-foreground">
Updates, tutorials, and insights about the standard protocol for AI software generation
</p>
</div>

<div className="space-y-8">
{posts
.sort((a, b) => {
const dateA = new Date(a.data.date || 0);
const dateB = new Date(b.data.date || 0);
return dateB.getTime() - dateA.getTime();
})
.map((post) => (
<article
key={post.url}
className="group rounded-xl border bg-card p-6 transition-all hover:shadow-md"
>
<Link href={post.url} className="block">
<div className="mb-2 flex items-center gap-4 text-sm text-muted-foreground">
{post.data.date && (
<time dateTime={post.data.date}>
{new Date(post.data.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
)}
{post.data.authors && (
<span>by {post.data.authors.join(', ')}</span>
)}
</div>

<h2 className="mb-2 text-2xl font-bold group-hover:text-primary transition-colors">
{post.data.title}
</h2>

{post.data.description && (
<p className="text-muted-foreground mb-4">
{post.data.description}
</p>
)}

{post.data.tags && post.data.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{post.data.tags.map((tag) => (
<span
key={tag}
className="inline-flex items-center rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
>
{tag}
</span>
))}
</div>
)}
</Link>
</article>
))}
</div>

{posts.length === 0 && (
<div className="text-center py-12">
<p className="text-muted-foreground">No blog posts yet. Check back soon!</p>
</div>
)}
</div>
);
}

// Blog post component
function BlogPost({ page }: { page: ReturnType<typeof blogSource.getPage> }) {
if (!page) return null;

const MDX = page.data.body;

return (
<article className="mx-auto max-w-4xl">
<Link
href="/blog"
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground mb-8 transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2"
>
<polyline points="15 18 9 12 15 6" />
</svg>
Back to Blog
</Link>

<header className="mb-8">
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl mb-4">
{page.data.title}
</h1>

{page.data.description && (
<p className="text-xl text-muted-foreground mb-4">
{page.data.description}
</p>
)}

<div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground border-t pt-4">
{page.data.date && (
<time dateTime={page.data.date}>
{new Date(page.data.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
)}
{page.data.authors && (
<span>by {page.data.authors.join(', ')}</span>
)}
</div>

{page.data.tags && page.data.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-4">
{page.data.tags.map((tag) => (
<span
key={tag}
className="inline-flex items-center rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
>
{tag}
</span>
))}
</div>
)}
</header>

<div className="prose prose-slate dark:prose-invert max-w-none">
<MDX components={{ ...defaultMdxComponents }} />
</div>

<footer className="mt-12 pt-8 border-t">
<Link
href="/blog"
className="inline-flex items-center text-sm text-primary hover:underline"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2"
>
<polyline points="15 18 9 12 15 6" />
</svg>
Back to Blog
</Link>
</footer>
</article>
);
}

export default async function BlogPage({
params,
}: {
params: { slug?: string[] };
}) {
// If no slug, show blog listing
if (!params.slug || params.slug.length === 0) {
return (
<HomeLayout {...baseOptions}>
<main className="container py-12 md:py-24">
<BlogListing />
</main>
</HomeLayout>
);
}

// Otherwise, show individual blog post
const page = blogSource.getPage(params.slug);
if (!page) notFound();

return (
<HomeLayout {...baseOptions}>
<main className="container py-12 md:py-24">
<BlogPost page={page} />
</main>
</HomeLayout>
);
}

export async function generateStaticParams() {
return blogSource.generateParams();
}

export function generateMetadata({ params }: { params: { slug?: string[] } }): Metadata {
// Default metadata for blog listing
if (!params.slug || params.slug.length === 0) {
return {
title: 'Blog',
description: 'Updates, tutorials, and insights about the standard protocol for AI software generation',
};
}

// Metadata for individual blog post
const page = blogSource.getPage(params.slug);
if (!page) notFound();

return {
title: page.data.title,
description: page.data.description,
};
}
5 changes: 5 additions & 0 deletions apps/site/app/layout.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export const baseOptions: Omit<DocsLayoutProps, 'tree'> = {
url: '/docs',
active: 'nested-url',
},
{
text: 'Blog',
url: '/blog',
active: 'nested-url',
},
],
githubUrl: 'https://github.com/objectstack-ai/objectql',
// Enable sidebar search
Expand Down
82 changes: 82 additions & 0 deletions apps/site/content/blog/protocol-driven-architecture.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Understanding ObjectQL's Protocol-Driven Architecture
description: A deep dive into how ObjectQL separates intent from implementation through its protocol-driven design.
date: 2026-01-19
authors:
- ObjectQL Team
tags:
- architecture
- technical
- protocol
---

# Understanding ObjectQL's Protocol-Driven Architecture

One of ObjectQL's core principles is being **Protocol-Driven, Not Code-Driven**. But what does this mean in practice?

## The Problem with Traditional ORMs

Traditional ORMs mix two concerns:

1. **What** you want (the business logic)
2. **How** to achieve it (the implementation details)

This creates several issues:

- Tight coupling to specific databases
- Difficulty in testing and mocking
- Hard to optimize queries
- Challenging for AI to generate correctly

## The ObjectQL Approach

ObjectQL decouples these concerns through a three-layer architecture:

### 1. The Intent Layer (YAML Schema)

```yaml
name: project
fields:
name:
type: text
required: true
status:
type: select
options: [planning, active, completed]
```

This layer is **database-agnostic**. It describes what you want, not how to achieve it.

### 2. The Compilation Layer

The ObjectQL engine acts as a **compiler**, not a runtime wrapper:

- Validates schema integrity
- Injects permission checks
- Optimizes query patterns
- Generates type definitions

### 3. The Driver Layer

Drivers translate the compiled AST into database-specific operations:

- `@objectql/driver-sql` → PostgreSQL, MySQL
- `@objectql/driver-mongo` → MongoDB
- `@objectql/driver-sdk` → Remote HTTP APIs

## Benefits for AI Code Generation

This architecture makes ObjectQL ideal for AI-driven development:

✅ **Hallucination-Free**: YAML schemas are validated before compilation
✅ **Type-Safe**: All operations are strongly typed
✅ **Testable**: Mock drivers for testing without a real database
✅ **Portable**: Change databases without rewriting business logic

## Learn More

Want to dive deeper? Check out:

- [Architecture Specification](/docs/reference/spec/architecture)
- [Core Concepts](/docs/modeling/concepts)
- [Driver Development Guide](/docs/reference/api/drivers)
40 changes: 40 additions & 0 deletions apps/site/content/blog/welcome.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Welcome to ObjectQL Blog
description: Introducing the ObjectQL blog - your source for updates, tutorials, and insights about the standard protocol for AI software generation.
date: 2026-01-20
authors:
- ObjectQL Team
tags:
- announcement
- getting-started
---

# Welcome to the ObjectQL Blog

We're excited to launch the ObjectQL blog! This is your go-to resource for:

## What You'll Find Here

- **Product Updates**: Stay informed about new features, improvements, and releases
- **Technical Deep Dives**: Learn about the architecture and design decisions behind ObjectQL
- **Best Practices**: Discover how to get the most out of ObjectQL in your projects
- **Community Stories**: Hear from developers building with ObjectQL

## Why ObjectQL?

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:

- ✅ Type-safe database schemas
- ✅ Validated CRUD operations
- ✅ Permission enforcement
- ✅ API endpoint generation

## Getting Started

If you're new to ObjectQL, check out our [Getting Started Guide](/docs/getting-started) to begin your journey.

## Stay Connected

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.

Happy coding! 🚀
7 changes: 6 additions & 1 deletion apps/site/lib/source.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { docs, meta } from '@/.source';
import { docs, meta, blog } from '@/.source';
import { createMDXSource } from 'fumadocs-mdx';
import { loader } from 'fumadocs-core/source';

export const source = loader({
baseUrl: '/docs',
source: createMDXSource(docs, meta),
});

export const blogSource = loader({
baseUrl: '/blog',
source: createMDXSource(blog, []),
});
Loading
Loading