stabilize-orm 1.1.8 → 1.3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ - Further features and improvements coming soon.
8
+
9
+ ## [1.3.0] - 2025-10-18
10
+
11
+ ### Added
12
+ - Introduced programmatic `defineModel` API for model definitions, eliminating the need for decorators (`model.ts`).
13
+ - Added `MetadataStorage` class to manage model configurations without `reflect-metadata`.
14
+ - Added support for defining lifecycle hooks in `ModelConfig` or as class methods (`hooks.ts`).
15
+ - Added `example.ts` to demonstrate the new programmatic API usage.
16
+ - Extended `ModelConfig` interface to support columns, relations, hooks, versioning, and soft deletes (`types.ts`).
17
+
18
+ ### Changed
19
+ - Replaced decorator-based model definitions with `defineModel` API, removing dependency on `reflect-metadata` and TypeScript experimental features (`experimentalDecorators`, `emitDecoratorMetadata`).
20
+ - Updated `stabilize.ts` to export `defineModel` and remove `reflect-metadata` import.
21
+ - Modified `repository.ts` to use `MetadataStorage` for table names, columns, relations, validators, and soft delete fields.
22
+ - Rewrote `hooks.ts` to support hooks via `ModelConfig` and class methods, integrated with `MetadataStorage`.
23
+ - Updated `migrations.ts` to generate schemas using `MetadataStorage` instead of decorator metadata.
24
+ - Revised `types.ts` to remove decorator-related types and add `ModelConfig`, `ColumnConfig`, and `RelationConfig` interfaces.
25
+ - Updated `README.md` to reflect the new programmatic API, remove decorator references, and update examples.
26
+ - Ensured compatibility with `verbatimModuleSyntax` by using `export type` for type exports in `stabilize.ts`.
27
+
28
+ ### Removed
29
+ - Deleted `decorators.ts` as decorators are no longer used.
30
+ - Removed dependency on `reflect-metadata` from the project.
31
+
32
+ ### Fixed
33
+ - Fixed TypeScript type errors in `repository.ts` for `columns` and `relations` by mapping `MetadataStorage` outputs to match expected types.
34
+ - Corrected `runHooks` in `repository.ts` to call `hook.callback(entity)` instead of `hook()`.
35
+ - Fixed TypeScript `verbatimModuleSyntax` error in `stabilize.ts` by separating type and value exports.
36
+
5
37
  ## [1.1.2] - 2025-10-14
6
38
 
7
39
  ### Added
@@ -16,8 +48,4 @@ All notable changes to this project will be documented in this file.
16
48
  - Enhanced documentation for open source best practices.
17
49
 
18
50
  ### Fixed
19
- - Various bug fixes for connection handling and retry logic.
20
-
21
- ## [Unreleased]
22
-
23
- - Further features and improvements coming soon.
51
+ - Various bug fixes for connection handling and retry logic.
package/README.md CHANGED
@@ -1,72 +1,70 @@
1
1
  # Stabilize ORM
2
2
 
3
- _A Modern, Type-Safe, and Expressive ORM for Bun, Node.js, and Deno_
3
+ _A Modern, Type-Safe, and Expressive ORM for Bun_
4
4
 
5
5
  ---
6
6
 
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.
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, programmatic model definitions, automatic versioning, and a full-featured command-line interface, Stabilize is built to scale with your app.
8
8
 
9
9
  ---
10
10
 
11
11
  ## 🚀 Features
