# Fastfold

Zero-boilerplate backend for React apps with **Drizzle ORM** integration, auto-generated CRUD, and declarative security. **React Query powered** for smart caching and background sync. Perfect for rapid UI development with minimal code.

## Features

- 🚀 **Zero boilerplate** - Auto-generated CRUD operations from Drizzle schemas
- 🎯 **Simple API** - Just `useQuery` and `useMutation` (powered by React Query!)
- 📦 **Automatic caching** - Smart cache invalidation and background refetching
- 🗄️ **Drizzle ORM** - Full database power with relationships, migrations, and validation
- 🔒 **Declarative security** - Role-based permissions per table
- 🔧 **TypeScript** - Full type safety out of the box
- 🤖 **LLM-friendly** - Small, predictable API surface
- ⚡ **Production ready** - PostgreSQL, MySQL, SQLite support with foreign keys
- 🛠️ **React Query DevTools** - Optional debugging and cache inspection

## Quick Start

### 1. Install Fastfold

```bash
npm install @flavoai/fastfold @tanstack/react-query
```

**Note**: React Query is a peer dependency for the client hooks.

### 2. Define Your Schema with Drizzle (30 seconds)

```typescript
// schema.ts - Use Drizzle's powerful schema definition
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  role: text('role').default('user'),
});

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content'),
  published: integer('published', { mode: 'boolean' }).default(false),
  authorId: integer('author_id').references(() => users.id, { onDelete: 'cascade' }),
});

// Define relationships
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));
```

### 3. Create Your Backend (30 seconds)

```typescript
// server.ts - Fastfold adds CRUD + Security to your Drizzle schema
import Fastfold, { Security } from '@flavoai/fastfold';
import path from 'path';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
import * as schema from './schema';

const db = drizzle(new Database('./app.db'), { schema });

await Fastfold.quickStart({
  // 🔗 DRIZZLE INTEGRATION - Use your existing schema
  drizzle: { db, schema },
  
  // 🔒 ADD SECURITY - Just specify security per table
  tables: {
    users: {
      security: Security.authenticated(),
      operations: ['read', 'update'] // Granular CRUD control
    },
    posts: {
      security: Security.owner('authorId'),
      operations: ['create', 'read', 'update', 'delete']
    }
  },

  // 🧩 (Optional) Serve your frontend SPA from the same server
  // Supports single or multiple static mounts
  staticFrontend: [
    {
      directory: path.resolve(process.cwd(), 'dist'), // your Vite build output
      urlPath: '/',            // mount at root
      spaFallback: true,       // route non-API requests to index.html
      excludePaths: ['/api', '/docs', /^\/assets\//],
      indexFile: 'index.html',
      staticOptions: { index: false, maxAge: '1h', immutable: true }
    },
    // Example: admin panel mounted at /admin
    // {
    //   directory: path.resolve(process.cwd(), 'admin-dist'),
    //   urlPath: '/admin',
    //   spaFallback: true,
    //   excludePaths: ['/api', '/docs']
    // }
  ]
});

// That's it! Full CRUD API with relationships and security
```

### 4. Build and Serve your Frontend (optional)

If you want to serve your SPA from Fastfold, build it with Vite (or similar) into `dist/` (or your configured directory):

```bash
# From your frontend project
npm run build
# Ensure build outputs to ./dist relative to your server or set staticFrontend.directory accordingly
```

Then start Fastfold; your app will be served along with the API.

### 5. Minimal frontend you can copy-paste (CSP-friendly)

Create `dist/index.html` with:

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Fastfold Quickstart</title>
    <link rel="stylesheet" href="app.css" />
  </head>
  <body>
    <h1>Fastfold Quickstart</h1>
    <p>Backend API is mounted at <code>/api</code>. Click to seed sample data, then list posts.</p>
    <div>
      <button id="seed">Seed Sample Data</button>
      <button id="load">Load Posts</button>
    </div>
    <div id="out" style="margin-top: 1rem"></div>
    <script defer src="app.js"></script>
  </body>
  </html>
