stabilize-orm 1.1.2 → 1.1.4
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/README.md +209 -155
- package/bun.lock +61 -0
- package/cache.ts +88 -17
- package/cli/stabilize-cli.ts +300 -432
- package/client.ts +153 -170
- package/decorators.ts +70 -4
- package/dist/cli/stabilize-cli.js +3093 -86
- package/dist/index.js +3023 -26
- package/index.ts +90 -51
- package/logger.ts +76 -75
- package/migrations.ts +160 -57
- package/package.json +5 -2
- package/query-builder.ts +103 -13
- package/repository.ts +447 -287
- package/types.ts +58 -29
package/README.md
CHANGED
|
@@ -1,203 +1,280 @@
|
|
|
1
1
|
# Stabilize ORM
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
_A Modern, Type-Safe, and Expressive ORM for Bun, Node.js, and Deno_
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
**Stabilize** is a lightweight,
|
|
7
|
+
**Stabilize** is a lightweight, feature-rich ORM designed for performance and developer experience. It provides a unified, database-agnostic API for **PostgreSQL**, **MySQL**, and **SQLite**, powered by a robust query builder, an elegant decorator-based model system, and a full-featured command-line interface.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## 🚀 Features
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
13
|
+
- **Unified API**: Write once, run against PostgreSQL, MySQL, or SQLite.
|
|
14
|
+
- **Type-Safe Decorators**: Define models and columns with the powerful `DataTypes` enum for true database-agnostic schemas.
|
|
15
|
+
- **Full-Featured CLI**: Generate models, manage migrations, seed data, and reset your database from the command line.
|
|
16
|
+
- **Automatic Migrations**: Generate database-specific SQL schemas directly from your model definitions.
|
|
17
|
+
- **Retry Logic**: Automatic exponential backoff for database queries to handle transient connection issues.
|
|
18
|
+
- **Connection Pooling**: Efficient connection management for PostgreSQL and MySQL out of the box.
|
|
19
|
+
- **Transactional Integrity**: Built-in support for atomic transactions with automatic rollback on failure.
|
|
20
|
+
- **Advanced Query Builder**: A fluent, chainable API for building complex queries with joins, where clauses, ordering, and pagination.
|
|
21
|
+
- **Model Relationships**: Define `OneToOne`, `ManyToOne`, `OneToMany`, and `ManyToMany` relationships with simple decorators.
|
|
22
|
+
- **Pluggable Logging**: Includes a robust `ConsoleLogger` with support for file-based, rotating logs.
|
|
23
|
+
- **Custom Errors**: `StabilizeError` provides clear, consistent error handling.
|
|
24
|
+
- **Caching Layer**: Optional Redis-backed caching with `cache-aside` and `write-through` strategies.
|
|
23
25
|
|
|
24
26
|
---
|
|
25
27
|
|
|
26
28
|
## 📦 Installation
|
|
27
29
|
|
|
28
|
-
Stabilize requires Bun
|
|
30
|
+
Stabilize ORM requires a modern JavaScript runtime (Bun v1.0+, Node.js v18+, Deno v1.28+).
|
|
29
31
|
|
|
30
32
|
```bash
|
|
31
|
-
|
|
33
|
+
# Using Bun
|
|
34
|
+
bun add stabilize-orm reflect-metadata
|
|
35
|
+
|
|
36
|
+
# Using npm
|
|
37
|
+
npm install stabilize-orm reflect-metadata
|
|
32
38
|
```
|
|
33
39
|
|
|
34
40
|
---
|
|
35
41
|
|
|
36
42
|
## 📃 Documentation & Community
|
|
37
43
|
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
44
|
+
- [Changelog](./CHANGELOG.md)
|
|
45
|
+
- [License](./LICENSE.md)
|
|
46
|
+
- [Code of Conduct](./CODE_OF_CONDUCT.md)
|
|
47
|
+
- [Contributing Guide](./CONTRIBUTING.md)
|
|
48
|
+
- [Security Policy](./SECURITY.md)
|
|
49
|
+
- [Support](./SUPPORT.md)
|
|
50
|
+
- [Funding](./FUNDING.md)
|
|
45
51
|
|
|
46
52
|
---
|
|
47
53
|
|
|
48
|
-
## ⚙️
|
|
54
|
+
## ⚙️ Configuration
|
|
55
|
+
|
|
56
|
+
First, create a database configuration file.
|
|
49
57
|
|
|
50
58
|
```typescript
|
|
51
59
|
// config/database.ts
|
|
52
|
-
import { DBType,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
import { DBType, type DBConfig } from "stabilize-orm";
|
|
61
|
+
|
|
62
|
+
const dbConfig: DBConfig = {
|
|
63
|
+
// Choose your database type
|
|
64
|
+
type: DBType.Postgres,
|
|
65
|
+
|
|
66
|
+
// Connection string for your database
|
|
67
|
+
connectionString: process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
|
|
68
|
+
|
|
69
|
+
// Optional: Connection retry settings
|
|
70
|
+
retryAttempts: 3,
|
|
71
|
+
retryDelay: 1000,
|
|
61
72
|
};
|
|
62
73
|
|
|
63
|
-
export
|
|
74
|
+
export default dbConfig;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Next, create a central ORM instance that your application can use. Remember to import `reflect-metadata` once at your application's entry point.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// db.ts
|
|
81
|
+
import 'reflect-metadata'; // declared first
|
|
82
|
+
import { Stabilize, type CacheConfig, type LoggerConfig, LogLevel } from "stabilize-orm";
|
|
83
|
+
import dbConfig from "./database";
|
|
84
|
+
|
|
85
|
+
const cacheConfig: CacheConfig = {
|
|
64
86
|
enabled: process.env.CACHE_ENABLED === "true",
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
redisUrl: process.env.REDIS_URL,
|
|
88
|
+
ttl: 60, // Default TTL in seconds
|
|
67
89
|
};
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
const loggerConfig: LoggerConfig = {
|
|
92
|
+
level: LogLevel.Info,
|
|
93
|
+
// Optional: Configure file logging
|
|
94
|
+
filePath: 'logs/stabilize.log',
|
|
95
|
+
maxFileSize: 5 * 1024 * 1024, // 5MB
|
|
96
|
+
maxFiles: 3,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create and export the ORM instance
|
|
100
|
+
export const orm = new Stabilize(dbConfig, cacheConfig, loggerConfig);
|
|
70
101
|
```
|
|
71
102
|
|
|
72
103
|
---
|
|
73
104
|
|
|
74
|
-
## 🏗️ Models
|
|
105
|
+
## 🏗️ Models & Relationships
|
|
75
106
|
|
|
76
|
-
Define
|
|
107
|
+
Define your database tables as classes using decorators. The new `@Column` decorator uses the `DataTypes` enum for a truly database-agnostic schema definition.
|
|
108
|
+
|
|
109
|
+
### Example: Users and Roles (Many-to-Many)
|
|
110
|
+
|
|
111
|
+
Here's how to model a many-to-many relationship between `User` and `Role` through a `UserRole` join table.
|
|
77
112
|
|
|
78
113
|
```typescript
|
|
79
114
|
// models/User.ts
|
|
80
|
-
import
|
|
81
|
-
import { Model, Column, Required } from
|
|
115
|
+
import 'reflect-metadata';
|
|
116
|
+
import { Model, Column, DataTypes, Required, Unique, OneToMany } from 'stabilize-orm';
|
|
117
|
+
import { UserRole } from './UserRole';
|
|
82
118
|
|
|
83
|
-
@Model(
|
|
119
|
+
@Model('users')
|
|
84
120
|
export class User {
|
|
85
|
-
@Column(
|
|
86
|
-
id
|
|
87
|
-
|
|
88
|
-
@Column(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
active?: boolean;
|
|
121
|
+
@Column({ type: DataTypes.INTEGER, name: 'id' })
|
|
122
|
+
id!: number;
|
|
123
|
+
|
|
124
|
+
@Column({ type: DataTypes.STRING, length: 100 })
|
|
125
|
+
@Required() @Unique()
|
|
126
|
+
email!: string;
|
|
127
|
+
|
|
128
|
+
// This side of the relationship is for querying convenience
|
|
129
|
+
@OneToMany(() => UserRole, 'user')
|
|
130
|
+
roles?: UserRole[];
|
|
96
131
|
}
|
|
97
132
|
```
|
|
98
133
|
|
|
99
134
|
```typescript
|
|
100
135
|
// models/Role.ts
|
|
101
|
-
import
|
|
102
|
-
import { Model, Column, Required } from
|
|
136
|
+
import 'reflect-metadata';
|
|
137
|
+
import { Model, Column, DataTypes, Required, Unique } from 'stabilize-orm';
|
|
103
138
|
|
|
104
|
-
@Model(
|
|
139
|
+
@Model('roles')
|
|
105
140
|
export class Role {
|
|
106
|
-
@Column(
|
|
107
|
-
id
|
|
141
|
+
@Column({ type: DataTypes.INTEGER, name: 'id' })
|
|
142
|
+
id!: number;
|
|
108
143
|
|
|
109
|
-
@Column(
|
|
110
|
-
|
|
144
|
+
@Column({ type: DataTypes.STRING, length: 50 })
|
|
145
|
+
@Required() @Unique()
|
|
146
|
+
name!: string;
|
|
111
147
|
}
|
|
112
148
|
```
|
|
113
149
|
|
|
114
150
|
```typescript
|
|
115
151
|
// models/UserRole.ts
|
|
116
|
-
import
|
|
117
|
-
import { Model, Column, Required, ManyToOne } from
|
|
118
|
-
import { User } from
|
|
119
|
-
import { Role } from
|
|
152
|
+
import 'reflect-metadata';
|
|
153
|
+
import { Model, Column, DataTypes, Required, ManyToOne, Index } from 'stabilize-orm';
|
|
154
|
+
import { User } from './User';
|
|
155
|
+
import { Role } from './Role';
|
|
120
156
|
|
|
121
|
-
@Model(
|
|
157
|
+
@Model('user_roles') // The join table
|
|
122
158
|
export class UserRole {
|
|
123
|
-
@Column(
|
|
124
|
-
id
|
|
159
|
+
@Column({ type: DataTypes.INTEGER, name: 'id' })
|
|
160
|
+
id!: number;
|
|
125
161
|
|
|
126
|
-
@Column(
|
|
127
|
-
|
|
162
|
+
@Column({ type: DataTypes.INTEGER, name: 'user_id' })
|
|
163
|
+
@Required() @Index()
|
|
164
|
+
userId!: number;
|
|
128
165
|
|
|
129
|
-
@Column(
|
|
130
|
-
|
|
166
|
+
@Column({ type: DataTypes.INTEGER, name: 'role_id' })
|
|
167
|
+
@Required() @Index()
|
|
168
|
+
roleId!: number;
|
|
131
169
|
|
|
132
|
-
//
|
|
133
|
-
@ManyToOne(() => User,
|
|
170
|
+
// Define the "many" sides of the relationship
|
|
171
|
+
@ManyToOne(() => User, 'userId')
|
|
134
172
|
user?: User;
|
|
135
173
|
|
|
136
|
-
@ManyToOne(() => Role,
|
|
174
|
+
@ManyToOne(() => Role, 'roleId')
|
|
137
175
|
role?: Role;
|
|
138
176
|
}
|
|
139
177
|
```
|
|
140
178
|
|
|
141
|
-
```typescript
|
|
142
|
-
// repository/userRepository.ts
|
|
143
|
-
import { orm } from "../config/database";
|
|
144
|
-
import { User } from "../models/User";
|
|
145
|
-
export const userRepository = orm.getRepository(User);
|
|
146
|
-
```
|
|
147
|
-
|
|
148
179
|
---
|
|
149
180
|
|
|
150
|
-
##
|
|
181
|
+
## 💻 Command-Line Interface (CLI)
|
|
151
182
|
|
|
152
|
-
Stabilize
|
|
183
|
+
Stabilize includes a powerful CLI for managing your development workflow.
|
|
153
184
|
|
|
154
|
-
|
|
185
|
+
### Generating Files
|
|
155
186
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
@ManyToOne(() => User, "user_id")
|
|
161
|
-
user?: User;
|
|
162
|
-
@ManyToOne(() => Role, "role_id")
|
|
163
|
-
role?: Role;
|
|
164
|
-
}
|
|
165
|
-
```
|
|
187
|
+
- **Generate a model**:
|
|
188
|
+
```bash
|
|
189
|
+
bun run stabilize-cli generate model Product
|
|
190
|
+
```
|
|
166
191
|
|
|
167
|
-
**
|
|
192
|
+
- **Generate a migration from a model**:
|
|
193
|
+
```bash
|
|
194
|
+
# Reads models/User.ts and creates a new migration file
|
|
195
|
+
bun run stabilize-cli generate migration User
|
|
196
|
+
```
|
|
168
197
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
- **Generate a seed file**:
|
|
199
|
+
```bash
|
|
200
|
+
bun run stabilize-cli generate seed InitialRoles
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Database & Migration Management
|
|
204
|
+
|
|
205
|
+
- **Run all pending migrations**:
|
|
206
|
+
```bash
|
|
207
|
+
bun run stabilize-cli migrate
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- **Roll back the last migration**:
|
|
211
|
+
```bash
|
|
212
|
+
bun run stabilize-cli migrate:rollback
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- **Run all pending seeds** (in dependency order):
|
|
216
|
+
```bash
|
|
217
|
+
bun run stabilize-cli seed
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- **Check the status of all migrations and seeds**:
|
|
221
|
+
```bash
|
|
222
|
+
bun run stabilize-cli status
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- **Reset the database (drop, migrate, seed)**:
|
|
226
|
+
```bash
|
|
227
|
+
bun run stabilize-cli db:reset
|
|
228
|
+
```
|
|
180
229
|
|
|
181
230
|
---
|
|
182
231
|
|
|
183
|
-
## 🧑💻
|
|
232
|
+
## 🧑💻 Querying Data
|
|
184
233
|
|
|
185
|
-
|
|
234
|
+
### Basic CRUD with Repositories
|
|
235
|
+
|
|
236
|
+
Interact with your data using the `Repository` pattern.
|
|
186
237
|
|
|
187
238
|
```typescript
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
239
|
+
import { orm } from './db';
|
|
240
|
+
import { User } from 'models/User';
|
|
241
|
+
|
|
242
|
+
const userRepository = orm.getRepository(User);
|
|
243
|
+
|
|
244
|
+
// Create a new user
|
|
245
|
+
const newUser = await userRepository.create({ email: 'lwazicd@icloud.com' });
|
|
246
|
+
|
|
247
|
+
// Find a user by ID
|
|
248
|
+
const foundUser = await userRepository.findOne(newUser.id);
|
|
249
|
+
|
|
250
|
+
// Update a user
|
|
251
|
+
const updatedUser = await userRepository.update(newUser.id, { email: 'admin@offbytesecure.com' });
|
|
252
|
+
|
|
253
|
+
// Delete a user
|
|
254
|
+
await userRepository.delete(newUser.id);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Advanced Queries with the Query Builder
|
|
194
258
|
|
|
195
|
-
|
|
196
|
-
console.log(query, params);
|
|
259
|
+
For complex queries, use the fluent `find()` method, which returns a chainable `QueryBuilder`.
|
|
197
260
|
|
|
198
|
-
|
|
261
|
+
```typescript
|
|
262
|
+
const activeAdmins = await orm.getRepository(UserRole)
|
|
263
|
+
.find() // Start a query on the user_roles table
|
|
264
|
+
.join("users", "user_roles.user_id = users.id")
|
|
265
|
+
.join("roles", "user_roles.role_id = roles.id")
|
|
266
|
+
.select("users.id", "users.email", "roles.name as role_name")
|
|
267
|
+
.where("roles.name = ?", "Admin")
|
|
268
|
+
.orderBy("users.email ASC")
|
|
269
|
+
.execute();
|
|
270
|
+
|
|
271
|
+
console.log(activeAdmins);
|
|
272
|
+
// [ { id: 1, email: 'lwazicd@icloud.com', role_name: 'Admin' } ]
|
|
199
273
|
```
|
|
200
274
|
|
|
275
|
+
The `execute()` method on a repository query does not require a client to be passed; it uses the repository's default client automatically.
|
|
276
|
+
|
|
277
|
+
---
|
|
201
278
|
**API:**
|
|
202
279
|
|
|
203
280
|
```typescript
|
|
@@ -213,81 +290,58 @@ const users = await qb.execute(orm["client"]);
|
|
|
213
290
|
}
|
|
214
291
|
```
|
|
215
292
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
## 📚 More Usage Examples
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
// Get all users
|
|
222
|
-
export const getAll = async () => userRepository.find().execute(orm["client"]);
|
|
223
|
-
|
|
224
|
-
// Get active users, ordered by name
|
|
225
|
-
export const getActiveUsers = async () =>
|
|
226
|
-
userRepository.find().where("active = ?", true).orderBy("name ASC").execute(orm["client"]);
|
|
227
|
-
|
|
228
|
-
// Paginated query
|
|
229
|
-
export const getPaginatedUsers = async (limit: number, offset: number) =>
|
|
230
|
-
userRepository.find().orderBy("created_at DESC").limit(limit).offset(offset).execute(orm["client"]);
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
---
|
|
293
|
+
## 🌐 Express.js Integration
|
|
234
294
|
|
|
235
|
-
|
|
295
|
+
Stabilize ORM works seamlessly with web frameworks like Express.
|
|
236
296
|
|
|
237
297
|
```typescript
|
|
298
|
+
// src/server.ts
|
|
238
299
|
import express from "express";
|
|
239
|
-
import {
|
|
240
|
-
import {
|
|
300
|
+
import { orm } from "./db";
|
|
301
|
+
import { User } from "./models/User";
|
|
241
302
|
|
|
242
303
|
const app = express();
|
|
243
304
|
app.use(express.json());
|
|
244
305
|
|
|
306
|
+
const userRepository = orm.getRepository(User);
|
|
307
|
+
|
|
308
|
+
// Get all users
|
|
245
309
|
app.get("/users", async (req, res) => {
|
|
246
310
|
try {
|
|
247
|
-
const users = await userRepository.find().execute(
|
|
311
|
+
const users = await userRepository.find().execute();
|
|
248
312
|
res.json(users);
|
|
249
|
-
} catch {
|
|
313
|
+
} catch (err) {
|
|
250
314
|
res.status(500).json({ error: "Failed to fetch users." });
|
|
251
315
|
}
|
|
252
316
|
});
|
|
253
317
|
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const users = await userRepository.find()
|
|
257
|
-
.where("active = ?", true)
|
|
258
|
-
.orderBy("name ASC")
|
|
259
|
-
.execute(orm["client"]);
|
|
260
|
-
res.json(users);
|
|
261
|
-
} catch {
|
|
262
|
-
res.status(500).json({ error: "Failed to fetch active users." });
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
318
|
+
// Create a new user
|
|
266
319
|
app.post("/users", async (req, res) => {
|
|
267
320
|
try {
|
|
268
321
|
const user = await userRepository.create(req.body);
|
|
269
322
|
res.status(201).json(user);
|
|
270
|
-
} catch {
|
|
323
|
+
} catch (err) {
|
|
271
324
|
res.status(500).json({ error: "User creation failed." });
|
|
272
325
|
}
|
|
273
326
|
});
|
|
274
327
|
|
|
275
328
|
app.listen(3000, () => {
|
|
276
|
-
console.log("
|
|
329
|
+
console.log("Server listening on port 3000");
|
|
277
330
|
});
|
|
278
331
|
```
|
|
279
332
|
|
|
280
333
|
---
|
|
281
334
|
|
|
282
|
-
|
|
283
335
|
## 📑 License
|
|
284
336
|
|
|
285
|
-
See [LICENSE.md](./LICENSE.md)
|
|
337
|
+
Licensed under the MIT License. See [LICENSE.md](./LICENSE.md) for details.
|
|
286
338
|
|
|
287
339
|
---
|
|
288
340
|
|
|
289
341
|
<div align="center">
|
|
290
342
|
|
|
291
|
-
Created with ❤️
|
|
343
|
+
Created with ❤️ by **ElectronSz**
|
|
344
|
+
<br/>
|
|
345
|
+
*File last updated: 2025-10-15 19:32:00 UTC*
|
|
292
346
|
|
|
293
347
|
</div>
|