12
12
 
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.
13
+ - **Unified API**: Write once, run on PostgreSQL, MySQL, or SQLite.
14
+ - **Programmatic Model Definitions**: Define models and columns using the `defineModel` API with the `DataTypes` enum for database-agnostic schemas.
15
+ - **Full-Featured CLI**: Generate models, manage migrations, seed data, and reset your database from the command line with [stabilize-cli](https://github.com/ElectronSz/stabilize-cli).
16
+ - **Automatic Migrations**: Generate database-specific SQL schemas directly from your model definitions.
17
+ - **Versioned Models & Time-Travel**: Enable versioning in your model configuration for automatic history tables and snapshot queries.
18
+ - **Retry Logic**: Automatic exponential backoff for database queries to handle transient connection issues.
19
+ - **Connection Pooling**: Efficient connection management for PostgreSQL and MySQL.
20
+ - **Transactional Integrity**: Built-in support for atomic transactions with automatic rollback on failure.
21
+ - **Advanced Query Builder**: Fluent, chainable API for building complex queries, including joins, filters, ordering, and pagination.
22
+ - **Model Relationships**: Define `OneToOne`, `ManyToOne`, `OneToMany`, and `ManyToMany` relationships in the model configuration.
23
+ - **Soft Deletes**: Enable soft deletes in the model configuration for transparent "deleted" flags and safe row removal.
24
+ - **Lifecycle Hooks**: Define hooks in the model configuration or as class methods for lifecycle events like `beforeCreate`, `afterUpdate`, etc.
25
+ - **Pluggable Logging**: Includes a robust `ConsoleLogger` with support for file-based, rotating logs.
26
+ - **Custom Errors**: `StabilizeError` provides clear, consistent error handling.
27
+ - **Caching Layer**: Optional Redis-backed caching with `cache-aside` and `write-through` strategies.
25
28
 
26
29
  ---
27
30
 
28
31
  ## 📦 Installation
29
32
 
30
- Stabilize ORM requires a modern JavaScript runtime (Bun v1.0+, Node.js v18+, Deno v1.28+).
33
+ Stabilize ORM requires a modern JavaScript runtime (Bun v1.3+).
31
34
 
32
35
  ```bash
33
36
  # Using Bun
34
- bun add stabilize-orm reflect-metadata
37
+ bun add stabilize-orm
35
38
 
36
39
  # Using npm
37
- npm install stabilize-orm reflect-metadata
40
+ npm install stabilize-orm
38
41
  ```
39
42
 
40
43
  ---
41
44
 
42
45
  ## 📃 Documentation & Community
43
46
 
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)
47
+ - [Changelog](./CHANGELOG.md)
48
+ - [License](./LICENSE.md)
49
+ - [Code of Conduct](./CODE_OF_CONDUCT.md)
50
+ - [Contributing Guide](./CONTRIBUTING.md)
51
+ - [Security Policy](./SECURITY.md)
52
+ - [Support](./SUPPORT.md)
53
+ - [Funding](./FUNDING.md)
51
54
 
52
55
  ---
53
56
 
54
57
  ## ⚙️ Configuration
55
58
 
56
- First, create a database configuration file.
59
+ Create a database configuration file.
57
60
 
58
61
  ```typescript
59
62
  // config/database.ts
60
63
  import { DBType, type DBConfig } from "stabilize-orm";
61
64
 
62
65
  const dbConfig: DBConfig = {
63
- // Choose your database type
64
- type: DBType.Postgres,
65
-
66
- // Connection string for your database
66
+ type: DBType.Postgres,
67
67
  connectionString: process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
68
-
69
- // Optional: Connection retry settings
70
68
  retryAttempts: 3,
71
69
  retryDelay: 1000,
72
70
  };
@@ -74,29 +72,26 @@ const dbConfig: DBConfig = {
74
72
  export default dbConfig;
75
73
  ```
76
74
 
77
- Next, create a central ORM instance that your application can use. Remember to import `reflect-metadata` once at your application's entry point.
75
+ Next, create a central ORM instance for your application.
78
76
 
79
77
  ```typescript
80
78
  // db.ts
81
- import 'reflect-metadata'; // declared first
82
79
  import { Stabilize, type CacheConfig, type LoggerConfig, LogLevel } from "stabilize-orm";
83
80
  import dbConfig from "./database";
84
81
 
85
82
  const cacheConfig: CacheConfig = {
86
83
  enabled: process.env.CACHE_ENABLED === "true",
87
84
  redisUrl: process.env.REDIS_URL,
88
- ttl: 60, // Default TTL in seconds
85
+ ttl: 60,
89
86
  };
90
87
 
91
88
  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
- }
89
+ level: LogLevel.Info,
90
+ filePath: "logs/stabilize.log",
91
+ maxFileSize: 5 * 1024 * 1024, // 5MB
92
+ maxFiles: 3,
93
+ };
98
94
 
99
- // Create and export the ORM instance
100
95
  export const orm = new Stabilize(dbConfig, cacheConfig, loggerConfig);
101
96
  ```
