stabilize-orm 1.2.0 → 1.3.2

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,43 +1,44 @@
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, elegant decorator-based models, automatic versioning, and a full-featured command-line interface, Stabilize is built to scale with your app.
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
13
  - **Unified API**: Write once, run on 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.
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
16
  - **Automatic Migrations**: Generate database-specific SQL schemas directly from your model definitions.
17
- - **Versioned Models & Time-Travel**: Add `@Versioned()` to your models for automatic history tables and snapshot queries.
17
+ - **Versioned Models & Time-Travel**: Enable versioning in your model configuration for automatic history tables and snapshot queries.
18
18
  - **Retry Logic**: Automatic exponential backoff for database queries to handle transient connection issues.
19
19
  - **Connection Pooling**: Efficient connection management for PostgreSQL and MySQL.
20
20
  - **Transactional Integrity**: Built-in support for atomic transactions with automatic rollback on failure.
21
21
  - **Advanced Query Builder**: Fluent, chainable API for building complex queries, including joins, filters, ordering, and pagination.
22
- - **Model Relationships**: Use `OneToOne`, `ManyToOne`, `OneToMany`, and `ManyToMany` decorators for relationships.
23
- - **Soft Deletes**: Add `@SoftDelete()` to your model for transparent "deleted" flags and safe row removal.
24
- - **Lifecycle Hooks**: Use the `@Hook()` decorator for model lifecycle events like `beforeCreate`, `afterUpdate`, etc.
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
25
  - **Pluggable Logging**: Includes a robust `ConsoleLogger` with support for file-based, rotating logs.
26
26
  - **Custom Errors**: `StabilizeError` provides clear, consistent error handling.
27
27
  - **Caching Layer**: Optional Redis-backed caching with `cache-aside` and `write-through` strategies.
28
+ - **Custom Query Scopes**: Define reusable query conditions (scopes) in models for simplified, reusable filtering logic.
28
29
 
29
30
  ---
30
31
 
31
32
  ## 📦 Installation
32
33
 
33
- Stabilize ORM requires a modern JavaScript runtime (Bun v1.0+, Node.js v18+, Deno v1.28+).
34
+ Stabilize ORM requires a modern JavaScript runtime (Bun v1.3+).
34
35
 
35
36
  ```bash
36
37
  # Using Bun
37
- bun add stabilize-orm reflect-metadata
38
+ bun add stabilize-orm
38
39
 
39
40
  # Using npm
40
- npm install stabilize-orm reflect-metadata
41
+ npm install stabilize-orm
41
42
  ```
42
43
 
43
44
  ---
@@ -56,14 +57,14 @@ npm install stabilize-orm reflect-metadata
56
57
 
57
58
  ## ⚙️ Configuration
58
59
 
59
- First, create a database configuration file.
60
+ Create a database configuration file.
60
61
 
61
62
  ```typescript
62
63
  // config/database.ts
63
64
  import { DBType, type DBConfig } from "stabilize-orm";
64
65
 
65
66
  const dbConfig: DBConfig = {
66
- type: DBType.Postgres,
67
+ type: DBType.Postgres,
67
68
  connectionString: process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb",
68
69
  retryAttempts: 3,
69
70
  retryDelay: 1000,
@@ -72,11 +73,10 @@ const dbConfig: DBConfig = {
72
73
  export default dbConfig;
73
74
  ```
74
75
 
75
- Next, create a central ORM instance that your application can use. Import `reflect-metadata` once at your application's entry point.
76
+ Next, create a central ORM instance for your application.
76
77
 
77
78
  ```typescript
78
79
  // db.ts
79
- import 'reflect-metadata';
80
80
  import { Stabilize, type CacheConfig, type LoggerConfig, LogLevel } from "stabilize-orm";
81
81
  import dbConfig from "./database";
82
82
 
@@ -88,7 +88,7 @@ const cacheConfig: CacheConfig = {
88
88
 
89
89
  const loggerConfig: LoggerConfig = {
90
90
  level: LogLevel.Info,
91
- filePath: 'logs/stabilize.log',
91
+ filePath: "logs/stabilize.log",
92
92
  maxFileSize: 5 * 1024 * 1024, // 5MB
93
93
  maxFiles: 3,
94
94
  };
@@ -100,80 +100,95 @@ export const orm = new Stabilize(dbConfig, cacheConfig, loggerConfig);
100
100
 
101
101
  ## 🏗️ Models & Relationships
102
102
 
103
- Define your tables as classes using decorators. The `@Column` decorator uses the `DataTypes` enum for a truly database-agnostic schema.
103
+ Define your tables as classes using the `defineModel` function. The `DataTypes` enum ensures database-agnostic schemas.
104
104
 
105
105
  ### Example: Users and Roles (Many-to-Many) with Versioning
106
106
 
107
107
  ```typescript
