From 4b612b9311a131d1206fb67b829a07a938f1e4ed Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Nov 2025 11:48:14 +0000 Subject: [PATCH] docs: Split README into user and developer guides - Refactored README.md to focus on library users with: - Clear features section highlighting key capabilities - Improved structure and readability - Added comprehensive Aggregations section - Enhanced code examples throughout - Added Contributing and License sections - Created CONTRIBUTING.md for developers with: - Complete development setup instructions - Monorepo structure and workflow details - Code style guidelines and conventions - Key design patterns and TypeScript techniques - Testing guidelines and examples - Pull request process This separation provides clear, purpose-driven documentation for both user and contributor audiences. --- CONTRIBUTING.md | 353 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 183 +++++++++---------------- 2 files changed, 414 insertions(+), 122 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a8664c3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,353 @@ +# Contributing to Esix + +Thank you for your interest in contributing to Esix! This guide will help you get started with development. + +## About + +Esix is a TypeScript database library for MongoDB, heavily inspired by Laravel's Eloquent and ActiveRecord. Models map to documents in the database, and the QueryBuilder is used to select which documents to retrieve. + +## Prerequisites + +- Node.js 20+ +- Yarn +- MongoDB (for local testing) + +## Getting Started + +```bash +# Clone the repository +git clone https://github.com/Artmann/esix.git +cd esix + +# Install dependencies +yarn install + +# Start all development servers +yarn dev + +# Build all packages +yarn build + +# Run tests +yarn test + +# Run linting +yarn lint + +# Format code +yarn format + +# Type check +yarn typecheck +``` + +## Monorepo Structure + +This project uses [Turborepo](https://turbo.build/) to manage a monorepo with the following structure: + +- `packages/esix/` - The main Esix ORM package +- `packages/website/` - Next.js documentation website +- `docs/` - Legacy documentation build system (to be replaced by website) + +## Build & Test Commands + +### From the root directory (using Turborepo): + +- Build all packages: `yarn build` +- Lint all packages: `yarn lint` +- Format all packages: `yarn format` +- Test all packages: `yarn test` +- Type check all packages: `yarn typecheck` +- Documentation: `yarn docs:build` and `yarn docs:serve` + +### From the esix package directory (`packages/esix/`): + +- Build: `yarn build` +- Lint: `yarn lint` +- Format: `yarn format` +- Test all: `yarn test` +- Test single file: `yarn test path/to/file.spec.ts` +- Test specific test: `yarn test -t "test name pattern"` +- Type check: `yarn typecheck` + +## Working on Specific Packages + +### Esix Package + +To work specifically on the Esix package: + +```bash +# Navigate to the package +cd packages/esix + +# Build the package +yarn build + +# Run tests +yarn test + +# Run linting +yarn lint +``` + +### Website Package + +To work on the Next.js documentation website: + +```bash +# Start the development server +yarn workspace website dev + +# Or navigate to the package +cd packages/website + +# Start development server +yarn dev + +# Build for production +yarn build + +# Start production server +yarn start +``` + +### Development Workflow + +```bash +# Start all development servers (recommended for full-stack development) +yarn dev + +# Work on specific packages +yarn workspace esix build +yarn workspace website dev + +# Run commands across all packages +yarn build # Build everything +yarn test # Test everything +yarn lint # Lint everything +``` + +## Code Style Guidelines + +- **TypeScript**: Strict typing with null checks and proper return types +- **Formatting**: Single quotes, no semicolons, 2-space indent (enforced by Prettier) +- **Naming**: + - PascalCase for classes + - camelCase for methods/properties + - kebab-case for files +- **Imports**: Group external then internal imports +- **Error Handling**: Promise-based with proper type guards and null handling +- **Testing**: BDD style with describe/it blocks, clear test sections per method +- **Default Sorting**: Order things alphabetically by default +- **Documentation**: Document all public methods with JSDoc comments including parameters and return types + +## File Organization + +- `packages/esix/src/base-model.ts` - Base model class all models extend from +- `packages/esix/src/query-builder.ts` - Query building logic for MongoDB operations +- `packages/esix/src/types.ts` - TypeScript type definitions +- `packages/esix/src/connection-handler.ts` - MongoDB connection management +- `packages/esix/src/sanitize.ts` - Input sanitization to prevent NoSQL injection +- Model definitions extend BaseModel +- Test files use `.spec.ts` extension +- Each class should have a single responsibility + +## Key Design Patterns and Concepts + +### Fluent Interface + +Methods return `this` or a QueryBuilder for chaining: + +```typescript +const users = await User + .where('age', '>', 18) + .where('isActive', true) + .orderBy('name') + .limit(10) + .get() +``` + +### Static vs. Instance Methods + +BaseModel provides both: +- **Static methods**: For querying collections (e.g., `User.find()`, `User.where()`) +- **Instance methods**: For manipulating instances (e.g., `user.save()`, `user.delete()`) + +### MongoDB Operators + +Query methods translate to MongoDB operators: +- `where('age', '>', 18)` → `{ age: { $gt: 18 } }` +- `whereIn('id', [1, 2, 3])` → `{ id: { $in: [1, 2, 3] } }` +- `whereNotIn('role', ['admin'])` → `{ role: { $nin: ['admin'] } }` + +### ID Handling + +MongoDB uses `_id` internally, but Esix exposes it as `id` in models for a more intuitive API. + +### Type Safety + +Advanced TypeScript features provide type safety: +- Conditional types +- `keyof` operator +- Generics with constraints +- Constructor typing + +## Query Methods + +### where + +Filter by field equality or comparison: + +- Two-parameter syntax: `where('status', 'active')` for equality +- Three-parameter syntax: `where('age', '>', 18)` with comparison operators +- Supported operators: `=`, `!=`, `<>`, `>`, `>=`, `<`, `<=` +- Maps to MongoDB operators: `$gt`, `$gte`, `$lt`, `$lte`, `$ne` + +### whereIn / whereNotIn + +Filter where a field's value is (or is not) in an array: + +```typescript +const users = await User.whereIn('id', ['id1', 'id2']).get() +const nonAdmins = await User.whereNotIn('role', ['admin', 'moderator']).get() +``` + +### find / findBy + +- `find`: Find a model by its ID +- `findBy`: Find a model where a field matches a value + +### Sorting and Pagination + +- `orderBy`: Sort results by a field +- `limit`: Limit the number of results +- `skip`: Skip a number of results (for pagination) + +### pluck + +Extract an array of values for a specific field: + +```typescript +const titles = await Post.pluck('title') +``` + +## Aggregation Methods + +Esix provides static aggregation methods on BaseModel: + +- `aggregate`: Direct access to MongoDB aggregation pipeline +- `average`: Calculate average of values for a field +- `count`: Count total number of documents +- `max`: Find maximum value for a field +- `min`: Find minimum value for a field +- `percentile`: Calculate nth percentile for a field +- `sum`: Calculate sum of values for a field + +### Aggregation Examples + +```typescript +// Count all users +const userCount = await User.count() + +// Sum all order amounts +const totalSales = await Order.sum('amount') + +// Get average user age +const avgAge = await User.average('age') + +// Find maximum score +const highScore = await Test.max('score') + +// Get 95th percentile response time +const p95 = await ResponseTime.percentile('value', 95) + +// Complex aggregations with MongoDB pipeline +const results = await User.aggregate([ + { $group: { _id: '$department', count: { $sum: 1 } } } +]) + +// Chaining with query methods +const avgAgeForAdults = await User.where('age', '>=', 18).average('age') +``` + +## TypeScript Techniques Used + +Understanding these TypeScript patterns will help you contribute effectively: + +- **Generic type parameters** for model types: `` +- **Conditional types**: `K extends keyof T ? T[K] : any` +- **The `keyof` operator** to get property names: `K extends keyof T | '_id'` +- **Constructor typing** with `ObjectType` +- **Method chaining** with `this` return types + +## Testing + +Tests use Jest with BDD-style describe/it blocks: + +```typescript +describe('User', () => { + describe('.where()', () => { + it('filters by field equality', async () => { + // Arrange + const user = await User.create({ name: 'John', age: 30 }) + + // Act + const results = await User.where('age', 30).get() + + // Assert + expect(results).toHaveLength(1) + expect(results[0].name).toBe('John') + }) + }) +}) +``` + +### Running Tests + +```bash +# Run all tests +yarn test + +# Run specific test file +yarn test path/to/file.spec.ts + +# Run tests matching a pattern +yarn test -t "where method" + +# Watch mode +yarn test --watch +``` + +## Recent Implementations + +- **Comparison Operators**: Added support for comparison operators in where clauses (`>`, `>=`, `<`, `<=`, `=`, `!=`, `<>`), following Laravel Eloquent and Rails Active Record patterns +- **Aggregation Functions**: Added static aggregation methods to BaseModel for direct use on model classes (count, sum, average, max, min, percentile, aggregate) +- **whereNotIn**: Added to filter out records where a field's value is within a given array +- **Improved Types**: Enhanced type safety for methods like `findBy` using conditional types + +## Pull Request Process + +1. Fork the repository and create your branch from `main` +2. Make your changes and add tests +3. Ensure all tests pass: `yarn test` +4. Ensure code is properly formatted: `yarn format` +5. Ensure there are no linting errors: `yarn lint` +6. Ensure TypeScript compiles: `yarn typecheck` +7. Update documentation if needed +8. Submit a pull request with a clear description of the changes + +## Development Environment Notes + +- The dev server runs on port 3000 +- MongoDB connection string is set via `DB_URL` environment variable +- Use `DB_ADAPTER=mock` for testing without a real MongoDB instance + +## Need Help? + +- Check the [documentation](https://esix.netlify.app/) +- Open an issue on GitHub +- Review existing issues and pull requests + +## License + +By contributing to Esix, you agree that your contributions will be licensed under the MIT License. diff --git a/README.md b/README.md index 624d442..4ff8a6e 100644 --- a/README.md +++ b/README.md @@ -9,119 +9,27 @@ npm downloads License Build Status -TypeScript - + TypeScript