102
97
 
@@ -104,127 +99,219 @@ export const orm = new Stabilize(dbConfig, cacheConfig, loggerConfig);
104
99
 
105
100
  ## 🏗️ Models & Relationships
106
101
 
107
- Define your database tables as classes using decorators. The new `@Column` decorator uses the `DataTypes` enum for a truly database-agnostic schema definition.
102
+ Define your tables as classes using the `defineModel` function. The `DataTypes` enum ensures database-agnostic schemas.
108
103
 
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.
104
+ ### Example: Users and Roles (Many-to-Many) with Versioning
112
105
 
113
106
  ```typescript
114
107
  // models/User.ts
115
- import 'reflect-metadata';
116
- import { Model, Column, DataTypes, Required, Unique, OneToMany } from 'stabilize-orm';
117
- import { UserRole } from './UserRole';
118
-
119
- @Model('users')
120
- export class User {
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[];
131
- }
108
+ import { defineModel, DataTypes, RelationType } from "stabilize-orm";
109
+ import { UserRole } from "./UserRole";
110
+
111
+ const User = defineModel({
112
+ tableName: "users",
113
+ versioned: true,
114
+ columns: {
115
+ id: { type: DataTypes.Integer, required: true },
116
+ email: { type: DataTypes.String, length: 100, required: true, unique: true },
117
+ },
118
+ relations: [
119
+ {
120
+ type: RelationType.OneToMany,
121
+ target: () => UserRole,
122
+ property: "roles",
123
+ foreignKey: "userId",
124
+ },
125
+ ],
126
+ hooks: {
127
+ beforeCreate: (entity) => console.log(`Creating user: ${entity.email}`),
128
+ },
129
+ });
130
+
131
+ // Add a hook as a class method
132
+ User.prototype.afterCreate = async function () {
133
+ console.log(`Created user with ID: ${this.id}`);
134
+ };
135
+
136
+ export { User };
132
137
  ```
133
138
 
134
139
  ```typescript
135
140
  // models/Role.ts
136
- import 'reflect-metadata';
137
- import { Model, Column, DataTypes, Required, Unique } from 'stabilize-orm';
138
-
139
- @Model('roles')
140
- export class Role {
141
- @Column({ type: DataTypes.INTEGER, name: 'id' })
142
- id!: number;
141
+ import { defineModel, DataTypes } from "stabilize-orm";
142
+
143
+ const Role = defineModel({
144
+ tableName: "roles",
145
+ columns: {
146
+ id: { type: DataTypes.Integer, required: true },
147
+ name: { type: DataTypes.String, length: 50, required: true, unique: true },
148
+ },
149
+ });
143
150
 
144
- @Column({ type: DataTypes.STRING, length: 50 })
145
- @Required() @Unique()
146
- name!: string;
147
- }
151
+ export { Role };
148
152
  ```
149
153
 
