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 CHANGED
@@ -1,203 +1,280 @@
1
1
  # Stabilize ORM
2
2
 
3
- _Stable, Fast, and Expressive ORM for Bun_
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, retry-aware ORM built on Bun’s native SQL API. It provides a unified interface for SQLite, MySQL, and PostgreSQL with connection pooling, automatic retries, transactions, savepoints, and robust logging. Designed for simplicity, performance, and reliability in Bun applications.
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
- - **Unified API**: Supports SQLite, MySQL, and PostgreSQL
14
- - **Retry Logic**: Automatic exponential backoff for queries & transactions
15
- - **Connection Management**: Pooling, connection switching, live metrics
16
- - **Transactions & Savepoints**: Built-in support with retry handling
17
- - **Prepared Statements**: Cached for SQLite to maximize performance
18
- - **Pluggable Logging**: Default ConsoleLogger, extensible for files or services
19
- - **Custom Errors**: `StabilizeError` with clear, database-specific codes
20
- - **CLI Tool**: Migrate, seed, and query from the command line
21
- - **Model & Repository Pattern**: Clean, scalable code with decorators
22
- - **Relationships & Joins**: Model relationships and flexible SQL joins
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 (v1.0+).
30
+ Stabilize ORM requires a modern JavaScript runtime (Bun v1.0+, Node.js v18+, Deno v1.28+).
29
31
 
30
32
  ```bash
31
- bun add stabilize-orm
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
- - [Changelog](./CHANGELOG.md)
39
- - [License](./LICENSE.md)
40
- - [Code of Conduct](./CODE_OF_CONDUCT.md)
41
- - [Contributing Guide](./CONTRIBUTING.md)
42
- - [Security Policy](./SECURITY.md)
43
- - [Support](./SUPPORT.md)
44
- - [Funding](./FUNDING.md)
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
- ## ⚙️ ORM Configuration
54
+ ## ⚙️ Configuration
55
+
56
+ First, create a database configuration file.
49
57
 
50
58
  ```typescript
51
59
  // config/database.ts
