mondel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/dist/index.d.mts +888 -0
- package/dist/index.d.ts +888 -0
- package/dist/index.js +947 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +934 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/public/logo.png" alt="Mondel Logo" width="120">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Mondel</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Lightweight TypeScript ORM for MongoDB</strong><br>
|
|
9
|
+
<em>Type-safe. Serverless-ready. Zero magic.</em>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="#installation">Installation</a> •
|
|
14
|
+
<a href="#quick-start">Quick Start</a> •
|
|
15
|
+
<a href="#features">Features</a> •
|
|
16
|
+
<a href="#api-reference">API Reference</a> •
|
|
17
|
+
<a href="#documentation">Documentation</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Overview
|
|
23
|
+
|
|
24
|
+
**Mondel** is a lightweight TypeScript ORM designed for MongoDB, optimized for serverless environments like Cloudflare Workers, Vercel Edge Functions, and AWS Lambda. It provides a **100% type-safe**, intuitive API inspired by Prisma and Drizzle, with minimal bundle size and zero cold-start overhead.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **100% Type-Safe** — Schema names, field names, and return types strictly typed with full inference
|
|
29
|
+
- **Serverless First** — Optimized for Cloudflare Workers & Vercel Edge with minimal bundle size (~27KB)
|
|
30
|
+
- **Zero Magic** — No decorators, no reflection. Just pure TypeScript functions and predictability
|
|
31
|
+
- **MongoDB Native** — Full access to MongoDB driver options (upsert, sessions, transactions)
|
|
32
|
+
- **Zod Integration** — Built-in runtime validation with Zod schemas
|
|
33
|
+
- **Intuitive API** — Prisma-inspired CRUD operations, Drizzle-inspired schema definition
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install mondel mongodb zod
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
yarn add mondel mongodb zod
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add mondel mongodb zod
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Requirements
|
|
50
|
+
|
|
51
|
+
- Node.js 18+
|
|
52
|
+
- MongoDB 6.0+
|
|
53
|
+
- TypeScript 5.0+ (recommended)
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### 1. Define Your Schema
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { defineSchema, s } from "mondel";
|
|
61
|
+
|
|
62
|
+
export const userSchema = defineSchema("users", {
|
|
63
|
+
timestamps: true,
|
|
64
|
+
fields: {
|
|
65
|
+
// _id is implicit - auto-generated by MongoDB and typed as ObjectId
|
|
66
|
+
email: s.string().required().unique().index({ name: "idx_email" }),
|
|
67
|
+
name: s.string(),
|
|
68
|
+
role: s.enum(["ADMIN", "USER", "GUEST"]).default("USER"),
|
|
69
|
+
age: s.number().min(0).max(150),
|
|
70
|
+
isActive: s.boolean().default(true),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Export schemas array for client initialization
|
|
75
|
+
export const schemas = [userSchema] as const;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Create Client (Serverless Mode)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { createClient, type SchemasToClient } from "mondel";
|
|
82
|
+
import { schemas } from "./schemas";
|
|
83
|
+
|
|
84
|
+
// Type for the connected client
|
|
85
|
+
export type DbClient = SchemasToClient<typeof schemas>;
|
|
86
|
+
|
|
87
|
+
// Create serverless client factory (no connection yet)
|
|
88
|
+
const connectDb = createClient({
|
|
89
|
+
serverless: true,
|
|
90
|
+
schemas,
|
|
91
|
+
syncIndexes: false,
|
|
92
|
+
validation: "strict",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Connect when needed (e.g., in request handler)
|
|
96
|
+
export async function getDb(uri: string): Promise<DbClient> {
|
|
97
|
+
return connectDb(uri);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 3. Perform CRUD Operations
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const db = await getDb(env.MONGODB_URI);
|
|
105
|
+
|
|
106
|
+
// Create
|
|
107
|
+
const result = await db.users.create({
|
|
108
|
+
email: "john@example.com",
|
|
109
|
+
name: "John Doe",
|
|
110
|
+
role: "USER",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Read with type-safe field selection
|
|
114
|
+
const found = await db.users.findOne(
|
|
115
|
+
{ email: "john@example.com" },
|
|
116
|
+
{ select: { _id: true, email: true, name: true } }
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Update with MongoDB native options
|
|
120
|
+
await db.users.updateOne(
|
|
121
|
+
{ email: "john@example.com" },
|
|
122
|
+
{ $set: { name: "John Smith" } },
|
|
123
|
+
{ upsert: true }
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Delete
|
|
127
|
+
await db.users.deleteOne({ email: "john@example.com" });
|
|
128
|
+
|
|
129
|
+
// Close connection when done
|
|
130
|
+
await db.close();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Type Safety
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// ✅ Works - 'users' is a registered schema
|
|
137
|
+
db.users.findMany({});
|
|
138
|
+
|
|
139
|
+
// TypeScript error - 'rooms' doesn't exist
|
|
140
|
+
db.rooms.findMany({}); // Property 'rooms' does not exist
|
|
141
|
+
|
|
142
|
+
// TypeScript error - 'invalidField' doesn't exist
|
|
143
|
+
db.users.findMany({}, { select: { invalidField: true } });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Schema Definition
|
|
147
|
+
|
|
148
|
+
Define your schemas with a fluent, type-safe builder:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { defineSchema, s } from "mondel";
|
|
152
|
+
|
|
153
|
+
const productSchema = defineSchema("products", {
|
|
154
|
+
timestamps: true,
|
|
155
|
+
fields: {
|
|
156
|
+
// _id is implicit - auto-generated by MongoDB
|
|
157
|
+
sku: s.string().required().index({ unique: true, name: "idx_sku" }),
|
|
158
|
+
name: s.string().required().index({ type: "text" }),
|
|
159
|
+
price: s.number().required().min(0),
|
|
160
|
+
stock: s.number().default(0),
|
|
161
|
+
category: s.string().required(),
|
|
162
|
+
tags: s.array(s.string()),
|
|
163
|
+
location: s
|
|
164
|
+
.object({
|
|
165
|
+
type: s.literal("Point"),
|
|
166
|
+
coordinates: s.array(s.number()),
|
|
167
|
+
})
|
|
168
|
+
.index({ type: "2dsphere", name: "idx_location" }),
|
|
169
|
+
},
|
|
170
|
+
indexes: [
|
|
171
|
+
{
|
|
172
|
+
fields: { category: 1, price: -1 },
|
|
173
|
+
options: { name: "idx_category_price" },
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Field Types
|
|
180
|
+
|
|
181
|
+
| Type | Builder | Description |
|
|
182
|
+
| -------- | ------------------ | -------------------------------------- |
|
|
183
|
+
| String | `s.string()` | String field with optional validations |
|
|
184
|
+
| Number | `s.number()` | Numeric field with min/max |
|
|
185
|
+
| Boolean | `s.boolean()` | Boolean field |
|
|
186
|
+
| Date | `s.date()` | Date field |
|
|
187
|
+
| ObjectId | `s.objectId()` | MongoDB ObjectId |
|
|
188
|
+
| Array | `s.array(items)` | Array of typed items |
|
|
189
|
+
| Object | `s.object(props)` | Nested object |
|
|
190
|
+
| JSON | `s.json()` | Arbitrary JSON data |
|
|
191
|
+
| Enum | `s.enum([...])` | String enum validation |
|
|
192
|
+
| Literal | `s.literal(value)` | Literal value |
|
|
193
|
+
|
|
194
|
+
### Field Modifiers
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
s.string()
|
|
198
|
+
.required() // Field is mandatory
|
|
199
|
+
.unique() // Unique constraint
|
|
200
|
+
.default("value") // Default value
|
|
201
|
+
.index() // Create index
|
|
202
|
+
.index({ name: "idx" }) // Named index
|
|
203
|
+
.index({ type: "text" }) // Text index
|
|
204
|
+
.min(1)
|
|
205
|
+
.max(100) // Length constraints (string/number)
|
|
206
|
+
.email() // Email validation
|
|
207
|
+
.url() // URL validation
|
|
208
|
+
.pattern(/regex/); // Regex pattern
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Index Types
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Simple index
|
|
215
|
+
email: s.string().index()
|
|
216
|
+
|
|
217
|
+
// Named index
|
|
218
|
+
email: s.string().index({ name: "idx_email" })
|
|
219
|
+
|
|
220
|
+
// Unique index
|
|
221
|
+
email: s.string().index({ unique: true })
|
|
222
|
+
|
|
223
|
+
// Text index (full-text search)
|
|
224
|
+
description: s.string().index({ type: "text" })
|
|
225
|
+
|
|
226
|
+
// Geospatial index
|
|
227
|
+
location: s.object({...}).index({ type: "2dsphere" })
|
|
228
|
+
|
|
229
|
+
// TTL index
|
|
230
|
+
expiresAt: s.date().index({ expireAfterSeconds: 3600 })
|
|
231
|
+
|
|
232
|
+
// Compound indexes (schema level)
|
|
233
|
+
indexes: [
|
|
234
|
+
{ fields: { category: 1, price: -1 }, options: { name: "idx_cat_price" } }
|
|
235
|
+
]
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Multiple Connections
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// Create separate clients for different databases
|
|
242
|
+
const mainDb = await createClient({
|
|
243
|
+
uri: process.env.MAIN_DB_URI,
|
|
244
|
+
schemas: [userSchema, orderSchema] as const,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const analyticsDb = await createClient({
|
|
248
|
+
uri: process.env.ANALYTICS_DB_URI,
|
|
249
|
+
schemas: [eventSchema, metricSchema] as const,
|
|
250
|
+
syncIndexes: false, // Don't sync indexes on replica
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Use them independently
|
|
254
|
+
const users = await mainDb.users.findMany({});
|
|
255
|
+
const events = await analyticsDb.events.findMany({});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Automatic Validation
|
|
259
|
+
|
|
260
|
+
Validation happens **automatically inside CRUD methods** - you don't need to call it manually:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
const db = await createClient({
|
|
264
|
+
uri: "mongodb://localhost:27017/mydb",
|
|
265
|
+
schemas: [userSchema] as const,
|
|
266
|
+
validation: "strict", // "strict" | "loose" | "off"
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// TypeScript catches type errors at compile time
|
|
270
|
+
await db.users.create({
|
|
271
|
+
email: "test@example.com",
|
|
272
|
+
name: 123, // ❌ TypeScript error: expected string
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Runtime validation catches invalid data
|
|
276
|
+
await db.users.create({
|
|
277
|
+
email: "invalid-email", // ❌ Runtime error: invalid email format
|
|
278
|
+
name: "Test",
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Validation modes:**
|
|
283
|
+
|
|
284
|
+
- `"strict"` (default): Throws error on validation failure
|
|
285
|
+
- `"loose"`: Logs warning but continues
|
|
286
|
+
- `"off"`: No runtime validation (TypeScript still enforces types)
|
|
287
|
+
|
|
288
|
+
### Transactions
|
|
289
|
+
|
|
290
|
+
Full support for MongoDB transactions via `db.startSession()`:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
const db = await createClient({
|
|
294
|
+
uri: process.env.MONGODB_URI,
|
|
295
|
+
schemas: [userSchema, orderSchema] as const,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const session = db.startSession();
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
await session.withTransaction(async () => {
|
|
302
|
+
const user = await db.users.create({ email: "john@example.com", balance: 100 }, { session });
|
|
303
|
+
|
|
304
|
+
await db.orders.create({ userId: user.insertedId, amount: 50 }, { session });
|
|
305
|
+
|
|
306
|
+
await db.users.updateById(user.insertedId, { $inc: { balance: -50 } }, { session });
|
|
307
|
+
});
|
|
308
|
+
} finally {
|
|
309
|
+
await session.endSession();
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## API Reference
|
|
314
|
+
|
|
315
|
+
### `defineSchema(name, definition)`
|
|
316
|
+
|
|
317
|
+
Creates a schema definition with full type inference.
|
|
318
|
+
|
|
319
|
+
### `createClient(config)`
|
|
320
|
+
|
|
321
|
+
Creates a database client. Supports two modes:
|
|
322
|
+
|
|
323
|
+
**Serverless Mode** (recommended for Cloudflare Workers, Vercel Edge):
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
const connectDb = createClient({
|
|
327
|
+
serverless: true,
|
|
328
|
+
schemas: [userSchema] as const,
|
|
329
|
+
syncIndexes: false,
|
|
330
|
+
validation: "strict",
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Returns a factory function - call with URI to connect
|
|
334
|
+
const db = await connectDb(env.MONGODB_URI);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Node.js Mode** (for traditional servers):
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const db = await createClient({
|
|
341
|
+
uri: process.env.MONGODB_URI,
|
|
342
|
+
schemas: [userSchema] as const,
|
|
343
|
+
syncIndexes: true,
|
|
344
|
+
validation: "strict",
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Collection Methods
|
|
349
|
+
|
|
350
|
+
All methods accept MongoDB native options as the last parameter.
|
|
351
|
+
|
|
352
|
+
| Method | Description |
|
|
353
|
+
| ----------------------------------- | ------------------------- |
|
|
354
|
+
| `findOne(where, options?)` | Find single document |
|
|
355
|
+
| `findMany(where?, options?)` | Find multiple documents |
|
|
356
|
+
| `findById(id)` | Find by ObjectId |
|
|
357
|
+
| `create(data, options?)` | Insert single document |
|
|
358
|
+
| `createMany(data[], options?)` | Insert multiple documents |
|
|
359
|
+
| `updateOne(where, data, options?)` | Update single document |
|
|
360
|
+
| `updateMany(where, data, options?)` | Update multiple documents |
|
|
361
|
+
| `updateById(id, data, options?)` | Update by ObjectId |
|
|
362
|
+
| `deleteOne(where)` | Delete single document |
|
|
363
|
+
| `deleteMany(where)` | Delete multiple documents |
|
|
364
|
+
| `deleteById(id)` | Delete by ObjectId |
|
|
365
|
+
| `count(where?)` | Count documents |
|
|
366
|
+
| `exists(where)` | Check if document exists |
|
|
367
|
+
| `aggregate(pipeline)` | Run aggregation pipeline |
|
|
368
|
+
|
|
369
|
+
## Documentation
|
|
370
|
+
|
|
371
|
+
Full documentation is available at [https://mondel.dev](https://mondel.dev) _(coming soon)_
|
|
372
|
+
|
|
373
|
+
## Contributing
|
|
374
|
+
|
|
375
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
MIT © [Edjo](https://github.com/edjo)
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
<p align="center">
|
|
384
|
+
Made with ❤️ for the MongoDB community
|
|
385
|
+
</p>
|