```

Create `dist/app.js` with:

```js
const out = document.getElementById('out');
const show = (html) => { out.innerHTML = html; };
document.getElementById('seed').onclick = async () => {
  const res = await fetch('/api/seed', { method: 'POST', headers: { 'content-type': 'application/json' } });
  const data = await res.json();
  show(`<div class="card">Seeded: <pre>${JSON.stringify(data, null, 2)}</pre></div>`);
};
document.getElementById('load').onclick = async () => {
  const res = await fetch('/api/posts?params=' + encodeURIComponent(JSON.stringify({ with: { author: true }, orderBy: { createdAt: 'desc' } })));
  const json = await res.json();
  const posts = json.data || [];
  show(posts.length ? posts.map(p => `<div class="card"><h3>${p.title}</h3><div>By: ${p.author?.name || 'Unknown'}</div><p>${p.content}</p></div>`).join('') : '<div class="card">No posts yet</div>');
};
```

Create `dist/app.css` with:

```css
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, 'Helvetica Neue', Arial, 'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji'; margin: 2rem; }
h1 { margin: 0 0 1rem; }
button { padding: .5rem .75rem; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: .5rem 0; }
code { background: #f6f8fa; padding: .2rem .4rem; border-radius: 4px; }
```

Now start the server and visit `http://localhost:3001`.

### 6. Use in React (30 seconds)

```tsx
// App.tsx - Wrap your app with FastfoldProvider
import { FastfoldProvider, configureFastfold } from '@flavoai/fastfold/client';

// Configure your API endpoint
configureFastfold({ baseUrl: 'http://localhost:3001/api' });

function App() {
  return (
    <FastfoldProvider>
      <BlogList />
    </FastfoldProvider>
  );
}

// BlogList.tsx - Use Fastfold hooks (powered by React Query!)
import { useQuery, useCreate } from '@flavoai/fastfold/client';

function BlogList() {
  // 🎯 SIMPLE QUERY - Fetch data with relationships (with React Query caching!)
  const { data: posts, isLoading, error } = useQuery('posts', {
    where: { published: true },
    orderBy: { createdAt: 'desc' },
    with: { author: true } // Include relationships!
  });

  // 🚀 SIMPLE MUTATION - Create data with automatic cache invalidation
  const createPost = useCreate('posts');

  const handleCreate = () => {
    createPost.mutate({ 
      title: 'Hello World', 
      content: 'My first post!',
      published: true
    });
    // Cache automatically updates! No manual refetch needed 🎉
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <button 
        onClick={handleCreate}
        disabled={createPost.isPending}
      >
        {createPost.isPending ? 'Creating...' : 'Create Post'}
      </button>
      
      {posts?.map(post => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>By: {post.author?.name}</p>
          <p>{post.content}</p>
        </div>
      ))}
    </div>
  );
}
```

## 🔒 Security Made Simple

Fastfold comes with built-in, declarative security that's both powerful and easy to use:

```typescript
// 🔓 LEVEL 1: ZERO-SECURITY (0 lines of security code)
// Perfect for: public data, prototypes
blog: {
  schema: { title: 'string', content: 'string' },
  security: Security.public() // Everyone can access ✨
},

// 🔐 LEVEL 2: ONE-LINER SECURITY (1 line of security code)
// Perfect for: most common use cases
adminLogs: {
  schema: { action: 'string', timestamp: 'number' },
  security: Security.admin() // Only admins ✨
},

userProfiles: {
  schema: { userId: 'string', name: 'string', bio: 'string' },
  security: Security.owner('userId') // Users own their data ✨
},

// ⚙️ LEVEL 3: CUSTOM SECURITY (as complex as you need)
// Perfect for: complex business rules
projects: {
  schema: { name: 'string', teamId: 'string' },
  security: Security.custom((ctx) => {
    return ctx.user?.teamId === ctx.data?.teamId;
  })
}
```

## API Reference

### Server API

```typescript
import Fastfold, { Security } from '@flavoai/fastfold';

// Quick start with Drizzle (recommended)
const adapter = await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: {
    users: {
      security: Security.authenticated(),
      operations: ['read', 'update']
    },
    posts: {
      security: Security.owner('authorId'),
      operations: ['create', 'read', 'update', 'delete']
    }
  }
});

// Advanced configuration with custom endpoints
const adapter = await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: { /* your table configs */ },
  auth: {
    secret: 'your-jwt-secret',
    expiresIn: '24h'
  },
  endpoints: (app) => {
    // Add custom endpoints
    app.get('/api/custom', (req, res) => {
      res.json({ message: 'Custom endpoint' });
    });
  },
  hooks: {
    onServerStart: async (server) => {
      console.log('Server started on port', server.port);
    }
  }
});
```

### Client API

```typescript
import { 
  useQuery,      // Fetch multiple records
  useQueryOne,   // Fetch single record
  useCreate,     // Create records
  useUpdate,     // Update records
  useDelete,     // Delete records
  configureFastfold,
  setAuthToken
} from '@flavoai/fastfold/client';

// Configure client
configureFastfold({ baseUrl: 'http://localhost:3001/api' });

// Set authentication
setAuthToken('your-jwt-token');
```

#### ⚙️ **Client Configuration**

```tsx
import { configureFastfold, setAuthToken } from '@flavoai/fastfold/client';

// Basic configuration
configureFastfold({
  baseUrl: 'https://api.myapp.com/api', // Your API base URL
  headers: {
    'X-App-Version': '1.0.0',
    'X-Custom-Header': 'value'
  }
});

// Authentication setup
function LoginComponent() {
  const handleLogin = async (email, password) => {
    // Your login logic here
    const response = await fetch('/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    });
    
    const { token } = await response.json();
    
    // Set auth token for all future requests
    setAuthToken(token);
    
    // Now all useQuery, useCreate, etc. will include this token
  };

  const handleLogout = () => {
    // Clear the auth token
    setAuthToken('');
    // or
    configureFastfold({ 
      headers: {} // Clear all headers
    });
  };

  return (
    <div>
      <button onClick={handleLogin}>Login</button>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
}
```

### Complete Client Examples

#### 📋 **Querying Data**

```tsx
import { useQuery, useQueryOne } from '@flavoai/fastfold/client';

function PostsList() {
  // Fetch multiple posts with relationships
  const { data: posts, isLoading, error } = useQuery('posts', {
    where: { published: true },
    orderBy: { createdAt: 'desc' },
    with: { author: true }, // Include author data
    limit: 10
  });

  // Fetch single post by ID
  const { data: post } = useQueryOne('posts', '123', {
    with: { author: true, comments: true }
  });

  if (isLoading) return <div>Loading posts...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {posts?.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>By: {post.author?.name}</p>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}
```

#### ✏️ **Creating Data**

```tsx
import { useCreate } from '@flavoai/fastfold/client';

function CreatePostForm() {
  const createPost = useCreate('posts', {
    onSuccess: (newPost) => {
      console.log('Post created:', newPost);
      // Cache automatically updated - no refetch needed!
    },
    onError: (error) => {
      console.error('Failed to create post:', error);
    }
  });

  const handleSubmit = async (formData) => {
    try {
      const newPost = await createPost.mutate({
        title: formData.title,
        content: formData.content,
        published: true,
        authorId: currentUser.id
      });
      
      console.log('Created post:', newPost);
    } catch (error) {
      console.error('Creation failed:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button 
        type="submit" 
        disabled={createPost.isLoading}
      >
        {createPost.isLoading ? 'Creating...' : 'Create Post'}
      </button>
    </form>
  );
}
```

#### 🔄 **Updating Data**

```tsx
import { useUpdate } from '@flavoai/fastfold/client';

function EditPostForm({ post }) {
  const updatePost = useUpdate('posts', {
    onSuccess: (updatedPost) => {
      console.log('Post updated:', updatedPost);
      // Navigate back or show success message
    },
    onError: (error) => {
      console.error('Update failed:', error);
    }
  });

  const handleUpdate = async (formData) => {
    try {
      const updatedPost = await updatePost.mutate({
        id: post.id,
        data: {
          title: formData.title,
          content: formData.content,
          published: formData.published
        }
      });
      
      console.log('Updated post:', updatedPost);
    } catch (error) {
      console.error('Update failed:', error);
    }
  };

  return (
    <form onSubmit={handleUpdate}>
      {/* form fields */}
      <button 
        type="submit" 
        disabled={updatePost.isLoading}
      >
        {updatePost.isLoading ? 'Updating...' : 'Update Post'}
      </button>
      
      {updatePost.error && (
        <div className="error">
          Error: {updatePost.error.message}
        </div>
      )}
    </form>
  );
}
```

#### 🗑️ **Deleting Data**

```tsx
import { useDelete } from '@flavoai/fastfold/client';

function PostItem({ post, onDeleted }) {
  const deletePost = useDelete('posts', {
    onSuccess: () => {
      console.log('Post deleted successfully');
      onDeleted?.(post.id); // Notify parent component
    },
    onError: (error) => {
      console.error('Delete failed:', error);
    }
  });

  const handleDelete = async () => {
    if (confirm('Are you sure you want to delete this post?')) {
      try {
        await deletePost.mutate(post.id);
      } catch (error) {
        console.error('Delete failed:', error);
      }
    }
  };

  return (
    <div className="post-item">
      <h3>{post.title}</h3>
      <p>{post.content}</p>
      
      <button 
        onClick={handleDelete}
        disabled={deletePost.isLoading}
        className="delete-btn"
      >
        {deletePost.isLoading ? 'Deleting...' : 'Delete'}
      </button>
    </div>
  );
}
```

#### 🔄 **Complete CRUD Example**

```tsx
import { useQuery, useCreate, useUpdate, useDelete } from '@flavoai/fastfold/client';

function PostsManager() {
  // Query posts - React Query handles caching automatically
  const { data: posts, isLoading } = useQuery('posts', {
    with: { author: true },
    orderBy: { createdAt: 'desc' }
  });

  // CRUD operations - cache automatically invalidated!
  const createPost = useCreate('posts');
  const updatePost = useUpdate('posts');
  const deletePost = useDelete('posts');

  const handleCreate = (data) => createPost.mutate(data);
  const handleUpdate = (id, data) => updatePost.mutate({ id, data });
  const handleDelete = (id) => deletePost.mutate(id);

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <CreateForm onSubmit={handleCreate} />
      
      {posts?.map(post => (
        <PostCard 
          key={post.id} 
          post={post}
          onUpdate={handleUpdate}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}
```

#### 🔄 **Cache Management with React Query**

Fastfold uses React Query for automatic cache management! Here's how it works:

##### **Automatic Cache Invalidation (Default Behavior)**

```tsx
function PostsList() {
  // React Query handles all caching automatically!
  const { data: posts, isLoading, error } = useQuery('posts', {
    with: { author: true }
  });
  
  const createPost = useCreate('posts');
  const updatePost = useUpdate('posts');
  const deletePost = useDelete('posts');

  const handleCreate = () => {
    createPost.mutate({
      title: 'New Post',
      content: 'Hello World!'
    });
    // ✨ Cache automatically invalidated and refetched!
    // No manual refetch needed!
  };

  // React Query provides:
  // - 5min stale time with background refetch
  // - Automatic cache invalidation after mutations
  // - Smart deduplication of identical requests
  // - Error retry with exponential backoff
}
```

##### **Advanced: Custom Cache Options**

```tsx
function PostsList() {
  // Customize React Query behavior
  const { data: posts } = useQuery('posts', {
    with: { author: true }
  }, {
    staleTime: 10 * 60 * 1000, // 10 minutes
    gcTime: 30 * 60 * 1000,    // 30 minutes  
    refetchOnWindowFocus: false,
    refetchInterval: 2 * 60 * 1000 // Poll every 2 minutes
  });

  const createPost = useCreate('posts', {
    onSuccess: (newPost) => {
      console.log('✅ Post created:', newPost);
      // Cache automatically updates - no manual work needed!
    },
    onError: (error) => {
      console.error('❌ Failed to create post:', error);
      // React Query handles retries automatically
    }
  });

  // React Query DevTools (optional)
  // Add <ReactQueryDevtools /> to see cache state
}
```

##### **Advanced: Cache Utilities for Power Users**

```tsx
import { useInvalidateCache, useUpdateCache } from '@flavoai/fastfold/client';

function PostEditor() {
  const invalidateCache = useInvalidateCache();
  const updateCache = useUpdateCache();

  const updatePost = useUpdate('posts', {
    onSuccess: (updatedPost) => {
      // Option 1: Invalidate specific cache keys
      invalidateCache(['posts']); // Refetch all posts queries
      
      // Option 2: Update cache directly (optimistic)
      updateCache(['posts'], (oldData) => 
        oldData?.map(post => 
          post.id === updatedPost.id ? updatedPost : post
        )
      );
    }
  });

  const handleBulkUpdate = async () => {
    // For complex operations, invalidate multiple caches
    await bulkUpdatePosts();
    invalidateCache(['posts', 'users', 'stats']);
  };
}
```

> **✨ React Query Power**: Fastfold uses React Query under the hood, giving you all its benefits:
> - **Smart caching** with configurable stale times
> - **Background refetching** to keep data fresh  
> - **Automatic deduplication** of identical requests
> - **Error retry** with exponential backoff
> - **Optimistic updates** and rollback on error
> - **DevTools integration** for debugging cache state
> 
> Most apps won't need manual cache management - React Query handles it all automatically! 🎉

### Security API

```typescript
import { Security } from '@flavoai/fastfold/client';

Security.public()                    // Anyone can access
Security.admin()                     // Only admin role
Security.owner('userId')             // Owner-based access
Security.team('teamId')              // Team-based access
Security.authenticated()             // Any logged-in user
Security.readOnlyPublic()            // Public read, admin write
Security.custom((ctx) => boolean)    // Custom logic
```

## Auto-Generated API Endpoints

Fastfold automatically creates CRUD endpoints based on your `operations` config:

### Users (operations: ['read', 'update'])
```
GET    /api/users           ✅ List users
GET    /api/users/:id       ✅ Get user  
PUT    /api/users/:id       ✅ Update user
POST   /api/users           ❌ Blocked
DELETE /api/users/:id       ❌ Blocked
```

### Posts (operations: ['create', 'read', 'update', 'delete'])
```
GET    /api/posts           ✅ List posts (with security filtering)
GET    /api/posts/:id       ✅ Get post
POST   /api/posts           ✅ Create post
PUT    /api/posts/:id       ✅ Update post (owner only)
DELETE /api/posts/:id       ✅ Delete post (owner only)

// Relationship endpoints (automatic from Drizzle relations)
GET    /api/posts/:id/author    ✅ Get post's author
GET    /api/posts/:id/comments  ✅ Get post's comments  
GET    /api/users/:id/posts     ✅ Get user's posts
```

### Query Parameters
```
GET /api/posts?where={"published":true}&orderBy={"createdAt":"desc"}&limit=10
GET /api/posts?with={"author":true,"comments":true}  // Include relations
```

## Examples

See the `examples/` directory for complete examples:

- [Quickstart Backend](examples/quickstart.ts)
- [React Client Usage](examples/react-client.tsx)

## Development

```bash
# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Run tests
npm test
```

## Database Operations

Fastfold automatically handles database setup with zero configuration:

```bash
# Auto-migration: Tables are created automatically when server starts
npm run dev  # Creates tables from your schema definitions

# Reset database: Simply delete the database file
rm ./fastfold.db  # Default SQLite database file
npm run dev       # Recreates tables on next start

# Custom database location
const adapter = await Fastfold.quickStart({
  drizzle: { 
    db: drizzle(new Database('./my-app.db'), { schema }),
    schema 
  },
  tables: { /* your table configs */ }
});
```

**Note**: Fastfold uses auto-migration - tables are created/updated automatically based on your schema definitions. No manual migration commands needed.

## Advanced Features

### 🔒 Granular CRUD Control

Control exactly which operations are allowed per table:

```typescript
await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: {
    users: {
      security: Security.authenticated(),
      operations: ['read', 'update'] // Only GET and PUT endpoints
    },
    posts: {
      security: Security.owner('authorId'),
      operations: ['create', 'read', 'update', 'delete'] // Full CRUD
    },
    comments: {
      security: Security.custom((ctx) => ctx.user?.id === ctx.data?.authorId),
      operations: ['create', 'read', 'delete'] // No updates allowed
    }
  }
});
```

### 🎯 Custom Endpoints with Full Drizzle Power

```typescript
const adapter = await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: { /* your table configs */ },
  
  endpoints: (app) => {
    // Use Drizzle's relational queries
    app.get('/api/trending', Security.public(), async (req, res) => {
      const trending = await adapter.queryWithRelations('posts', {
        with: { 
          author: { columns: { name: true } },
          comments: true 
        },
        where: { published: true },
        orderBy: { createdAt: 'desc' },
        limit: 10
      });
      res.json(trending);
    });

    // Complex business logic
    app.post('/api/newsletter', Security.admin(), async (req, res) => {
      const users = await adapter.query('users', {
        where: { role: 'subscriber' }
      });
      // Send emails...
      res.json({ sent: users.length });
    });
  }
});

// Use adapter anywhere in your app
export { adapter };
```

### 🚀 Advanced Configuration

```typescript
await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: { /* your table configs */ },
  
  // Authentication configuration
  auth: {
    secret: 'your-jwt-secret',
    expiresIn: '24h'
  },
  
  // Custom endpoints
  endpoints: (app) => {
    app.get('/api/trending', async (req, res) => {
      const trending = await adapter.queryWithRelations('posts', {
        with: { author: true },
        where: { published: true },
        orderBy: { createdAt: 'desc' },
        limit: 10
      });
      res.json(trending);
    });
  },
  
  // Server hooks
  hooks: {
    onServerStart: async (server) => {
      console.log('🚀 Server ready on port', server.port);
    }
  }
});
});
```

## Why Fastfold?

**Perfect for LLMs and rapid development:**

1. **Drizzle ORM Power** - Full database relationships, migrations, validation
2. **Tiny API surface** - Just define Drizzle schema + security rules
3. **Zero boilerplate** - Auto-generated CRUD with granular control
4. **Type-safe by default** - Full TypeScript from database to frontend
5. **Production ready** - PostgreSQL, MySQL, SQLite with foreign keys
6. **Extensible** - Full Express access + Drizzle queries when needed

**Perfect for:**

✅ **Production applications** - Full database power with Drizzle  
✅ **Rapid prototyping** - Zero-config CRUD generation  
✅ **LLM-assisted development** - Minimal, predictable API  
✅ **React + Backend** - Unified TypeScript experience  
✅ **Complex data models** - Relationships, constraints, validation  
✅ **Custom business logic** - Express endpoints + Drizzle queries

**Not suitable for:**

❌ **Microservices architecture** - Single server design  
❌ **Non-database apps** - Focused on CRUD operations  
❌ **GraphQL requirements** - REST API only  
❌ **Real-time by default** - WebSockets require custom endpoints  

## License

MIT