108
108
  // models/User.ts
109
- import 'reflect-metadata';
110
- import { Model, Column, DataTypes, Required, Unique, OneToMany, Versioned } from 'stabilize-orm';
111
- import { UserRole } from './UserRole';
112
-
113
- @Model('users')
114
- @Versioned() // Enables history table for time-travel/audit
115
- export class User {
116
- @Column({ type: DataTypes.INTEGER, name: 'id' })
117
- id!: number;
118
-
119
- @Column({ type: DataTypes.STRING, length: 100 })
120
- @Required() @Unique()
121
- email!: string;
122
-
123
- @OneToMany(() => UserRole, 'user')
124
- roles?: UserRole[];
125
- }
109
+ import { defineModel, DataTypes, RelationType } from "stabilize-orm";
110
+ import { UserRole } from "./UserRole";
111
+
112
+ const User = defineModel({
113
+ tableName: "users",
114
+ versioned: true,
115
+ columns: {
116
+ id: { type: DataTypes.Integer, required: true },
117
+ email: { type: DataTypes.String, length: 100, required: true, unique: true },
118
+ },
119
+ relations: [
120
+ {
121
+ type: RelationType.OneToMany,
122
+ target: () => UserRole,
123
+ property: "roles",
124
+ foreignKey: "userId",
125
+ },
126
+ ],
127
+ hooks: {
128
+ beforeCreate: (entity) => console.log(`Creating user: ${entity.email}`),
129
+ },
130
+ });
131
+
132
+ // Add a hook as a class method
133
+ User.prototype.afterCreate = async function () {
134
+ console.log(`Created user with ID: ${this.id}`);
135
+ };
136
+
137
+ export { User };
126
138
  ```
127
139
 
128
140
  ```typescript
129
141
  // models/Role.ts
130
- import 'reflect-metadata';
131
- import { Model, Column, DataTypes, Required, Unique } from 'stabilize-orm';
132
-
133
- @Model('roles')
134
- export class Role {
135
- @Column({ type: DataTypes.INTEGER, name: 'id' })
136
- id!: number;
142
+ import { defineModel, DataTypes } from "stabilize-orm";
143
+
144
+ const Role = defineModel({
145
+ tableName: "roles",
146
+ columns: {
147
+ id: { type: DataTypes.Integer, required: true },
148
+ name: { type: DataTypes.String, length: 50, required: true, unique: true },
149
+ },
150
+ });
137
151
 
138
- @Column({ type: DataTypes.STRING, length: 50 })
139
- @Required() @Unique()
140
- name!: string;
141
- }
152
+ export { Role };
142
153
  ```
143
154
 
144
155
  ```typescript
145
156
  // models/UserRole.ts
