Skip to content

Commit abff826

Browse files
committed
Blog: Create blogs route & add blog for ts-rewrite
1 parent f609d92 commit abff826

File tree

9 files changed

+463
-5
lines changed

9 files changed

+463
-5
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,13 @@ For comprehensive guides, API reference, and examples, visit our documentation:
100100

101101
**📖 [https://sequelize-guard.js.org](https://sequelize-guard.js.org)**
102102

103-
**📖 (Fallback: [https://sequelize-guard.vercel.app](https://sequelize-guard.vercel.app))**
104-
105103
### Key Topics
106104

107105
- **[Getting Started](https://sequelize-guard.js.org/getting-started)** - Installation and basic setup
108106
- **[API Reference](https://sequelize-guard.js.org/api)** - Complete API documentation
109107
- **[TypeScript Support](https://sequelize-guard.js.org/typescript)** - Using with TypeScript
110108
- **[Examples](https://sequelize-guard.js.org/examples)** - Real-world usage examples
109+
- **[Blog](https://sequelize-guard.js.org/blogs)** - Articles and insights about the project
111110

112111
### Migration
113112

@@ -223,7 +222,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
223222

224223
Need help? Here's how to get support:
225224

226-
- 📖 **Documentation:** [https://sequelize-guard.js.org](https://sequelize-guard.js.org) (or [https://sequelize-guard.vercel.app](https://sequelize-guard.vercel.app))
225+
- 📖 **Documentation:** [https://sequelize-guard.js.org](https://sequelize-guard.js.org) (fallback: [https://sequelize-guard.vercel.app](https://sequelize-guard.vercel.app))
227226
- 🐛 **Bug Reports:** [GitHub Issues](https://github.com/lotivo/sequelize-guard/issues)
228227
- 💬 **Questions:** [GitHub Discussions](https://github.com/lotivo/sequelize-guard/discussions)
229228

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
---
2+
title: "5 Years Later: sequelize-guard Goes Full TypeScript (with AI as Co-Pilot)"
3+
description: "The journey of rewriting a 5-year-old JavaScript authorization library in TypeScript, and how AI helped modernize an open-source project while preserving its battle-tested architecture."
4+
date: 2024-12-11
5+
author: Pankaj Vaghela
6+
tags: ["typescript", "open-source", "system-design", "ai", "sequelize"]
7+
---
8+
9+
# 5 Years Later: sequelize-guard Goes Full TypeScript (with AI as Co-Pilot)
10+
11+
Back in 2019, I open-sourced [sequelize-guard](https://github.com/lotivo/sequelize-guard)—a lightweight authorization library that brings Laravel-style permissions to Sequelize.js in Node.js. It was born out of a real need: managing role-based access control without drowning in complexity.
12+
13+
Today, I'm proud to ship **v6**: fully rewritten in TypeScript with a [modern documentation site](https://sequelize-guard.js.org)!
14+
15+
This post is about the journey, the lessons learned about system design, and how AI became an invaluable co-pilot in modernizing open-source software.
16+
17+
## The Genesis: Why sequelize-guard Exists
18+
19+
Authorization is one of those problems that seems simple until you actually implement it. You need roles, permissions, caching, audit trails, and a clean API that doesn't make developers want to tear their hair out.
20+
21+
I wanted something that felt as intuitive as Laravel's authorization system but worked seamlessly with Sequelize in Node.js. The result was sequelize-guard:
22+
23+
```typescript
24+
await user.can('edit:post'); // Simple, readable, powerful
25+
```
26+
27+
Clean separation of roles, permissions, and caching. Event-driven hooks for auditing. A fluent API that reads like natural language.
28+
29+
## System Design That Stands the Test of Time
30+
31+
Here's the thing about good architecture: it ages well.
32+
33+
The original JavaScript codebase was built with longevity in mind. Even after 5 years, multiple Sequelize major versions (v5 → v6), and the entire JavaScript ecosystem evolving around it, **the core logic ported to TypeScript almost unchanged**.
34+
35+
### What Made It Last?
36+
37+
**1. Strong Abstractions**
38+
39+
The library was built around clear concepts:
40+
- **Roles** as containers for permissions
41+
- **Permissions** as action-resource pairs
42+
- **Caching layer** as a separate concern
43+
- **Event system** for extensibility
44+
45+
These abstractions didn't leak. They didn't depend on Sequelize internals more than necessary. They just... worked.
46+
47+
**2. Fluent, Readable API**
48+
49+
```typescript
50+
// This API design from 2019 still feels modern in 2024
51+
user.can('edit:post')
52+
user.isA('admin')
53+
user.isAnyOf(['admin', 'editor'])
54+
```
55+
56+
When your API reads like natural language, it doesn't age. It doesn't need "modernizing" because it was already intuitive.
57+
58+
**3. Event-Driven Architecture**
59+
60+
```typescript
61+
guard.on('permission:checked', (event) => {
62+
// Audit logging, analytics, whatever you need
63+
});
64+
```
65+
66+
By emitting events at key points, the library became extensible without modification. Users could hook into the authorization flow without touching the core.
67+
68+
**The Lesson**: Invest in strong abstractions early. They make migrations painless and keep your code relevant for years.
69+
70+
## Maintainability = Survival in Open Source
71+
72+
JavaScript evolves at breakneck speed. What was cutting-edge in 2019 became legacy by 2024.
73+
74+
The v6 rewrite wasn't just about adding types—it was about **ensuring the project's survival**:
75+
76+
### The Modernization Checklist
77+
78+
-**TypeScript with strict generics** for models & permissions
79+
-**Dual ESM + CommonJS support** for maximum compatibility
80+
-**Vitest** for testing (goodbye Jest config hell)
81+
-**Vite** for blazing-fast builds
82+
-**Typedoc** for auto-generated API documentation
83+
-**99% backward compatible** (breaking changes only where absolutely necessary)
84+
85+
### Why This Matters
86+
87+
Open source isn't "set it and forget it." Projects that don't evolve become abandonware. Users move on. Contributors disappear. The ecosystem leaves you behind.
88+
89+
By modernizing the toolchain, I ensured that:
90+
- New contributors can jump in with familiar tools
91+
- The library works with modern Node.js projects (ESM)
92+
- TypeScript users get first-class support
93+
- The documentation stays fresh and accurate
94+
95+
**The Lesson**: Evolve or fade. Maintenance isn't glamorous, but it's what keeps projects alive.
96+
97+
## LLMs as Open-Source Superchargers
98+
99+
Here's where it gets interesting.
100+
101+
I approached this massive rewrite by treating Claude and GPT-4 as my **co-pilots**. I gave them the old JavaScript codebase and a clear task:
102+
103+
> "Migrate to strict TypeScript. Keep 100% behavior. Add generics. Update tests."
104+
105+
### What AI Crushed
106+
107+
**The Grunt Work:**
108+
- Type definitions across hundreds of lines of code
109+
- Converting modules from CommonJS to ESM
110+
- Migrating test suites from Jest to Vitest
111+
- Generating boilerplate for generic types
112+
- Updating configuration files for modern tooling
113+
114+
This is the stuff that's tedious, error-prone, and soul-crushing. AI handled it with remarkable accuracy.
115+
116+
### What I Still Owned
117+
118+
**The High-Value Work:**
119+
- Core architecture decisions
120+
- Handling complex edge cases specific to Sequelize
121+
- Designing the generic type system for models
122+
- Setting up CI/CD pipelines
123+
- Building and deploying the documentation site
124+
- Reviewing and validating AI-generated code
125+
126+
### The Truth About AI-Assisted Development
127+
128+
**AI didn't replace me.** It removed the noise so I could focus purely on quality, correctness, and architecture.
129+
130+
Think of it like this:
131+
- **Before AI**: 70% grunt work, 30% creative problem-solving
132+
- **With AI**: 20% grunt work, 80% creative problem-solving
133+
134+
The work became more intellectually engaging. I spent less time typing boilerplate and more time thinking about design.
135+
136+
**The Lesson**: AI is a force multiplier for maintainers. It doesn't eliminate the need for expertise—it amplifies it.
137+
138+
## The Result: Battle-Tested, Type-Safe, Future-Proof
139+
140+
sequelize-guard v6 is everything the original was, but better:
141+
142+
```typescript
143+
// Now with compile-time type checking!
144+
await user.can('delete:user'); // TypeScript knows this is valid
145+
146+
// Generic support for your models
147+
const guard = new SequelizeGuard<MyUserModel>(sequelize);
148+
149+
// Full IntelliSense support
150+
user.assignRole('admin'); // Autocomplete works perfectly
151+
```
152+
153+
### What Users Get
154+
155+
- **Type Safety**: Catch permission errors at compile time
156+
- **Better DX**: IntelliSense, autocomplete, inline documentation
157+
- **Modern Tooling**: Works with ESM, Vite, and modern Node.js
158+
- **Same Great API**: If you used v5, v6 feels familiar
159+
- **Comprehensive Docs**: [sequelize-guard.js.org](https://sequelize-guard.js.org)
160+
161+
## A Message to Open-Source Maintainers
162+
163+
If you have a dusty open-source gem sitting in your GitHub repos, **it's never too late to modernize**.
164+
165+
The tools are better than ever:
166+
- TypeScript makes refactoring safer
167+
- AI can help carry the grunt work
168+
- Modern build tools (Vite, Vitest) are faster and simpler
169+
- Documentation generators (Typedoc, VitePress) are incredible
170+
171+
The community is here. People still care about well-designed libraries that solve real problems.
172+
173+
Don't let your project fade because the tooling aged. Give it new life.
174+
175+
## Try It Out
176+
177+
If you use Sequelize + Node.js, I'd love for you to try sequelize-guard v6:
178+
179+
- 🌟 [Star on GitHub](https://github.com/lotivo/sequelize-guard)
180+
- 📖 [Read the Docs](https://sequelize-guard.js.org)
181+
- 📦 [Install from NPM](https://www.npmjs.com/package/sequelize-guard)
182+
183+
I'd especially love to hear your thoughts on the new type system and developer experience.
184+
185+
---
186+
187+
**About the Author**: Pankaj Vaghela is a software engineer passionate about system design, open source, and building tools that make developers' lives easier. With over a decade of experience in full-stack development, he specializes in creating scalable solutions and contributing to the open-source community. Learn more at [pankajvaghela.in](https://pankajvaghela.in/) or connect on [GitHub](https://github.com/pankajvaghela).

apps/sequelize-guard-docs/source.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
frontmatterSchema,
55
metaSchema,
66
} from 'fumadocs-mdx/config';
7+
import { z } from 'zod';
78

89
// You can customise Zod schemas for frontmatter and `meta.json` here
910
// see https://fumadocs.dev/docs/mdx/collections
@@ -20,6 +21,17 @@ export const docs = defineDocs({
2021
},
2122
});
2223

24+
export const blogs = defineDocs({
25+
dir: 'content/blogs',
26+
docs: {
27+
schema: frontmatterSchema.extend({
28+
author: z.string().optional(),
29+
date: z.string().or(z.date()).optional(),
30+
tags: z.array(z.string()).optional(),
31+
}),
32+
},
33+
});
34+
2335
export default defineConfig({
2436
mdxOptions: {
2537
// MDX options
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { blogSource } from '@/lib/source';
2+
import type { Metadata } from 'next';
3+
import { notFound } from 'next/navigation';
4+
import { BlogIndexPage } from '../components/BlogIndexPage';
5+
import { BlogPostPage } from '../components/BlogPostPage';
6+
7+
export default async function Page(props: {
8+
params: Promise<{ slug?: string[] }>;
9+
}) {
10+
const params = await props.params;
11+
12+
// If no slug, show blog index
13+
if (!params.slug || params.slug.length === 0) {
14+
return <BlogIndexPage />;
15+
}
16+
17+
// Otherwise, show individual blog post
18+
return <BlogPostPage slug={params.slug} />;
19+
}
20+
21+
export async function generateStaticParams() {
22+
return blogSource.generateParams();
23+
}
24+
25+
export async function generateMetadata(props: {
26+
params: Promise<{ slug?: string[] }>;
27+
}): Promise<Metadata> {
28+
const params = await props.params;
29+
30+
// If no slug, return blog index metadata
31+
if (!params.slug || params.slug.length === 0) {
32+
return {
33+
title: 'Blog - Sequelize Guard',
34+
description: 'Insights, updates, and stories about sequelize-guard',
35+
};
36+
}
37+
38+
const page = blogSource.getPage(params.slug);
39+
if (!page) notFound();
40+
41+
return {
42+
title: page.data.title,
43+
description: page.data.description,
44+
};
45+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { blogSource } from '@/lib/source';
2+
import Link from 'next/link';
3+
import { Calendar, User, Tag } from 'lucide-react';
4+
5+
export function BlogIndexPage() {
6+
const allBlogs = [...blogSource.getPages()].sort((a, b) => {
7+
const dateA = new Date(a.data.date || 0).getTime();
8+
const dateB = new Date(b.data.date || 0).getTime();
9+
return dateB - dateA;
10+
});
11+
12+
return (
13+
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
14+
<div className="max-w-5xl mx-auto px-6 py-16">
15+
<div className="mb-12">
16+
<Link
17+
href="/"
18+
className="text-emerald-600 hover:text-emerald-700 font-medium mb-4 inline-block"
19+
>
20+
← Back to Home
21+
</Link>
22+
<h1 className="text-5xl font-bold text-slate-900 mb-4">Blog</h1>
23+
<p className="text-xl text-slate-600">
24+
Insights, updates, and stories about sequelize-guard
25+
</p>
26+
</div>
27+
28+
<div className="space-y-8">
29+
{allBlogs.map((blog) => (
30+
<article
31+
key={blog.url}
32+
className="bg-white rounded-xl shadow-md hover:shadow-lg transition-shadow p-8 border border-slate-200"
33+
>
34+
<Link href={blog.url} className="group">
35+
<h2 className="text-3xl font-bold text-slate-900 mb-3 group-hover:text-emerald-600 transition">
36+
{blog.data.title}
37+
</h2>
38+
</Link>
39+
40+
{blog.data.description && (
41+
<p className="text-lg text-slate-600 mb-4">
42+
{blog.data.description}
43+
</p>
44+
)}
45+
46+
<div className="flex flex-wrap gap-4 text-sm text-slate-500 mb-4">
47+
{blog.data.date && (
48+
<div className="flex items-center gap-2">
49+
<Calendar className="w-4 h-4" />
50+
<time>
51+
{new Date(blog.data.date).toLocaleDateString('en-US', {
52+
year: 'numeric',
53+
month: 'long',
54+
day: 'numeric',
55+
})}
56+
</time>
57+
</div>
58+
)}
59+
60+
{blog.data.author && (
61+
<div className="flex items-center gap-2">
62+
<User className="w-4 h-4" />
63+
<span>{blog.data.author}</span>
64+
</div>
65+
)}
66+
</div>
67+
68+
{blog.data.tags && blog.data.tags.length > 0 && (
69+
<div className="flex items-center gap-2 flex-wrap">
70+
<Tag className="w-4 h-4 text-slate-400" />
71+
{blog.data.tags.map((tag: string) => (
72+
<span
73+
key={tag}
74+
className="px-3 py-1 bg-emerald-50 text-emerald-700 rounded-full text-sm font-medium"
75+
>
76+
{tag}
77+
</span>
78+
))}
79+
</div>
80+
)}
81+
82+
<Link
83+
href={blog.url}
84+
className="inline-flex items-center gap-2 mt-6 text-emerald-600 hover:text-emerald-700 font-semibold"
85+
>
86+
Read more →
87+
</Link>
88+
</article>
89+
))}
90+
</div>
91+
92+
{allBlogs.length === 0 && (
93+
<div className="text-center py-16">
94+
<p className="text-xl text-slate-500">No blog posts yet. Check back soon!</p>
95+
</div>
96+
)}
97+
</div>
98+
</div>
99+
);
100+
}

0 commit comments

Comments
 (0)