-## Development - -This project uses [Turborepo](https://turbo.build/) to manage the monorepo structure. The main package is located in `packages/esix/`. - -### Prerequisites - -- Node.js 20+ -- Yarn - -### Getting Started - -```bash -# Install dependencies -yarn install - -# Start all development servers -yarn dev - -# Build all packages -yarn build - -# Run tests -yarn test - -# Run linting -yarn lint - -# Format code -yarn format - -# Type check -yarn typecheck -``` - -### Package Structure - -- `packages/esix/` - The main Esix ORM package -- `packages/website/` - Next.js documentation website -- `docs/` - Legacy documentation build system (to be replaced by website) - -### Working on Specific Packages - -#### Esix Package - -To work specifically on the Esix package: - -```bash -# Navigate to the package -cd packages/esix - -# Build the package -yarn build - -# Run tests -yarn test - -# Run linting -yarn lint -``` - -#### Website Package - -To work on the Next.js documentation website: +--- -```bash -# Start the development server -yarn workspace website dev - -# Or navigate to the package -cd packages/website - -# Start development server -yarn dev - -# Build for production -yarn build - -# Start production server -yarn start -``` - -### Development Workflow +Working with MongoDB in TypeScript usually means choosing between simplicity and type safety. Native drivers require verbose, untyped queries, while most ORMs demand extensive configuration and boilerplate. -```bash -# Start all development servers (recommended for full-stack development) -yarn dev - -# Work on specific packages -yarn workspace esix build -yarn workspace website dev - -# Run commands across all packages -yarn build # Build everything -yarn test # Test everything -yarn lint # Lint everything -``` +Esix brings the elegance of ActiveRecord and Eloquent to MongoDB with full TypeScript support. Define your models as simple TypeScript classes, and Esix automatically handles database operations and type inference through sensible conventions. -Working with MongoDB in TypeScript usually means choosing between simplicity and -type safety. Native drivers require verbose, untyped queries, while most ORMs -demand extensive configuration and boilerplate. +**No configuration files, no setup overhead**—just MongoDB development that feels as natural as working with any TypeScript object while maintaining the flexibility that makes MongoDB powerful. -Esix brings the elegance of ActiveRecord and Eloquent to MongoDB with full -TypeScript support. Define your models as simple TypeScript classes, and Esix -automatically handles database operations and type inference through sensible -conventions. +## Features -No configuration files, no setup overhead—just MongoDB development that feels as -natural as working with any TypeScript object while maintaining the flexibility -that makes MongoDB powerful. +- **Zero Configuration** - No decorators, no config files, just TypeScript classes +- **Full Type Safety** - Leverages TypeScript's type system for compile-time safety +- **Eloquent-Style API** - Familiar, intuitive syntax inspired by Laravel and ActiveRecord +- **Automatic Connection** - Connects to MongoDB automatically when needed +- **Rich Query Builder** - Fluent API with comparison operators, sorting, pagination, and more +- **Aggregation Support** - Built-in methods for common aggregations (count, sum, average, etc.) +- **Relationships** - Simple, type-safe relationship definitions +- **NoSQL Injection Protection** - Automatic input sanitization ## Installation @@ -188,6 +96,8 @@ await user.delete() ### Querying +Esix provides a powerful, fluent query builder with full type safety: + ```ts // Find by field const activeUsers = await User.where('isActive', true).get() @@ -200,7 +110,8 @@ const affordableItems = await Product.where('price', '<=', 50).get() const nonBannedUsers = await User.where('status', '!=', 'banned').get() // Multiple conditions -const youngActiveUsers = await User.where('isActive', true) +const youngActiveUsers = await User + .where('isActive', true) .where('age', '<', 25) .get() @@ -237,20 +148,41 @@ const titles = await Post.pluck('title') const authors = await Post.pluck('authorId') ``` +### Aggregations + +```ts +// Count documents +const userCount = await User.count() +const activeCount = await User.where('isActive', true).count() + +// Sum values +const totalSales = await Order.sum('amount') + +// Calculate averages +const avgAge = await User.average('age') + +// Find min/max +const lowestPrice = await Product.min('price') +const highestScore = await Test.max('score') + +// Percentiles +const p95ResponseTime = await ResponseTime.percentile('value', 95) + +// Custom aggregations +const results = await User.aggregate([ + { $group: { _id: '$department', count: { $sum: 1 } } } +]) +``` + ### First or Create -Find existing records or create new ones: +Find existing records or create new ones atomically: ```ts // Find user by email, create if doesn't exist const user = await User.firstOrCreate( - { - email: 'new@example.com' - }, - { - name: 'New User', - age: 25 - } + { email: 'new@example.com' }, + { name: 'New User', age: 25 } ) // Using only filter (attributes default to filter) @@ -262,6 +194,8 @@ const settings = await Settings.firstOrCreate({ ### Relationships +Define relationships between models with type safety: + ```ts class Author extends BaseModel { public name = '' @@ -286,7 +220,8 @@ const publishedPosts = await author ```ts // Blog API endpoints export async function getPosts(req: Request, res: Response) { - const posts = await Post.where('publishedAt', '!=', null) + const posts = await Post + .where('publishedAt', '!=', null) .orderBy('publishedAt', 'desc') .limit(20) .get() @@ -308,10 +243,7 @@ export async function createPost(req: Request, res: Response) { export async function getOrCreateUser(req: Request, res: Response) { const user = await User.firstOrCreate( { email: req.body.email }, - { - name: req.body.name, - isActive: true - } + { name: req.body.name, isActive: true } ) res.json({ user, created: user.createdAt === user.updatedAt }) @@ -328,5 +260,12 @@ Esix works with zero configuration but supports these environment variables: ## Documentation -For comprehensive documentation, visit -[https://esix.netlify.app/](https://esix.netlify.app/). +For comprehensive documentation, visit [https://esix.netlify.app/](https://esix.netlify.app/). + +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines. + +## License + +MIT © [Christoffer Artmann](https://github.com/Artmann)