A lightweight, type-safe MySQL ORM for Node.js and TypeScript with database migrations
npm install mango-ormbash
Using npm
npm install mango-orm mysql @types/mysql
Using yarn
yarn add mango-orm mysql @types/mysql
Using pnpm (recommended)
pnpm add mango-orm mysql @types/mysql
`
$3
- Node.js: >= 14.x
- TypeScript: >= 4.5 (for TypeScript projects)
- MySQL: >= 5.7 or MariaDB >= 10.2
π Quick Start
Get started with Mango ORM in less than 5 minutes!
`typescript
import { Mango } from "mango-orm";
// 1. Initialize and connect
const mango = new Mango();
await mango.connect({
host: "localhost",
user: "root",
password: "your_password",
database: "your_database"
});
// 2. Define your data structure
interface User {
id?: number;
username: string;
email: string;
age?: number;
}
// 3. Create a table
const users = await mango.createTable("users", {
id: mango.types().int().autoIncrement().primaryKey(),
username: mango.types().varchar(50).notNull().unique(),
email: mango.types().varchar(100).notNull(),
age: mango.types().int()
});
// 4. Insert data
await users.insertOne({
username: "john_doe",
email: "john@example.com",
age: 25
}).execute();
// 5. Query data with conditions
const adults = await users
.selectAll()
.where("age", ">=", 18)
.orderBy("username")
.execute();
console.log(adults);
// 6. Update data
await users
.update({ age: 26 })
.where("username", "=", "john_doe")
.execute();
// 7. Delete data
await users
.delete()
.where("age", "<", 18)
.execute();
// 8. Disconnect when done
await mango.disconnect();
`
---
π Complete Documentation
$3
#### Basic Connection
`typescript
const mango = new Mango();
await mango.connect({
host: "localhost", // MySQL server host
user: "root", // Database username
password: "mypassword", // Database password
database: "myapp" // Database name
});
`
#### Connection Features
- β
Auto-connects using connection pooling for performance
- β
Auto-discovers all existing tables and their schemas
- β
Validates connection parameters
- β
Returns Mango instance for chaining
#### Disconnect
Always disconnect when your application is shutting down:
`typescript
await mango.disconnect();
`
$3
#### Define Your Schema
`typescript
interface Post {
id?: number;
title: string;
content: string;
author_id: number;
created_at?: Date;
is_published?: boolean;
}
const posts = await mango.createTable("posts", {
id: mango.types().int().autoIncrement().primaryKey(),
title: mango.types().varchar(200).notNull(),
content: mango.types().text().notNull(),
author_id: mango.types().int().notNull(),
created_at: mango.types().timeStamp(),
is_published: mango.types().boolean()
});
`
#### Available Data Types
| Type | Method | Example | SQL Output |
|------|--------|---------|------------|
| Integer | int() | types().int() | INT |
| Big Integer | bigInt() | types().bigInt() | BIGINT |
| Float | float() | types().float() | FLOAT |
| Variable String | varchar(n) | types().varchar(255) | VARCHAR(255) |
| Fixed String | char(n) | types().char(10) | CHAR(10) |
| Text | text() | types().text() | TEXT |
| Date | date() | types().date() | DATE |
| DateTime | dateTime() | types().dateTime() | DATETIME |
| Timestamp | timeStamp() | types().timeStamp() | TIMESTAMP |
| Boolean | boolean() | types().boolean() | BOOLEAN |
| Tiny Integer | tinyInt(n) | types().tinyInt(1) | TINYINT(1) |
#### Type Modifiers
Chain modifiers to customize your columns:
`typescript
mango.types()
.int() // Column type
.notNull() // Cannot be NULL
.autoIncrement() // Auto-incrementing
.primaryKey() // Primary key
.unique() // Unique constraint
`
Common Patterns:
`typescript
// Primary key (typical ID column)
id: mango.types().int().autoIncrement().primaryKey()
// Required email with uniqueness
email: mango.types().varchar(100).notNull().unique()
// Optional text field
bio: mango.types().text()
// Required foreign key
user_id: mango.types().int().notNull()
`
#### Accessing Existing Tables
After connection, all existing tables are automatically loaded:
`typescript
// Get a typed table instance
const existingUsers = mango.selectTable("users");
// Query immediately
const data = await existingUsers.selectAll().execute();
`
`
#### Table Information
`typescript
const tableName = users.getName(); // Returns: "users"
const fields = users.getFields(); // Returns: ["id", "username", "email"]
console.log(Table: ${tableName});
console.log(Columns:, fields);
`
---
$3
#### WHERE Clause
Filter results with WHERE conditions:
`typescript
// Find user by username
const user = await users
.selectAll()
.where("username", "=", "john_doe")
.execute();
// Find adult users
const adults = await users
.selectAll()
.where("age", ">=", 18)
.execute();
// Find published posts
const published = await posts
.selectAll()
.where("is_published", "=", true)
.execute();
`
#### Supported Operators
| Operator | Description | Example |
|----------|-------------|---------|
| = | Equal to | where("age", "=", 25) |
| != or <> | Not equal | where("status", "!=", "deleted") |
| > | Greater than | where("age", ">", 18) |
| < | Less than | where("price", "<", 100) |
| >= | Greater or equal | where("age", ">=", 21) |
| <= | Less or equal | where("stock", "<=", 10) |
| LIKE | Pattern match | where("email", "LIKE", "%@gmail.com") |
| NOT LIKE | Not matching | where("name", "NOT LIKE", "test%") |
| IN | In list | Use whereIn() method |
| NOT IN | Not in list | Use whereNotIn() method |
#### AND Conditions
Combine multiple conditions (all must be true):
`typescript
// Find young adults
const youngAdults = await users
.selectAll()
.where("age", ">=", 18)
.and("age", "<=", 30)
.execute();
// Find published posts by specific author
const authorPosts = await posts
.selectAll()
.where("author_id", "=", 1)
.and("is_published", "=", true)
.execute();
// Multiple AND conditions
const specificUsers = await users
.selectAll()
.where("age", ">", 25)
.and("age", "<", 40)
.and("email", "LIKE", "%@company.com")
.execute();
`
#### OR Conditions
Alternative conditions (any can be true):
`typescript
// Find users who are either admins or moderators
const staff = await users
.selectAll()
.where("role", "=", "admin")
.or("role", "=", "moderator")
.execute();
// Find urgent or high priority tasks
const important = await tasks
.selectAll()
.where("priority", "=", "urgent")
.or("priority", "=", "high")
.execute();
`
#### Combining AND/OR
`typescript
// Complex conditions: (age >= 18 AND age <= 65) OR is_verified = true
const eligible = await users
.selectAll()
.where("age", ">=", 18)
.and("age", "<=", 65)
.or("is_verified", "=", true)
.execute();
`
#### WHERE IN / NOT IN
Check if value exists in a list:
`typescript
// Find users with specific IDs
const selectedUsers = await users
.selectAll()
.whereIn("id", [1, 5, 10, 15])
.execute();
// Find posts by multiple authors
const multiAuthorPosts = await posts
.selectAll()
.whereIn("author_id", [1, 2, 3])
.execute();
// Exclude specific users
const otherUsers = await users
.selectAll()
.whereNotIn("id", [1, 2, 3])
.execute();
`
#### Working with Falsy Values
Important: WHERE conditions support falsy values (0, false, empty string):
`typescript
// β
Works correctly with 0
const inactiveUsers = await users
.selectAll()
.where("login_count", "=", 0)
.execute();
// β
Works correctly with false
const unpublished = await posts
.selectAll()
.where("is_published", "=", false)
.execute();
// β
Works correctly with empty string
const noDescription = await products
.selectAll()
.where("description", "=", "")
.execute();
`
---
$3
Update existing records with WHERE conditions:
`typescript
// Update single field
await users
.update({ age: 26 })
.where("username", "=", "john_doe")
.execute();
// Update multiple fields
await users
.update({
email: "newemail@example.com",
age: 30,
is_verified: true
})
.where("id", "=", 5)
.execute();
// Update with AND conditions
await posts
.update({ is_published: true })
.where("author_id", "=", 1)
.and("title", "LIKE", "%Draft%")
.execute();
// Bulk update
await products
.update({ stock: 0 })
.where("stock", "<", 5)
.execute();
`
Important Notes:
- β
Always use WHERE to avoid updating all rows
- β
Validates all field names exist in table
- β
Supports all data types
- β
Uses prepared statements for security
---
$3
Remove records from table:
`typescript
// Delete specific user
await users
.delete()
.where("id", "=", 10)
.execute();
// Delete with conditions
await posts
.delete()
.where("is_published", "=", false)
.and("created_at", "<", "2024-01-01")
.execute();
// Delete using IN
await users
.delete()
.whereIn("id", [5, 10, 15])
.execute();
`
β οΈ Warning: DELETE without WHERE will remove ALL rows!
`typescript
// This deletes everything! Use with caution
await users.delete().execute(); // β Dangerous!
// Better: Use truncate for clearing table
await users.truncate().execute(); // β
Explicit intent
`
---
$3
#### Join Tables
Combine data from multiple tables:
`typescript
const userPosts = await users
.selectAll()
.join("INNER", "posts", {
left: "users.id",
operator: "=",
right: "posts.author_id"
})
.execute();
// LEFT JOIN
const allUsersWithPosts = await users
.selectAll()
.join("LEFT", "posts", {
left: "users.id",
operator: "=",
right: "posts.author_id"
})
.execute();
`
Join Types: INNER, LEFT, RIGHT, FULL
#### Modifying Table Schema
Add New Columns:
`typescript
await users.addColumns({
phone: mango.types().varchar(20),
address: mango.types().text(),
verified_at: mango.types().dateTime()
}).execute();
`
Remove Columns:
`typescript
await users.removeColumns(["phone", "address"]).execute();
`
#### Truncate Table
Remove all rows (faster than DELETE):
`typescript
await users.truncate().execute();
`
#### Drop Table
Permanently delete a table:
`typescript
await mango.dropTable("old_table");
`
#### Custom Raw Queries
For complex queries not covered by the query builder:
`typescript
// Custom SELECT
const result = await users.customQuery(
"SELECT * FROM users WHERE age > ? AND email LIKE ?",
[18, "%@gmail.com"]
);
// Custom INSERT with RETURNING (if supported)
const inserted = await posts.customQuery(
"INSERT INTO posts (title, content) VALUES (?, ?) RETURNING id",
["New Post", "Content here"]
);
// Complex JOIN
const complexQuery = await users.customQuery(
SELECT u.*, COUNT(p.id) as post_count
,
[5]
);
`
Tips:
- Always use ? placeholders for values
- Never concatenate user input into queries
- Use for complex operations like GROUP BY, HAVING, subqueries
---
$3
#### Single Row Insert
Insert one record at a time:
`typescript
// Basic insert
await users.insertOne({
username: "jane_doe",
email: "jane@example.com",
age: 28
}).execute();
// Insert with all fields
await posts.insertOne({
title: "Getting Started with Mango",
content: "This is a comprehensive guide...",
author_id: 1,
is_published: true
}).execute();
`
Features:
- β
Type-safe: TypeScript validates field names and types
- β
SQL Injection protected with prepared statements
- β
Auto-validates fields exist in table schema
- β
Supports all data types (numbers, strings, booleans, dates, null)
#### Bulk Insert
Insert multiple rows efficiently:
`typescript
await posts.insertMany(
["title", "content", "author_id"], // Column names
[ // Data rows
["First Post", "Content here", 1],
["Second Post", "More content", 1],
["Third Post", "Even more", 2]
]
).execute();
// Insert 1000 records efficiently
const bulkData = [];
for (let i = 0; i < 1000; i++) {
bulkData.push([User ${i}, user${i}@example.com, 20 + i % 50]);
}
await users.insertMany(
["username", "email", "age"],
bulkData
).execute();
`
Performance:
- β
Single query for all rows (much faster than multiple inserts)
- β
Efficient for batch operations
- β
Validates all rows have same number of columns
$3
#### Select All Columns
`typescript
// Get all users
const allUsers = await users.selectAll().execute();
// Get all posts
const allPosts = await posts.selectAll().execute();
`
#### Select Specific Columns
`typescript
// Select only username and email
const userContacts = await users
.selectColumns(["username", "email"])
.execute();
// Select specific post fields
const postTitles = await posts
.selectColumns(["id", "title", "created_at"])
.execute();
`
#### Select Distinct Values
Get unique values only:
`typescript
// Get unique email domains
const uniqueEmails = await users
.selectDistinctColumns(["email"])
.execute();
// Get unique authors
const uniqueAuthors = await posts
.selectDistinctColumns(["author_id"])
.execute();
`
#### Ordering Results
Sort your query results:
`typescript
// Order by username ascending (A-Z)
const sortedUsers = await users
.selectAll()
.orderBy("username")
.sort(1) // 1 = ASC, -1 = DESC
.execute();
// Order by age descending (highest first)
const oldestFirst = await users
.selectAll()
.orderBy("age")
.sort(-1) // Descending order
.execute();
// Order posts by creation date (newest first)
const recentPosts = await posts
.selectAll()
.orderBy("created_at")
.sort(-1)
.execute();
`
#### Limiting and Pagination
`typescript
// Get first 10 users
const firstTen = await users
.selectAll()
.limit(10)
.execute();
// Get 10 users, skip first 20 (page 3, 10 per page)
const page3 = await users
.selectAll()
.limit(10)
.offset(20)
.execute();
// Complete pagination example
const PAGE_SIZE = 25;
const page = 2; // Get page 2
const paginatedUsers = await users
.selectAll()
.orderBy("id")
.sort(1)
.limit(PAGE_SIZE)
.offset((page - 1) * PAGE_SIZE)
.execute();
`
$3
Add Columns:
`typescript
await users.addColumns({
age: mango.types().int(),
phone: mango.types().varchar(20)
}).execute();
`
Remove Columns:
`typescript
await users.removeColumns(["age"]).execute();
`
$3
Truncate Table:
`typescript
await users.truncate().execute();
`
Drop Table:
`typescript
await mango.dropTable("users");
`
$3
`typescript
const result = await users.customQuery(
"SELECT * FROM users WHERE age > ?",
[18]
);
`
---
π§ API Reference
$3
Main class for database connection and management.
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| connect(config) | {host, user, password, database} | Promise | Connect to MySQL and load tables |
| disconnect() | - | Promise | Close connection pool |
| createTable | name: string, fields: Record | Promise | Create new table |
| selectTable | name: string | MangoTable | Get existing table instance |
| dropTable(name) | name: string | Promise | Drop table permanently |
| getTables() | - | MangoTable[] | Get all table instances |
| types() | - | MangoType | Get schema type builder |
| customQuery | query: string, params: any[] | Promise | Execute raw SQL |
---
$3
Query builder for table operations (all methods chainable except execute()).
#### Query Building Methods
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| selectAll() | - | this | Select all columns |
| selectColumns(cols) | cols: string[] | this | Select specific columns |
| selectDistinctColumns(cols) | cols: string[] | this | Select unique values |
| orderBy(column) | column: string | this | Order results by column |
| sort(direction) | 1 (ASC) or -1 (DESC) | this | Sort direction |
| limit(n) | n: number | this | Limit number of results |
| offset(n) | n: number | this | Skip n rows |
#### Filtering Methods
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| where(field, op, value) | field: string, op: string, value: any | this | Add WHERE condition |
| and(field, op, value) | field: string, op: string, value: any | this | Add AND condition |
| or(field, op, value) | field: string, op: string, value: any | this | Add OR condition |
| whereIn(field, values) | field: string, values: any[] | this | WHERE field IN (values) |
| whereNotIn(field, values) | field: string, values: any[] | this | WHERE field NOT IN (values) |
#### Data Modification Methods
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| insertOne(data) | data: Record | this | Insert single row |
| insertMany(fields, data) | fields: string[], data: any[][] | this | Insert multiple rows |
| update(data) | data: Record | this | Update rows (use with WHERE) |
| delete() | - | this | Delete rows (use with WHERE) |
#### Schema Modification Methods
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| addColumns(fields) | fields: Record | this | Add new columns |
| removeColumns(fields) | fields: string[] | this | Remove columns |
| truncate() | - | this | Remove all rows |
#### Utility Methods
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| execute() | - | Promise | Execute the built query |
| customQuery | query: string, params: any[] | Promise | Execute custom SQL |
| getName() | - | string | Get table name |
| getFields() | - | string[] | Get column names (copy) |
| getQuery() | - | MangoQuery | Get query object (advanced) |
#### Advanced Methods
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| join(type, table, condition) | type, table: string, condition | this | Join tables |
| commit() | - | this | Add COMMIT to query |
| rollback() | - | this | Add ROLLBACK to query |
---
$3
Schema type builder for column definitions.
#### Data Types
| Method | Returns | SQL Output | Description |
|--------|---------|------------|-------------|
| int() | this | INT | Integer number |
| bigInt() | this | BIGINT | Large integer |
| float() | this | FLOAT | Floating point |
| varchar(n) | this | VARCHAR(n) | Variable string |
| char(n) | this | CHAR(n) | Fixed string |
| text() | this | TEXT | Long text |
| date() | this | DATE | Date only |
| dateTime() | this | DATETIME | Date and time |
| timeStamp() | this | TIMESTAMP | Auto-updating timestamp |
| boolean() | this | BOOLEAN | True/false |
| tinyInt(n) | this | TINYINT(n) | Small integer |
#### Modifiers
| Method | Returns | SQL Output | Description |
|--------|---------|------------|-------------|
| notNull() | this | NOT NULL | Cannot be null |
| autoIncrement() | this | AUTO_INCREMENT | Auto-incrementing |
| primaryKey() | this | PRIMARY KEY | Primary key |
| unique() | this | UNIQUE | Must be unique |
| getQuery() | string | - | Get built SQL string |
---
οΏ½ Database Migrations
Mango includes a powerful migration system to version and manage database schema changes with color-coded console feedback.
$3
Generate a new migration file:
`bash
npm run migration:generate create_users_table
`
This creates a timestamped migration file like migrations/1734912000000_create_users_table.ts:
`typescript
import { IMangoMigrationType, Mango } from "mango-orm";
export const create_users_table: IMangoMigrationType = {
name: "create_users_table",
timestamp: 1734912000000,
up: async (mango: Mango) => {
await mango.createTable("users", {
id: mango.types().int().primaryKey().autoIncrement(),
username: mango.types().varchar(255).notNull().unique(),
email: mango.types().varchar(255).notNull(),
created_at: mango.types().timeStamp().default("CURRENT_TIMESTAMP")
});
console.log("β Users table created");
},
down: async (mango: Mango) => {
await mango.dropTable("users");
console.log("β Users table dropped");
}
};
`
$3
Create a migration runner script:
`typescript
import { Mango, MangoMigration } from "mango-orm";
import { create_users_table } from "./migrations/1734912000000_create_users_table.js";
const mango = new Mango();
await mango.connect({ / config / });
const migration = new MangoMigration(mango);
migration.add(create_users_table);
// Check migration status
await migration.status();
// Run next pending migration
await migration.migrateUp();
// Run all pending migrations
await migration.migrateUpToLatest();
// Rollback last migration
await migration.migrateDown();
// Rollback all migrations
await migration.migrateDownToOldest();
`
$3
`
=== Migration Status ===
β Executed: create_users_table
β¦Ώ Pending: add_posts_table
Total: 2 | Executed: 1 | Pending: 1
`
---
οΏ½π Security
Mango ORM is built with security in mind:
$3
All queries use parameterized statements to prevent SQL injection:
`typescript
// β
SAFE - Values are parameterized
await users.insertOne({
username: userInput, // Automatically escaped
email: emailInput
}).execute();
// β
SAFE - WHERE values are parameterized
await users
.selectAll()
.where("username", "=", userInput) // Safe from injection
.execute();
// β
SAFE - Bulk insert with parameterized values
await posts.insertMany(
["title", "content"],
[[userTitle, userContent]] // All values escaped
).execute();
`
$3
When using customQuery(), always use placeholders:
`typescript
// β
SAFE - Using placeholders
const results = await users.customQuery(
"SELECT * FROM users WHERE email = ?",
[userEmail]
);
// β UNSAFE - String concatenation
const unsafe = await users.customQuery(
SELECT * FROM users WHERE email = '${userEmail}', // SQL INJECTION RISK!
[]
);
`
$3
All operations validate field names against table schema:
`typescript
// β
Validated - Throws error if 'invalid_field' doesn't exist
await users.insertOne({
invalid_field: "value" // Error: Field doesn't exist
}).execute();
`
$3
`typescript
// Use environment variables for credentials
await mango.connect({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
`
Best Practices:
- β
Never commit credentials to source control
- β
Use .env files with proper .gitignore
- β
Rotate credentials regularly
- β
Use least-privilege database users
- β
Enable SSL/TLS for production databases
---
π§ Current Limitations
While Mango is functional, some features are still in development:
- β
Migration System: Database schema migrations with version tracking
- β³ No Transaction Support: BEGIN/COMMIT/ROLLBACK not fully implemented
- β³ Limited JOIN Support: Basic joins work but complex joins need testing
- β³ No Relations: ORM-style relations (hasMany, belongsTo) not yet available
- β³ No Query Caching: Results are not cached (may add in future)
- β³ No Connection Retry: Failed connections don't auto-retry
- β³ Single Database Only: Cannot connect to multiple databases simultaneously
---
πΊοΈ Roadmap
$3
- [x] WHERE clause support
- [x] UPDATE operations
- [x] DELETE operations
- [x] Comprehensive documentation
- [x] Migration system with CLI
- [ ] Transaction support (BEGIN, COMMIT, ROLLBACK)
- [ ] Better error messages with stack traces
- [ ] Connection pool configuration options
$3
- [ ] Seed data functionality
- [ ] Query result caching
- [ ] Batch operation optimization
- [ ] Advanced JOIN support
- [ ] Aggregate functions (COUNT, SUM, AVG, etc.)
- [ ] GROUP BY and HAVING clauses
$3
- [ ] Relation support (hasOne, hasMany, belongsTo, manyToMany)
- [ ] Eager loading and lazy loading
- [ ] Model hooks (beforeCreate, afterUpdate, etc.)
- [ ] Query event listeners
- [ ] Schema validation
- [ ] Multiple database connections
- [ ] Read/write splitting
- [ ] Query builder visual tool
---
π Example Project
$3
Check out the /test directory for complete working examples:
`bash
Clone the repository
git clone https://github.com/devSiddharthKarn/Mango.git
cd Mango
Install dependencies
pnpm install
or: npm install
Configure database
Edit test/test.ts with your database credentials
Run tests
pnpm run test
`
$3
`
Mango/
βββ src/
β βββ mango.ts # Main ORM code
βββ test/
β βββ test.ts # Basic usage examples
β βββ test-operations.ts # Advanced operations
βββ package.json
βββ tsconfig.json
βββ README.md
`
$3
Here's a complete example application:
`typescript
import { Mango } from "mango-orm";
interface User {
id?: number;
username: string;
email: string;
age: number;
created_at?: Date;
}
async function main() {
// 1. Connect
const mango = new Mango();
await mango.connect({
host: "localhost",
user: "root",
password: "password",
database: "myapp"
});
// 2. Create table
const users = await mango.createTable("users", {
id: mango.types().int().autoIncrement().primaryKey(),
username: mango.types().varchar(50).notNull().unique(),
email: mango.types().varchar(100).notNull(),
age: mango.types().int().notNull(),
created_at: mango.types().timeStamp()
});
// 3. Insert sample data
await users.insertOne({
username: "alice",
email: "alice@example.com",
age: 25
}).execute();
// 4. Query with filters
const adults = await users
.selectColumns(["username", "email", "age"])
.where("age", ">=", 18)
.orderBy("age")
.sort(-1)
.limit(10)
.execute();
console.log("Adult users:", adults);
// 5. Update
await users
.update({ age: 26 })
.where("username", "=", "alice")
.execute();
// 6. Delete
await users
.delete()
.where("age", "<", 13)
.execute();
// 7. Cleanup
await mango.disconnect();
}
main().catch(console.error);
`
---
π€ Contributing
We welcome contributions! This project is in active development and there are many ways to help:
$3
- π Report Bugs: Open an issue with reproduction steps
- π‘ Suggest Features: Share your ideas for new features
- π Improve Documentation: Fix typos, add examples, clarify concepts
- π§ Submit Pull Requests: Fix bugs or implement features
- β Star the Project: Show your support on GitHub
- π’ Spread the Word: Share Mango with others
$3
`bash
Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/Mango.git
cd Mango
Install dependencies
pnpm install
Make your changes in src/
Test your changes
pnpm run test
Build TypeScript
pnpm run build
Commit and push
git add .
git commit -m "Description of changes"
git push origin your-branch-name
``