146
- import 'reflect-metadata';
147
- import { Model, Column, DataTypes, Required, ManyToOne, Index } from 'stabilize-orm';
148
- import { User } from './User';
149
- import { Role } from './Role';
150
-
151
- @Model('user_roles')
152
- export class UserRole {
153
- @Column({ type: DataTypes.INTEGER, name: 'id' })
154
- id!: number;
155
-
156
- @Column({ type: DataTypes.INTEGER, name: 'user_id' })
157
- @Required() @Index()
158
- userId!: number;
159
-
160
- @Column({ type: DataTypes.INTEGER, name: 'role_id' })
161
- @Required() @Index()
162
- roleId!: number;
163
-
164
- @ManyToOne(() => User, 'userId')
165
- user?: User;
157
+ import { defineModel, DataTypes, RelationType } from "stabilize-orm";
158
+ import { User } from "./User";
159
+ import { Role } from "./Role";
160
+
161
+ const UserRole = defineModel({
162
+ tableName: "user_roles",
163
+ columns: {
164
+ id: { type: DataTypes.Integer, required: true },
165
+ userId: { type: DataTypes.Integer, required: true, index: "idx_user_id" },
166
+ roleId: { type: DataTypes.Integer, required: true, index: "idx_role_id" },
167
+ },
168
+ relations: [
169
+ {
170
+ type: RelationType.ManyToOne,
171
+ target: () => User,
172
+ property: "user",
173
+ foreignKey: "userId",
174
+ },
175
+ {
176
+ type: RelationType.ManyToOne,
177
+ target: () => Role,
178
+ property: "role",
179
+ foreignKey: "roleId",
180
+ },
181
+ ],
182
+ });
166
183
 
167
- @ManyToOne(() => Role, 'roleId')
168
- role?: Role;
169
- }
184
+ export { UserRole };
170
185
  ```
171
186
 
172
187
  ---
173
188
 
174
189
  ## ⏳ Versioning & Auditing
175
190
 
176
- Enable automatic history tracking and time-travel queries by adding `@Versioned()` to your model.
191
+ Enable automatic history tracking and time-travel queries by setting `versioned: true` in your model configuration.
177
192
 
178
193
  - Each change is recorded in a `<table>_history` table with version, operation, and audit columns.
179
194
  - Supports snapshot queries, rollbacks, audits, and time-travel.
@@ -181,15 +196,16 @@ Enable automatic history tracking and time-travel queries by adding `@Versioned(
181
196
  ### **Versioning Example**
182
197
 
183
198
  ```typescript
184
- @Model('users')
185
- @Versioned()
186
- export class User {
187
- @Column({ type: DataTypes.INTEGER, name: 'id' })
188
- id!: number;
189
-
190
- @Column({ type: DataTypes.STRING, length: 100 })
191
- name!: string;
192
- }
199
+ import { defineModel, DataTypes } from "stabilize-orm";
200
+
201
+ const User = defineModel({
202
+ tableName: "users",
203
+ versioned: true,
204
+ columns: {
205
+ id: { type: DataTypes.Integer, required: true },
206
+ name: { type: DataTypes.String, length: 100 },
207
+ },
208
+ });
193
209
 
194
210
  // --- Using versioning features:
195
211
 
@@ -199,7 +215,7 @@ const userRepository = orm.getRepository(User);
199
215
  await userRepository.rollback(1, 3); // roll back user with id=1 to version 3
200
216
 
201
217
  // Get a snapshot as of a specific date
202
- const userAsOf = await userRepository.asOf(1, new Date('2025-01-01T00:00:00Z'));
218
+ const userAsOf = await userRepository.asOf(1, new Date("2025-01-01T00:00:00Z"));
203
219
  console.log(userAsOf);
204
220
 
205
221
  // View full version history
@@ -211,95 +227,92 @@ console.log(history);
211
227
 
212
228
  ## 🔄 Model Lifecycle Hooks
213
229
 
214
- Stabilize ORM supports lifecycle hooks via the `@Hook()` decorator.
215
- You can run logic before/after create, update, delete, or save.
230
+ 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.
216
231
 
217
232
  ### **Hooks Example**
218
233
 
219
234
  ```typescript
220
- import { Model, Column, DataTypes, Hook } from 'stabilize-orm';
221
-
222
- @Model('users')
223
- export class User {
224
- @Column({ type: DataTypes.INTEGER, name: 'id' })
225
- id!: number;
226
-
227
- @Column({ type: DataTypes.STRING, length: 100 })
228
- name!: string;
229
-
230
- @Column({ type: DataTypes.DATETIME, name: 'created_at' })
231
- createdAt!: Date;
232
-
233
- @Column({ type: DataTypes.DATETIME, name: 'updated_at' })
234
- updatedAt!: Date;
235
-
236
- @Hook('beforeCreate')
237
- setCreatedAt() {
238
- this.createdAt = new Date();
239
- }
235
+ import { defineModel, DataTypes } from "stabilize-orm";
236
+
237
+ const User = defineModel({
238
+ tableName: "users",
239
+ columns: {
240
+ id: { type: DataTypes.Integer, required: true },
241
+ name: { type: DataTypes.String, length: 100 },
242
+ createdAt: { type: DataTypes.DateTime },
243
+ updatedAt: { type: DataTypes.DateTime },
244
+ },
245
+ hooks: {
246
+ beforeCreate: (entity) => {
247
+ entity.createdAt = new Date();
248
+ },
249
+ beforeUpdate: (entity) => {
250
+ entity.updatedAt = new Date();
251
+ },
252
+ afterCreate: (entity) => {
253
+ console.log(`User created: ${entity.name}`);
254
+ },
255
+ },
256
+ });
240
257
 
241
- @Hook('beforeUpdate')
242
- setUpdatedAt() {
243
- this.updatedAt = new Date();
244
- }
258
+ // Add a hook as a class method
259
+ User.prototype.afterUpdate = async function () {
260
+ console.log(`Updated user: ${this.name}`);
261
+ };
245
262
 
246
- @Hook('afterCreate')
247
- logCreate() {
248
- console.log(`User created: ${this.name}`);
249
- }
250
- }
263
+ export { User };
251
264
  ```
252
265
 
253
- You can use `@Hook` with: `'beforeCreate'`, `'afterCreate'`, `'beforeUpdate'`, `'afterUpdate'`, `'beforeDelete'`, `'afterDelete'`, `'beforeSave'`, `'afterSave'`.
266
+ Supported hooks: `beforeCreate`, `afterCreate`, `beforeUpdate`, `afterUpdate`, `beforeDelete`, `afterDelete`, `beforeSave`, `afterSave`.
254
267
 
255
268
  ---
256
269
 
257
270
  ## 💻 Command-Line Interface (CLI)
258
271
 
259
- Stabilize includes a powerful CLI for managing your workflow.
272
+ Stabilize includes a powerful CLI for managing your workflow. See: [stabilize-cli on GitHub](https://github.com/ElectronSz/stabilize-cli)
260
273
 
261
274
  ### Generating Files
262
275
 
263
276
  - **Generate a model**:
264
277
  ```bash
265
- bun run stabilize-cli generate model Product
278
+ stabilize-cli generate model Product
266
279
  ```
267
280
 
268
281
  - **Generate a migration from a model**:
269
282
  ```bash
270
- bun run stabilize-cli generate migration User
283
+ stabilize-cli generate migration User
271
284
  ```
272
285
 
273
286
  - **Generate a seed file**:
274
287
  ```bash
275
- bun run stabilize-cli generate seed InitialRoles
288
+ stabilize-cli generate seed InitialRoles
276
289
  ```
277
290
 
278
291
  ### Database & Migration Management
279
292
 
280
293
  - **Run all pending migrations**:
281
294
  ```bash
282
- bun run stabilize-cli migrate
295
+ stabilize-cli migrate
283
296
  ```
284
297
 
285
298
  - **Roll back the last migration**:
286
299
  ```bash
287
- bun run stabilize-cli migrate:rollback
300
+ stabilize-cli migrate:rollback
288
301
  ```
289
302
 
290
303
  - **Run all pending seeds (in dependency order)**:
291
304
  ```bash
292
- bun run stabilize-cli seed
305
+ stabilize-cli seed
293
306
  ```
294
307
 
295
308
  - **Check the status of migrations and seeds**:
296
309
  ```bash
297
- bun run stabilize-cli status
310
+ stabilize-cli status
298
311
  ```
299
312
 
300
313
  - **Reset the database (drop, migrate, seed)**:
301
314
  ```bash
302
- bun run stabilize-cli db:reset
315
+ stabilize-cli db:reset
303
316
  ```
304
317
 
305
318
  ---
@@ -309,21 +322,22 @@ Stabilize includes a powerful CLI for managing your workflow.
309
322
  ### Basic CRUD with Repositories
310
323
 
311
324
  ```typescript
312
- import { orm } from './db';
313
- import { User } from 'models/User';
325
+ import { orm } from "./db";
326
+ import { User } from "./models/User";
314
327
 
315
328
  const userRepository = orm.getRepository(User);
316
329
 
317
- const newUser = await userRepository.create({ email: 'lwazicd@icloud.com' });
330
+ const newUser = await userRepository.create({ email: "lwazicd@icloud.com" });
318
331
  const foundUser = await userRepository.findOne(newUser.id);
319
- const updatedUser = await userRepository.update(newUser.id, { email: 'admin@offbytesecure.com' });
332
+ const updatedUser = await userRepository.update(newUser.id, { email: "admin@offbytesecure.com" });
320
333
  await userRepository.delete(newUser.id);
321
334
  ```
322
335
 
323
336
  ### Advanced Queries with the Query Builder
324
337
 
325
338
  ```typescript
326
- const activeAdmins = await orm.getRepository(UserRole)
339
+ const activeAdmins = await orm
340
+ .getRepository(UserRole)
327
341
  .find()
328
342
  .join("users", "user_roles.user_id = users.id")
329
343
  .join("roles", "user_roles.role_id = roles.id")
@@ -345,18 +359,85 @@ console.log(activeAdmins);
345
359
  orderBy(clause: string): QueryBuilder<User>;
346
360
  limit(limit: number): QueryBuilder<User>;
347
361
  offset(offset: number): QueryBuilder<User>;
362
+ scope(name: string, ...args: any[]): QueryBuilder<User>;
348
363
  build(): { query: string; params: any[] };
349
364
  execute(client?: DBClient, cache?: Cache, cacheKey?: string): Promise<User[]>;
350
365
  }
