diff --git a/README.md b/README.md index 6a48a29..3d8ded2 100644 --- a/README.md +++ b/README.md @@ -100,14 +100,13 @@ For comprehensive guides, API reference, and examples, visit our documentation: **πŸ“– [https://sequelize-guard.js.org](https://sequelize-guard.js.org)** -**πŸ“– (Fallback: [https://sequelize-guard.vercel.app](https://sequelize-guard.vercel.app))** - ### Key Topics - **[Getting Started](https://sequelize-guard.js.org/getting-started)** - Installation and basic setup - **[API Reference](https://sequelize-guard.js.org/api)** - Complete API documentation - **[TypeScript Support](https://sequelize-guard.js.org/typescript)** - Using with TypeScript - **[Examples](https://sequelize-guard.js.org/examples)** - Real-world usage examples +- **[Blog](https://sequelize-guard.js.org/blogs)** - Articles and insights about the project ### Migration @@ -223,7 +222,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file Need help? Here's how to get support: -- πŸ“– **Documentation:** [https://sequelize-guard.js.org](https://sequelize-guard.js.org) (or [https://sequelize-guard.vercel.app](https://sequelize-guard.vercel.app)) +- πŸ“– **Documentation:** [https://sequelize-guard.js.org](https://sequelize-guard.js.org) (fallback: [https://sequelize-guard.vercel.app](https://sequelize-guard.vercel.app)) - πŸ› **Bug Reports:** [GitHub Issues](https://github.com/lotivo/sequelize-guard/issues) - πŸ’¬ **Questions:** [GitHub Discussions](https://github.com/lotivo/sequelize-guard/discussions) diff --git a/apps/sequelize-guard-docs/content/blogs/2024/12/five-years-typescript-rewrite-with-ai.mdx b/apps/sequelize-guard-docs/content/blogs/2024/12/five-years-typescript-rewrite-with-ai.mdx new file mode 100644 index 0000000..32c5473 --- /dev/null +++ b/apps/sequelize-guard-docs/content/blogs/2024/12/five-years-typescript-rewrite-with-ai.mdx @@ -0,0 +1,187 @@ +--- +title: "5 Years Later: sequelize-guard Goes Full TypeScript (with AI as Co-Pilot)" +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." +date: 2024-12-11 +author: Pankaj Vaghela +tags: ["typescript", "open-source", "system-design", "ai", "sequelize"] +--- + +# 5 Years Later: sequelize-guard Goes Full TypeScript (with AI as Co-Pilot) + +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. + +Today, I'm proud to ship **v6**: fully rewritten in TypeScript with a [modern documentation site](https://sequelize-guard.js.org)! + +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. + +## The Genesis: Why sequelize-guard Exists + +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. + +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: + +```typescript +await user.can('edit:post'); // Simple, readable, powerful +``` + +Clean separation of roles, permissions, and caching. Event-driven hooks for auditing. A fluent API that reads like natural language. + +## System Design That Stands the Test of Time + +Here's the thing about good architecture: it ages well. + +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**. + +### What Made It Last? + +**1. Strong Abstractions** + +The library was built around clear concepts: +- **Roles** as containers for permissions +- **Permissions** as action-resource pairs +- **Caching layer** as a separate concern +- **Event system** for extensibility + +These abstractions didn't leak. They didn't depend on Sequelize internals more than necessary. They just... worked. + +**2. Fluent, Readable API** + +```typescript +// This API design from 2019 still feels modern in 2024 +user.can('edit:post') +user.isA('admin') +user.isAnyOf(['admin', 'editor']) +``` + +When your API reads like natural language, it doesn't age. It doesn't need "modernizing" because it was already intuitive. + +**3. Event-Driven Architecture** + +```typescript +guard.on('permission:checked', (event) => { + // Audit logging, analytics, whatever you need +}); +``` + +By emitting events at key points, the library became extensible without modification. Users could hook into the authorization flow without touching the core. + +**The Lesson**: Invest in strong abstractions early. They make migrations painless and keep your code relevant for years. + +## Maintainability = Survival in Open Source + +JavaScript evolves at breakneck speed. What was cutting-edge in 2019 became legacy by 2024. + +The v6 rewrite wasn't just about adding typesβ€”it was about **ensuring the project's survival**: + +### The Modernization Checklist + +- βœ… **TypeScript with strict generics** for models & permissions +- βœ… **Dual ESM + CommonJS support** for maximum compatibility +- βœ… **Vitest** for testing (goodbye Jest config hell) +- βœ… **Vite** for blazing-fast builds +- βœ… **Typedoc** for auto-generated API documentation +- βœ… **99% backward compatible** (breaking changes only where absolutely necessary) + +### Why This Matters + +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. + +By modernizing the toolchain, I ensured that: +- New contributors can jump in with familiar tools +- The library works with modern Node.js projects (ESM) +- TypeScript users get first-class support +- The documentation stays fresh and accurate + +**The Lesson**: Evolve or fade. Maintenance isn't glamorous, but it's what keeps projects alive. + +## LLMs as Open-Source Superchargers + +Here's where it gets interesting. + +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: + +> "Migrate to strict TypeScript. Keep 100% behavior. Add generics. Update tests." + +### What AI Crushed + +**The Grunt Work:** +- Type definitions across hundreds of lines of code +- Converting modules from CommonJS to ESM +- Migrating test suites from Jest to Vitest +- Generating boilerplate for generic types +- Updating configuration files for modern tooling + +This is the stuff that's tedious, error-prone, and soul-crushing. AI handled it with remarkable accuracy. + +### What I Still Owned + +**The High-Value Work:** +- Core architecture decisions +- Handling complex edge cases specific to Sequelize +- Designing the generic type system for models +- Setting up CI/CD pipelines +- Building and deploying the documentation site +- Reviewing and validating AI-generated code + +### The Truth About AI-Assisted Development + +**AI didn't replace me.** It removed the noise so I could focus purely on quality, correctness, and architecture. + +Think of it like this: +- **Before AI**: 70% grunt work, 30% creative problem-solving +- **With AI**: 20% grunt work, 80% creative problem-solving + +The work became more intellectually engaging. I spent less time typing boilerplate and more time thinking about design. + +**The Lesson**: AI is a force multiplier for maintainers. It doesn't eliminate the need for expertiseβ€”it amplifies it. + +## The Result: Battle-Tested, Type-Safe, Future-Proof + +sequelize-guard v6 is everything the original was, but better: + +```typescript +// Now with compile-time type checking! +await user.can('delete:user'); // TypeScript knows this is valid + +// Generic support for your models +const guard = new SequelizeGuard(sequelize); + +// Full IntelliSense support +user.assignRole('admin'); // Autocomplete works perfectly +``` + +### What Users Get + +- **Type Safety**: Catch permission errors at compile time +- **Better DX**: IntelliSense, autocomplete, inline documentation +- **Modern Tooling**: Works with ESM, Vite, and modern Node.js +- **Same Great API**: If you used v5, v6 feels familiar +- **Comprehensive Docs**: [sequelize-guard.js.org](https://sequelize-guard.js.org) + +## A Message to Open-Source Maintainers + +If you have a dusty open-source gem sitting in your GitHub repos, **it's never too late to modernize**. + +The tools are better than ever: +- TypeScript makes refactoring safer +- AI can help carry the grunt work +- Modern build tools (Vite, Vitest) are faster and simpler +- Documentation generators (Typedoc, VitePress) are incredible + +The community is here. People still care about well-designed libraries that solve real problems. + +Don't let your project fade because the tooling aged. Give it new life. + +## Try It Out + +If you use Sequelize + Node.js, I'd love for you to try sequelize-guard v6: + +- 🌟 [Star on GitHub](https://github.com/lotivo/sequelize-guard) +- πŸ“– [Read the Docs](https://sequelize-guard.js.org) +- πŸ“¦ [Install from NPM](https://www.npmjs.com/package/sequelize-guard) + +I'd especially love to hear your thoughts on the new type system and developer experience. + +--- + +**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). diff --git a/apps/sequelize-guard-docs/source.config.ts b/apps/sequelize-guard-docs/source.config.ts index b5ffa0a..84cb3ae 100644 --- a/apps/sequelize-guard-docs/source.config.ts +++ b/apps/sequelize-guard-docs/source.config.ts @@ -4,6 +4,7 @@ import { frontmatterSchema, metaSchema, } from 'fumadocs-mdx/config'; +import { z } from 'zod'; // You can customise Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.dev/docs/mdx/collections @@ -20,6 +21,17 @@ export const docs = defineDocs({ }, }); +export const blogs = defineDocs({ + dir: 'content/blogs', + docs: { + schema: frontmatterSchema.extend({ + author: z.string().optional(), + date: z.string().or(z.date()).optional(), + tags: z.array(z.string()).optional(), + }), + }, +}); + export default defineConfig({ mdxOptions: { // MDX options diff --git a/apps/sequelize-guard-docs/src/app/blogs/[[...slug]]/page.tsx b/apps/sequelize-guard-docs/src/app/blogs/[[...slug]]/page.tsx new file mode 100644 index 0000000..3187530 --- /dev/null +++ b/apps/sequelize-guard-docs/src/app/blogs/[[...slug]]/page.tsx @@ -0,0 +1,45 @@ +import { blogSource } from '@/lib/source'; +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import { BlogIndexPage } from '../components/BlogIndexPage'; +import { BlogPostPage } from '../components/BlogPostPage'; + +export default async function Page(props: { + params: Promise<{ slug?: string[] }>; +}) { + const params = await props.params; + + // If no slug, show blog index + if (!params.slug || params.slug.length === 0) { + return ; + } + + // Otherwise, show individual blog post + return ; +} + +export async function generateStaticParams() { + return blogSource.generateParams(); +} + +export async function generateMetadata(props: { + params: Promise<{ slug?: string[] }>; +}): Promise { + const params = await props.params; + + // If no slug, return blog index metadata + if (!params.slug || params.slug.length === 0) { + return { + title: 'Blog - Sequelize Guard', + description: 'Insights, updates, and stories about sequelize-guard', + }; + } + + const page = blogSource.getPage(params.slug); + if (!page) notFound(); + + return { + title: page.data.title, + description: page.data.description, + }; +} diff --git a/apps/sequelize-guard-docs/src/app/blogs/components/BlogIndexPage.tsx b/apps/sequelize-guard-docs/src/app/blogs/components/BlogIndexPage.tsx new file mode 100644 index 0000000..e3cb1de --- /dev/null +++ b/apps/sequelize-guard-docs/src/app/blogs/components/BlogIndexPage.tsx @@ -0,0 +1,100 @@ +import { blogSource } from '@/lib/source'; +import Link from 'next/link'; +import { Calendar, User, Tag } from 'lucide-react'; + +export function BlogIndexPage() { + const allBlogs = [...blogSource.getPages()].sort((a, b) => { + const dateA = new Date(a.data.date || 0).getTime(); + const dateB = new Date(b.data.date || 0).getTime(); + return dateB - dateA; + }); + + return ( +
+
+
+ + ← Back to Home + +

Blog

+

+ Insights, updates, and stories about sequelize-guard +

+
+ +
+ {allBlogs.map((blog) => ( +
+ +

+ {blog.data.title} +

+ + + {blog.data.description && ( +

+ {blog.data.description} +

+ )} + +
+ {blog.data.date && ( +
+ + +
+ )} + + {blog.data.author && ( +
+ + {blog.data.author} +
+ )} +
+ + {blog.data.tags && blog.data.tags.length > 0 && ( +
+ + {blog.data.tags.map((tag: string) => ( + + {tag} + + ))} +
+ )} + + + Read more β†’ + +
+ ))} +
+ + {allBlogs.length === 0 && ( +
+

No blog posts yet. Check back soon!

+
+ )} +
+
+ ); +} diff --git a/apps/sequelize-guard-docs/src/app/blogs/components/BlogPostPage.tsx b/apps/sequelize-guard-docs/src/app/blogs/components/BlogPostPage.tsx new file mode 100644 index 0000000..150fbf7 --- /dev/null +++ b/apps/sequelize-guard-docs/src/app/blogs/components/BlogPostPage.tsx @@ -0,0 +1,94 @@ +import { blogSource } from '@/lib/source'; +import { DocsBody } from 'fumadocs-ui/page'; +import { Calendar, User, Tag, ArrowLeft } from 'lucide-react'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; + +interface BlogPostPageProps { + slug: string[]; +} + +export function BlogPostPage({ slug }: BlogPostPageProps) { + const page = blogSource.getPage(slug); + if (!page) notFound(); + + const MDX = page.data.body; + + return ( +
+
+ + + Back to Blog + + +
+
+

+ {page.data.title} +

+ + {page.data.description && ( +

+ {page.data.description} +

+ )} + +
+ {page.data.date && ( +
+ + +
+ )} + + {page.data.author && ( +
+ + {page.data.author} +
+ )} +
+ + {page.data.tags && page.data.tags.length > 0 && ( +
+ + {page.data.tags.map((tag: string) => ( + + {tag} + + ))} +
+ )} +
+ + + + +
+ +
+ + + Back to all posts + +
+
+
+ ); +} diff --git a/apps/sequelize-guard-docs/src/app/blogs/layout.tsx b/apps/sequelize-guard-docs/src/app/blogs/layout.tsx new file mode 100644 index 0000000..0be77f7 --- /dev/null +++ b/apps/sequelize-guard-docs/src/app/blogs/layout.tsx @@ -0,0 +1,5 @@ +import type { ReactNode } from 'react'; + +export default function BlogLayout({ children }: { children: ReactNode }) { + return children; +} diff --git a/apps/sequelize-guard-docs/src/components/screen/home/HomePageScreen.tsx b/apps/sequelize-guard-docs/src/components/screen/home/HomePageScreen.tsx index 1bbc570..8b78ab9 100644 --- a/apps/sequelize-guard-docs/src/components/screen/home/HomePageScreen.tsx +++ b/apps/sequelize-guard-docs/src/components/screen/home/HomePageScreen.tsx @@ -33,6 +33,12 @@ export const HomePageScreen = () => { > Documentation + + Blog + { Documentation +
+ + Blog +

- Made with love by{' '} + Made with ❀️ for Open-Source by{' '} ) { const segments = [...page.slugs, 'image.png'];