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 +33 -5
- package/README.md +253 -143
- package/bun.lock +0 -3
- package/client.ts +7 -82
- package/hooks.ts +76 -0
- package/index.ts +8 -36
- package/migrations.ts +64 -74
- package/model.ts +115 -0
- package/package.json +3 -9
- package/repository.ts +330 -47
- package/decorators.ts +0 -165
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
|
|
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
|
|
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
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
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.
|
|
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
|
|
37
|
+
bun add stabilize-orm
|
|
35
38
|
|
|
36
39
|
# Using npm
|
|
37
|
-
npm install stabilize-orm
|
|
40
|
+
npm install stabilize-orm
|
|
38
41
|
```
|
|
39
42
|
|
|
40
43
|
---
|
|
41
44
|
|
|
42
45
|
## 📃 Documentation & Community
|
|
43
46
|
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
85
|
+
ttl: 60,
|
|
89
86
|
};
|
|
90
87
|
|
|
91
88
|
const loggerConfig: LoggerConfig = {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
|
116
|
-
import {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
@Required() @Unique()
|
|
146
|
-
name!: string;
|
|
147
|
-
}
|
|
151
|
+
export { Role };
|
|
148
152
|
```
|
|
149
153
|
|
|
150
154
|
```typescript
|
|
151
155
|
// models/UserRole.ts
|
|
152
|
-
import
|
|
153
|
-
import {
|
|
154
|
-
import {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
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
|
-
-
|
|
275
|
+
- **Generate a model**:
|
|
188
276
|
```bash
|
|
189
|
-
|
|
277
|
+
stabilize-cli generate model Product
|
|
190
278
|
```
|
|
191
279
|
|
|
192
|
-
-
|
|
280
|
+
- **Generate a migration from a model**:
|
|
193
281
|
```bash
|
|
194
|
-
|
|
195
|
-
bun run stabilize-cli generate migration User
|
|
282
|
+
stabilize-cli generate migration User
|
|
196
283
|
```
|
|
197
284
|
|
|
198
|
-
-
|
|
285
|
+
- **Generate a seed file**:
|
|
199
286
|
```bash
|
|
200
|
-
|
|
287
|
+
stabilize-cli generate seed InitialRoles
|
|
201
288
|
```
|
|
202
289
|
|
|
203
290
|
### Database & Migration Management
|
|
204
291
|
|
|
205
|
-
-
|
|
292
|
+
- **Run all pending migrations**:
|
|
206
293
|
```bash
|
|
207
|
-
|
|
294
|
+
stabilize-cli migrate
|
|
208
295
|
```
|
|
209
296
|
|
|
210
|
-
-
|
|
297
|
+
- **Roll back the last migration**:
|
|
211
298
|
```bash
|
|
212
|
-
|
|
299
|
+
stabilize-cli migrate:rollback
|
|
213
300
|
```
|
|
214
301
|
|
|
215
|
-
-
|
|
302
|
+
- **Run all pending seeds (in dependency order)**:
|
|
216
303
|
```bash
|
|
217
|
-
|
|
304
|
+
stabilize-cli seed
|
|
218
305
|
```
|
|
219
306
|
|
|
220
|
-
-
|
|
307
|
+
- **Check the status of migrations and seeds**:
|
|
221
308
|
```bash
|
|
222
|
-
|
|
309
|
+
stabilize-cli status
|
|
223
310
|
```
|
|
224
311
|
|
|
225
|
-
-
|
|
312
|
+
- **Reset the database (drop, migrate, seed)**:
|
|
226
313
|
```bash
|
|
227
|
-
|
|
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
|
|
240
|
-
import { User } from
|
|
324
|
+
import { orm } from "./db";
|
|
325
|
+
import { User } from "./models/User";
|
|
241
326
|
|
|
242
327
|
const userRepository = orm.getRepository(User);
|
|
243
328
|
|
|
244
|
-
|
|
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
|
|
263
|
-
.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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=="],
|