52
- import { DBType, Stabilize, type CacheConfig, type DBConfig } from "stabilize-orm";
53
-
54
- export const dbConfig: DBConfig = {
55
- type: DBType.Postgres, // or DBType.SQLite, DBType.MySQL
56
- connectionString: process.env.DB_CONNECTION_STRING || "postgres://admin:P@ssw0rd@localhost:5432/db",
57
- poolSize: Number(process.env.DB_POOL_SIZE) || 10,
58
- retryAttempts: Number(process.env.DB_RETRY_ATTEMPTS) || 3,
59
- retryDelay: Number(process.env.DB_RETRY_DELAY) || 1000,
60
- maxJitter: Number(process.env.DB_MAX_JITTER) || 100,
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 const cacheConfig: CacheConfig = {
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
- ttl: Number(process.env.CACHE_TTL) || 60,
66
- redisUrl: process.env.REDIS_URL || "redis://localhost:6379",
87
+ redisUrl: process.env.REDIS_URL,
88
+ ttl: 60, // Default TTL in seconds
67
89
  };
68
90
 
69
- export const orm = new Stabilize(dbConfig, cacheConfig);
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, Relationships & Repositories
105
+ ## 🏗️ Models & Relationships
75
106
 
76
- Define models with decorators, express relationships, and interact using repositories:
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 "reflect-metadata";
81
- import { Model, Column, Required } from "stabilize-orm";
115
+ import 'reflect-metadata';
116
+ import { Model, Column, DataTypes, Required, Unique, OneToMany } from 'stabilize-orm';
117
+ import { UserRole } from './UserRole';
82
118
 
83
- @Model("users")
119
+ @Model('users')
84
120
  export class User {
85
- @Column("id", "TEXT") @Required()
86
- id: string = crypto.randomUUID();
87
-
88
- @Column("name", "TEXT") @Required()
89
- name?: string;
90
-
91
- @Column("email", "TEXT") @Required()
92
- email?: string;
93
-
94
- @Column("active", "BOOLEAN") @Required()
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 "reflect-metadata";
102
- import { Model, Column, Required } from "stabilize-orm";
136
+ import 'reflect-metadata';
137
+ import { Model, Column, DataTypes, Required, Unique } from 'stabilize-orm';
103
138
 
104
- @Model("roles")
139
+ @Model('roles')
105
140
  export class Role {
106
- @Column("id", "TEXT") @Required()
107
- id: string = crypto.randomUUID();
141
+ @Column({ type: DataTypes.INTEGER, name: 'id' })
142
+ id!: number;
108
143
 
109
- @Column("name", "TEXT") @Required()
110
- name?: string;
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 "reflect-metadata";
117
- import { Model, Column, Required, ManyToOne } from "stabilize-orm";
118
- import { User } from "./User";
119
- import { Role } from "./Role";
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("userroles")
157
+ @Model('user_roles') // The join table
122
158
  export class UserRole {
123
- @Column("id", "TEXT") @Required()
124
- id: string = crypto.randomUUID();
159
+ @Column({ type: DataTypes.INTEGER, name: 'id' })
160
+ id!: number;
125
161
 
126
- @Column("user_id", "TEXT") @Required()
127
- user_id!: string;
162
+ @Column({ type: DataTypes.INTEGER, name: 'user_id' })
163
+ @Required() @Index()
164
+ userId!: number;
128
165
 
129
- @Column("role_id", "TEXT") @Required()
130
- role_id!: string;
166
+ @Column({ type: DataTypes.INTEGER, name: 'role_id' })
167
+ @Required() @Index()
168
+ roleId!: number;
131
169
 
132
- // Relationships
133
- @ManyToOne(() => User, "user_id")
170
+ // Define the "many" sides of the relationship
171
+ @ManyToOne(() => User, 'userId')
134
172
  user?: User;
135
173
 
136
- @ManyToOne(() => Role, "role_id")
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
- ## 🔀 Table Joins & Relationships
181
+ ## 💻 Command-Line Interface (CLI)
151
182
 
152
- Stabilize ORM supports relationships and flexible SQL joins to help you write advanced queries.
183
+ Stabilize includes a powerful CLI for managing your development workflow.
153
184
 
154
- **Relationship Decorators Example:**
185
+ ### Generating Files
155
186
 
156
- ```typescript
157
- @Model("user_roles")
158
- export class UserRole {
159
- // ...columns...
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
- **Join Example:**
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
- ```typescript
170
- const adminUsers = await orm.getRepository(UserRole)
171
- .find()
172
- .join("users", "user_roles.user_id = users.id")
173
- .join("roles", "use_rroles.role_id = roles.id")
174
- .select("users.id", "users.name", "roles.name AS role")
175
- .where("roles.name = ?", "Admin")
176
- .orderBy("users.name ASC")
177
- .execute(orm["client"]);
178
- ```
179
- - Use `.join(table, condition)` to add joins, then combine with `.select`, `.where`, `.orderBy`, etc.
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
- ## 🧑‍💻 Query Builder
232
+ ## 🧑‍💻 Querying Data
184
233
 
185
- The repository `.find()` method returns a chainable query builder:
234
+ ### Basic CRUD with Repositories
235
+
236
+ Interact with your data using the `Repository` pattern.
186
237
 
187
238
  ```typescript
188
- const qb = userRepository.find()
189
- .where("active = ?", true)
190
- .orderBy("created_at DESC")
191
- .limit(10)
192
- .offset(20)
193
- .select("id", "name", "email");
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
- const { query, params } = qb.build();
196
- console.log(query, params);
259
+ For complex queries, use the fluent `find()` method, which returns a chainable `QueryBuilder`.
197
260
 
198
- const users = await qb.execute(orm["client"]);
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
- ## 🌐 ExpressJS Integration
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 { userRepository } from "./repository/userRepository";
240
- import { orm } from "./config/database";
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(orm["client"]);
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
- app.get("/users/active", async (req, res) => {
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("Express server listening on port 3000");
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 ❤️ in Eswatini by ElectronSz
343
+ Created with ❤️ by **ElectronSz**
344
+ <br/>
345
+ *File last updated: 2025-10-15 19:32:00 UTC*
292
346
 
293
347
  </div>