150
154
  ```typescript
151
155
  // models/UserRole.ts
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';
156
-
157
- @Model('user_roles') // The join table
158
- export class UserRole {
159
- @Column({ type: DataTypes.INTEGER, name: 'id' })
160
- id!: number;
161
-
162
- @Column({ type: DataTypes.INTEGER, name: 'user_id' })
163
- @Required() @Index()
164
- userId!: number;
165
-
166
- @Column({ type: DataTypes.INTEGER, name: 'role_id' })
167
- @Required() @Index()
168
- roleId!: number;
169
-
170
- // Define the "many" sides of the relationship
171
- @ManyToOne(() => User, 'userId')
172
- user?: User;
173
-
174
- @ManyToOne(() => Role, 'roleId')
175
- role?: Role;
176
- }
156
+ import { defineModel, DataTypes, RelationType } from "stabilize-orm";
157
+ import { User } from "./User";
158
+ import { Role } from "./Role";
159
+
160
+ const UserRole = defineModel({
161
+ tableName: "user_roles",
162
+ columns: {
163
+ id: { type: DataTypes.Integer, required: true },
164
+ userId: { type: DataTypes.Integer, required: true, index: "idx_user_id" },
165
+ roleId: { type: DataTypes.Integer, required: true, index: "idx_role_id" },
166
+ },
167
+ relations: [
168
+ {
169
+ type: RelationType.ManyToOne,
170
+ target: () => User,
171
+ property: "user",
172
+ foreignKey: "userId",
173
+ },
174
+ {
175
+ type: RelationType.ManyToOne,
176
+ target: () => Role,
177
+ property: "role",
178
+ foreignKey: "roleId",
179
+ },
180
+ ],
181
+ });
182
+
183
+ export { UserRole };
184
+ ```
185
+
186
+ ---
187
+
188
+ ## ⏳ Versioning & Auditing
189
+
190
+ Enable automatic history tracking and time-travel queries by setting `versioned: true` in your model configuration.
191
+
192
+ - Each change is recorded in a `<table>_history` table with version, operation, and audit columns.
193
+ - Supports snapshot queries, rollbacks, audits, and time-travel.
194
+
195
+ ### **Versioning Example**
196
+
197
+ ```typescript
198
+ import { defineModel, DataTypes } from "stabilize-orm";
199
+
200
+ const User = defineModel({
201
+ tableName: "users",
202
+ versioned: true,
203
+ columns: {
204
+ id: { type: DataTypes.Integer, required: true },
205
+ name: { type: DataTypes.String, length: 100 },
206
+ },
207
+ });
208
+
209
+ // --- Using versioning features:
210
+
211
+ const userRepository = orm.getRepository(User);
212
+
213
+ // Rollback to a previous version
214
+ await userRepository.rollback(1, 3); // roll back user with id=1 to version 3
215
+
216
+ // Get a snapshot as of a specific date
217
+ const userAsOf = await userRepository.asOf(1, new Date("2025-01-01T00:00:00Z"));
218
+ console.log(userAsOf);
219
+
220
+ // View full version history
221
+ const history = await userRepository.history(1);
222
+ console.log(history);
177
223
  ```
178
224
 
179
225
  ---
180
226
 
227
+ ## 🔄 Model Lifecycle Hooks
228
+
229
+ Stabilize ORM supports lifecycle hooks defined in the model configuration or as class methods. You can run logic before/after create, update, delete, or save.
230
+
231
+ ### **Hooks Example**
232
+
233
+ ```typescript
234
+ import { defineModel, DataTypes } from "stabilize-orm";
235
+
236
+ const User = defineModel({
237
+ tableName: "users",
238
+ columns: {
239
+ id: { type: DataTypes.Integer, required: true },
240
+ name: { type: DataTypes.String, length: 100 },
241
+ createdAt: { type: DataTypes.DateTime },
242
+ updatedAt: { type: DataTypes.DateTime },
243
+ },
244
+ hooks: {
245
+ beforeCreate: (entity) => {
246
+ entity.createdAt = new Date();
247
+ },
248
+ beforeUpdate: (entity) => {
249
+ entity.updatedAt = new Date();
250
+ },
251
+ afterCreate: (entity) => {
252
+ console.log(`User created: ${entity.name}`);
253
+ },
254
+ },
255
+ });
256
+
257
+ // Add a hook as a class method
258
+ User.prototype.afterUpdate = async function () {
259
+ console.log(`Updated user: ${this.name}`);
260
+ };
261
+
262
+ export { User };
263
+ ```
264
+
265
+ Supported hooks: `beforeCreate`, `afterCreate`, `beforeUpdate`, `afterUpdate`, `beforeDelete`, `afterDelete`, `beforeSave`, `afterSave`.
266
+
267
+ ---
268
+
181
269
  ## 💻 Command-Line Interface (CLI)
182
270
 