351
366
  ```
352
367
 
368
+ ### Custom Query Scopes
369
+
370
+ Define reusable query conditions (scopes) in your model configuration to simplify and reuse common filtering logic. Scopes are applied via the `scope` method on `Repository` or `QueryBuilder`, allowing you to chain them with other query operations.
371
+
372
+ #### **Scopes Example**
373
+
374
+ ```typescript
375
+ import { defineModel, DataTypes } from "stabilize-orm";
376
+ import { orm } from "./db";
377
+
378
+ const User = defineModel({
379
+ tableName: "users",
380
+ columns: {
381
+ id: { type: DataTypes.Integer, required: true },
382
+ email: { type: DataTypes.String, length: 100, required: true },
383
+ isActive: { type: DataTypes.Boolean, required: true },
384
+ createdAt: { type: DataTypes.DateTime },
385
+ },
386
+ scopes: {
387
+ active: (qb) => qb.where("isActive = ?", true),
388
+ recent: (qb, days: number) => qb.where("createdAt >= ?", new Date(Date.now() - days * 24 * 60 * 60 * 1000)),
389
+ },
390
+ });
391
+
392
+ const userRepository = orm.getRepository(User);
393
+
394
+ // Fetch active users
395
+ const activeUsers = await userRepository.scope("active").execute();
396
+
397
+ // Fetch users created in the last 7 days
398
+ const recentUsers = await userRepository.scope("recent", 7).execute();
399
+
400
+ // Combine scopes with other query operations
401
+ const recentActiveUsers = await userRepository
402
+ .scope("active")
403
+ .scope("recent", 7)
404
+ .orderBy("createdAt DESC")
405
+ .limit(10)
406
+ .execute();
407
+
408
+ console.log(recentActiveUsers);
409
+ ```
410
+
353
411
  ---
354
412
 
355
413
  ## 🗑️ Soft Deletes
356
414
 
357
- Add `@SoftDelete()` to a model property to enable transparent soft deletes (e.g., `deleted_at` timestamp).
358
- - Use `repository.softDelete(id)` to mark an entity as deleted.
359
- - Use `find({ includeDeleted: true })` to include soft-deleted rows.
415
+ Enable soft deletes by setting `softDelete: true` and marking a column (e.g., `deletedAt`) with `softDelete: true` in the model configuration.
416
+
417
+ - Use `repository.delete(id)` to mark an entity as deleted.
418
+ - Use `repository.recover(id)` to restore a soft-deleted entity.
419
+ - Queries automatically exclude soft-deleted rows unless specified otherwise.
420
+
421
+ ### **Soft Delete Example**
422
+
423
+ ```typescript
424
+ import { defineModel, DataTypes } from "stabilize-orm";
425
+
426
+ const User = defineModel({
427
+ tableName: "users",
428
+ softDelete: true,
429
+ columns: {
430
+ id: { type: DataTypes.Integer, required: true },
431
+ email: { type: DataTypes.String, length: 100, required: true },
432
+ deletedAt: { type: DataTypes.DateTime, softDelete: true },
433
+ },
434
+ });
435
+
436
+ const userRepository = orm.getRepository(User);
437
+ await userRepository.create({ email: "lwazicd@icloud.com" });
438
+ await userRepository.delete(1); // Soft delete
439
+ await userRepository.recover(1); // Recover
440
+ ```
360
441
 
361
442
  ---
362
443
 
@@ -416,6 +497,6 @@ Licensed under the MIT License. See [LICENSE.md](./LICENSE.md) for details.
416
497
 
417
498
  Created with ❤️ by **ElectronSz**
418
499
  <br/>
419
- <em>File last updated: 2025-10-16 19:41:00 UTC</em>
500
+ <em>File last updated: 2025-10-19 10:24:00 SAST</em>
420
501
 
421
502
  </div>