183
- Stabilize includes a powerful CLI for managing your development workflow.
271
+ Stabilize includes a powerful CLI for managing your workflow. See: [stabilize-cli on GitHub](https://github.com/ElectronSz/stabilize-cli)
184
272
 
185
273
  ### Generating Files
186
274
 
187
- - **Generate a model**:
275
+ - **Generate a model**:
188
276
  ```bash
189
- bun run stabilize-cli generate model Product
277
+ stabilize-cli generate model Product
190
278
  ```
191
279
 
192
- - **Generate a migration from a model**:
280
+ - **Generate a migration from a model**:
193
281
  ```bash
194
- # Reads models/User.ts and creates a new migration file
195
- bun run stabilize-cli generate migration User
282
+ stabilize-cli generate migration User
196
283
  ```
197
284
 
198
- - **Generate a seed file**:
285
+ - **Generate a seed file**:
199
286
  ```bash
200
- bun run stabilize-cli generate seed InitialRoles
287
+ stabilize-cli generate seed InitialRoles
201
288
  ```
202
289
 
203
290
  ### Database & Migration Management
204
291
 
205
- - **Run all pending migrations**:
292
+ - **Run all pending migrations**:
206
293
  ```bash
207
- bun run stabilize-cli migrate
294
+ stabilize-cli migrate
208
295
  ```
209
296
 
210
- - **Roll back the last migration**:
297
+ - **Roll back the last migration**:
211
298
  ```bash
212
- bun run stabilize-cli migrate:rollback
299
+ stabilize-cli migrate:rollback
213
300
  ```
214
301
 
215
- - **Run all pending seeds** (in dependency order):
302
+ - **Run all pending seeds (in dependency order)**:
216
303
  ```bash
217
- bun run stabilize-cli seed
304
+ stabilize-cli seed
218
305
  ```
219
306
 
220
- - **Check the status of all migrations and seeds**:
307
+ - **Check the status of migrations and seeds**:
221
308
  ```bash
222
- bun run stabilize-cli status
309
+ stabilize-cli status
223
310
  ```
224
311
 
225
- - **Reset the database (drop, migrate, seed)**:
312
+ - **Reset the database (drop, migrate, seed)**:
226
313
  ```bash
227
- bun run stabilize-cli db:reset
314
+ stabilize-cli db:reset
228
315
  ```
229
316
 
230
317
  ---
@@ -233,34 +320,24 @@ Stabilize includes a powerful CLI for managing your development workflow.
233
320
 
234
321
  ### Basic CRUD with Repositories
235
322
 
236
- Interact with your data using the `Repository` pattern.
237
-
238
323
  ```typescript
239
- import { orm } from './db';
240
- import { User } from 'models/User';
324
+ import { orm } from "./db";
325
+ import { User } from "./models/User";
241
326
 
242
327
  const userRepository = orm.getRepository(User);
243
328
 
244
- // Create a new user
245
- const newUser = await userRepository.create({ email: 'lwazicd@icloud.com' });
246
-
247
- // Find a user by ID
329
+ const newUser = await userRepository.create({ email: "lwazicd@icloud.com" });
248
330
  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
331
+ const updatedUser = await userRepository.update(newUser.id, { email: "admin@offbytesecure.com" });
254
332
  await userRepository.delete(newUser.id);
255
333
  ```
256
334
 
257
335
  ### Advanced Queries with the Query Builder
258
336
 
259
- For complex queries, use the fluent `find()` method, which returns a chainable `QueryBuilder`.
260
-
261
337
  ```typescript
262
- const activeAdmins = await orm.getRepository(UserRole)
263
- .find() // Start a query on the user_roles table
338
+ const activeAdmins = await orm
339
+ .getRepository(UserRole)
340
+ .find()
264
341
  .join("users", "user_roles.user_id = users.id")
265
342
  .join("roles", "user_roles.role_id = roles.id")
266
343
  .select("users.id", "users.email", "roles.name as role_name")
@@ -269,13 +346,9 @@ const activeAdmins = await orm.getRepository(UserRole)
269
346
  .execute();
270
347
 
271
348
  console.log(activeAdmins);
272
- // [ { id: 1, email: 'lwazicd@icloud.com', role_name: 'Admin' } ]
273
349
  ```
274
350
 
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
- ---
278
- **API:**
351
+ #### Query Builder API
279
352
 
280
353
  ```typescript
281
354
  {
@@ -286,16 +359,48 @@ The `execute()` method on a repository query does not require a client to be pas
286
359
  limit(limit: number): QueryBuilder<User>;
287
360
  offset(offset: number): QueryBuilder<User>;
288
361
  build(): { query: string; params: any[] };
289
- execute(client: DBClient, cache?: Cache, cacheKey?: string): Promise<User[]>;
362
+ execute(client?: DBClient, cache?: Cache, cacheKey?: string): Promise<User[]>;
290
363
  }
291
364
  ```
292
365
 
366
+ ---
367
+
368
+ ## 🗑️ Soft Deletes
369
+
370
+ Enable soft deletes by setting `softDelete: true` and marking a column (e.g., `deletedAt`) with `softDelete: true` in the model configuration.
371
+
372
+ - Use `repository.delete(id)` to mark an entity as deleted.
373
+ - Use `repository.recover(id)` to restore a soft-deleted entity.
374
+ - Queries automatically exclude soft-deleted rows unless specified otherwise.
375
+
376
+ ### **Soft Delete Example**
377
+
378
+ ```typescript
379
+ import { defineModel, DataTypes } from "stabilize-orm";
380
+
381
+ const User = defineModel({
382
+ tableName: "users",
383
+ softDelete: true,
384
+ columns: {
385
+ id: { type: DataTypes.Integer, required: true },
386
+ email: { type: DataTypes.String, length: 100, required: true },
387
+ deletedAt: { type: DataTypes.DateTime, softDelete: true },
388
+ },
389
+ });
390
+
391
+ const userRepository = orm.getRepository(User);
392
+ await userRepository.create({ email: "lwazicd@icloud.com" });
393
+ await userRepository.delete(1); // Soft delete
394
+ await userRepository.recover(1); // Recover
395
+ ```
396
+
397
+ ---
398
+
293
399
  ## 🌐 Express.js Integration
294
400
 
295
401
  Stabilize ORM works seamlessly with web frameworks like Express.
296
402
 
297
403
  ```typescript
298
- // src/server.ts
299
404
  import express from "express";
300
405
  import { orm } from "./db";
301
406
  import { User } from "./models/User";
@@ -305,7 +410,6 @@ app.use(express.json());
305
410
 
306
411
  const userRepository = orm.getRepository(User);
307
412
 
308
- // Get all users
309
413
  app.get("/users", async (req, res) => {
310
414
  try {
311
415
  const users = await userRepository.find().execute();
@@ -315,7 +419,6 @@ app.get("/users", async (req, res) => {
315
419
  }
316
420
  });
317
421
 
318
- // Create a new user
319
422
  app.post("/users", async (req, res) => {
320
423
  try {
321
424
  const user = await userRepository.create(req.body);
@@ -332,6 +435,13 @@ app.listen(3000, () => {
332
435
 
333
436
  ---
334
437
 
438
+ ## 🧑‍🔬 Testing & Time-Travel
439
+
440
+ - Use time-travel queries to inspect historical entity states.
441
+ - Assert audit trails and rollback operations in your tests.
442
+
443
+ ---
444
+
335
445
  ## 📑 License
336
446
 
337
447
  Licensed under the MIT License. See [LICENSE.md](./LICENSE.md) for details.
@@ -342,6 +452,6 @@ Licensed under the MIT License. See [LICENSE.md](./LICENSE.md) for details.
342
452
 
343
453
  Created with ❤️ by **ElectronSz**
344
454
  <br/>
345
- *File last updated: 2025-10-15 19:32:00 UTC*
455
+ <em>File last updated: 2025-10-18 22:10:00 SAST</em>
346
456
 
347
457
  </div>
package/bun.lock CHANGED
@@ -7,7 +7,6 @@
7
7
  "@types/pg": "^8.15.5",
8
8
  "@types/uuid": "^11.0.0",
9
9
  "commander": "^12.1.0",
10
- "dotenv": "^17.2.3",
11
10
  "figlet": "^1.9.3",
12
11
  "glob": "^11.0.0",
13
12
  "ioredis": "^5.4.1",
@@ -307,8 +306,6 @@
307
306
 
308
307
  "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
309
308
 
310
- "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
311
-
312
309
  "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
313
310
 
314
311
  "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],