@decaf-ts/db-decorators 0.8.15 → 0.8.17
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 +89 -472
- package/dist/db-decorators.cjs +1 -1
- package/dist/db-decorators.cjs.map +1 -1
- package/dist/db-decorators.js +1 -1
- package/dist/db-decorators.js.map +1 -1
- package/lib/esm/identity/index.d.ts +5 -0
- package/lib/esm/identity/index.js +5 -0
- package/lib/esm/identity/index.js.map +1 -1
- package/lib/esm/index.d.ts +1 -6
- package/lib/esm/index.js +6 -6
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/interfaces/index.d.ts +5 -0
- package/lib/esm/interfaces/index.js +5 -0
- package/lib/esm/interfaces/index.js.map +1 -1
- package/lib/esm/model/index.d.ts +5 -0
- package/lib/esm/model/index.js +5 -0
- package/lib/esm/model/index.js.map +1 -1
- package/lib/esm/operations/Operations.js +1 -5
- package/lib/esm/operations/Operations.js.map +1 -1
- package/lib/esm/operations/OperationsRegistry.d.ts +7 -0
- package/lib/esm/operations/OperationsRegistry.js +18 -3
- package/lib/esm/operations/OperationsRegistry.js.map +1 -1
- package/lib/esm/operations/index.d.ts +5 -0
- package/lib/esm/operations/index.js +5 -0
- package/lib/esm/operations/index.js.map +1 -1
- package/lib/esm/overrides/index.d.ts +5 -0
- package/lib/esm/overrides/index.js +5 -0
- package/lib/esm/overrides/index.js.map +1 -1
- package/lib/esm/overrides/overrides.js +1 -1
- package/lib/esm/overrides/overrides.js.map +1 -1
- package/lib/esm/repository/errors.d.ts +3 -0
- package/lib/esm/repository/errors.js +5 -0
- package/lib/esm/repository/errors.js.map +1 -1
- package/lib/esm/repository/index.d.ts +5 -0
- package/lib/esm/repository/index.js +5 -0
- package/lib/esm/repository/index.js.map +1 -1
- package/lib/identity/index.cjs +5 -0
- package/lib/identity/index.d.ts +5 -0
- package/lib/identity/index.js.map +1 -1
- package/lib/index.cjs +6 -6
- package/lib/index.d.ts +1 -6
- package/lib/index.js.map +1 -1
- package/lib/interfaces/index.cjs +5 -0
- package/lib/interfaces/index.d.ts +5 -0
- package/lib/interfaces/index.js.map +1 -1
- package/lib/model/index.cjs +5 -0
- package/lib/model/index.d.ts +5 -0
- package/lib/model/index.js.map +1 -1
- package/lib/operations/Operations.cjs +1 -5
- package/lib/operations/Operations.js.map +1 -1
- package/lib/operations/OperationsRegistry.cjs +18 -3
- package/lib/operations/OperationsRegistry.d.ts +7 -0
- package/lib/operations/OperationsRegistry.js.map +1 -1
- package/lib/operations/index.cjs +5 -0
- package/lib/operations/index.d.ts +5 -0
- package/lib/operations/index.js.map +1 -1
- package/lib/overrides/index.cjs +5 -0
- package/lib/overrides/index.d.ts +5 -0
- package/lib/overrides/index.js.map +1 -1
- package/lib/overrides/overrides.cjs +1 -1
- package/lib/overrides/overrides.js.map +1 -1
- package/lib/repository/errors.cjs +7 -1
- package/lib/repository/errors.d.ts +3 -0
- package/lib/repository/errors.js.map +1 -1
- package/lib/repository/index.cjs +5 -0
- package/lib/repository/index.d.ts +5 -0
- package/lib/repository/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,14 @@ The db-decorators library provides a comprehensive set of TypeScript decorators
|
|
|
5
5
|
|
|
6
6
|
> Release docs refreshed on 2025-11-26. See [workdocs/reports/RELEASE_NOTES.md](./workdocs/reports/RELEASE_NOTES.md) for ticket summaries.
|
|
7
7
|
|
|
8
|
+
### Core Concepts
|
|
9
|
+
|
|
10
|
+
* **`Repository`**: An abstract base class that implements the repository pattern, providing a consistent interface for CRUD operations.
|
|
11
|
+
* **Operation Hooks**: The `Repository` class provides `Prefix` and `Suffix` methods for each CRUD operation, allowing you to execute custom logic before and after the main operation.
|
|
12
|
+
* **Operation Decorators**: Decorators like `@onCreate`, `@onUpdate`, `@onDelete`, and `@onRead` allow you to attach custom logic to specific repository operations.
|
|
13
|
+
* **`Context`**: A class for passing contextual information through the different layers of your application.
|
|
14
|
+
* **Database-related Decorators**: A set of decorators for handling common database tasks, such as defining primary keys (`@id`), generating values (`@generated`), hashing values (`@hash`), composing values from other properties (`@composed`), and managing version numbers (`@version`).
|
|
15
|
+
|
|
8
16
|

|
|
9
17
|

|
|
10
18
|

|
|
@@ -68,569 +76,178 @@ The db-decorators library is a powerful TypeScript framework for database operat
|
|
|
68
76
|
The library is designed to be extensible and adaptable to different database backends, providing a consistent API regardless of the underlying storage mechanism.
|
|
69
77
|
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
# How to Use
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
- [Installation](./workdocs/tutorials/For%20Developers.md#installation)
|
|
81
|
+
This guide provides examples of how to use the main features of the `@decaf-ts/db-decorators` library.
|
|
75
82
|
|
|
76
|
-
##
|
|
83
|
+
## Repository
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
The `Repository` class is an abstract base class that implements the repository pattern.
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
### Creating a Repository
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
To create a repository, you need to extend the `Repository` class and implement the abstract CRUD methods.
|
|
83
90
|
|
|
84
91
|
```typescript
|
|
85
|
-
import { Model } from
|
|
86
|
-
import {
|
|
87
|
-
import { required, minLength, email as emailValidator } from "@decaf-ts/decorator-validation";
|
|
92
|
+
import { Repository, Model } from '@decaf-ts/db-decorators';
|
|
93
|
+
import { model, id, required } from '@decaf-ts/decorator-validation';
|
|
88
94
|
|
|
95
|
+
@model()
|
|
89
96
|
class User extends Model {
|
|
90
97
|
@id()
|
|
91
98
|
id: string;
|
|
92
99
|
|
|
93
|
-
@required()
|
|
94
|
-
@minLength(3)
|
|
95
|
-
name: string;
|
|
96
|
-
|
|
97
|
-
@required()
|
|
98
|
-
@emailValidator()
|
|
99
|
-
email: string;
|
|
100
|
-
|
|
101
|
-
@required()
|
|
102
|
-
@minLength(8)
|
|
103
|
-
@hash()
|
|
104
|
-
password: string;
|
|
105
|
-
|
|
106
|
-
@version()
|
|
107
|
-
version: number;
|
|
108
|
-
|
|
109
|
-
@transient()
|
|
110
|
-
temporaryData: any;
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
#### Composed Properties
|
|
115
|
-
|
|
116
|
-
Description: Create a Product model with a SKU that is automatically composed from other properties.
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
import { Model } from "@decaf-ts/decorator-validation";
|
|
120
|
-
import { id, composed, composedFromKeys } from "@decaf-ts/db-decorators";
|
|
121
|
-
import { required } from "@decaf-ts/decorator-validation";
|
|
122
|
-
|
|
123
|
-
class Product extends Model {
|
|
124
|
-
@id()
|
|
125
|
-
id: string;
|
|
126
|
-
|
|
127
|
-
@required()
|
|
128
|
-
category: string;
|
|
129
|
-
|
|
130
100
|
@required()
|
|
131
101
|
name: string;
|
|
132
|
-
|
|
133
|
-
@required()
|
|
134
|
-
variant: string;
|
|
135
|
-
|
|
136
|
-
@composed(['category', 'name', 'variant'], '-')
|
|
137
|
-
sku: string;
|
|
138
|
-
|
|
139
|
-
@composedFromKeys(['category', 'name'], '_', true, 'PROD_', '_KEY')
|
|
140
|
-
productKey: string;
|
|
141
102
|
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### 2. Implementing Repositories
|
|
145
|
-
|
|
146
|
-
#### Basic Repository Implementation
|
|
147
|
-
|
|
148
|
-
Description: Create a repository for the User model that implements the required CRUD operations.
|
|
149
103
|
|
|
150
|
-
|
|
151
|
-
import { Repository } from "@decaf-ts/db-decorators";
|
|
152
|
-
import { User } from "./models/User";
|
|
153
|
-
|
|
154
|
-
class UserRepository extends Repository<User> {
|
|
104
|
+
class UserRepository extends Repository<User, any> {
|
|
155
105
|
constructor() {
|
|
156
106
|
super(User);
|
|
157
107
|
}
|
|
158
108
|
|
|
159
|
-
async create(model: User
|
|
160
|
-
// Implementation for creating a user
|
|
161
|
-
console.log(`Creating user: ${model.name}`);
|
|
162
|
-
// Assign an ID if not already present
|
|
163
|
-
if (!model.id) {
|
|
164
|
-
model.id = Date.now().toString();
|
|
165
|
-
}
|
|
109
|
+
async create(model: User): Promise<User> {
|
|
110
|
+
// Implementation for creating a user
|
|
166
111
|
return model;
|
|
167
112
|
}
|
|
168
113
|
|
|
169
|
-
async read(key: string
|
|
170
|
-
// Implementation for reading a user
|
|
171
|
-
|
|
172
|
-
return new User({ id: key, name: "Example User", email: "user@example.com" });
|
|
114
|
+
async read(key: string): Promise<User> {
|
|
115
|
+
// Implementation for reading a user
|
|
116
|
+
return new User({ id: key, name: 'User' });
|
|
173
117
|
}
|
|
174
118
|
|
|
175
|
-
async update(model: User
|
|
176
|
-
// Implementation for updating a user
|
|
177
|
-
console.log(`Updating user: ${model.name}`);
|
|
119
|
+
async update(model: User): Promise<User> {
|
|
120
|
+
// Implementation for updating a user
|
|
178
121
|
return model;
|
|
179
122
|
}
|
|
180
123
|
|
|
181
|
-
async delete(key: string
|
|
182
|
-
// Implementation for deleting a user
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return user;
|
|
124
|
+
async delete(key: string): Promise<User> {
|
|
125
|
+
// Implementation for deleting a user
|
|
126
|
+
const model = await this.read(key);
|
|
127
|
+
return model;
|
|
186
128
|
}
|
|
187
129
|
}
|
|
188
130
|
```
|
|
189
131
|
|
|
190
|
-
|
|
132
|
+
### Operation Hooks
|
|
191
133
|
|
|
192
|
-
|
|
134
|
+
The `Repository` class provides `Prefix` and `Suffix` methods for each CRUD operation, allowing you to execute custom logic before and after the main operation.
|
|
193
135
|
|
|
194
136
|
```typescript
|
|
195
|
-
|
|
196
|
-
|
|
137
|
+
class UserRepository extends Repository<User, any> {
|
|
138
|
+
// ...
|
|
197
139
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
140
|
+
protected async createPrefix(model: User, ...args: any[]): Promise<[User, ...any[], any]> {
|
|
141
|
+
console.log('Before creating user...');
|
|
142
|
+
return [model, ...args];
|
|
201
143
|
}
|
|
202
144
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// Implementation
|
|
145
|
+
protected async createSuffix(model: User, context: any): Promise<User> {
|
|
146
|
+
console.log('After creating user...');
|
|
206
147
|
return model;
|
|
207
148
|
}
|
|
208
|
-
|
|
209
|
-
async read(key: string | number, ...args: any[]): Promise<Product> {
|
|
210
|
-
// Implementation
|
|
211
|
-
return new Product({ id: key });
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async update(model: Product, ...args: any[]): Promise<Product> {
|
|
215
|
-
// Implementation
|
|
216
|
-
return model;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async delete(key: string | number, ...args: any[]): Promise<Product> {
|
|
220
|
-
// Implementation
|
|
221
|
-
return await this.read(key);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Override bulk methods for optimized implementation
|
|
225
|
-
async createAll(models: Product[], ...args: any[]): Promise<Product[]> {
|
|
226
|
-
console.log(`Bulk creating ${models.length} products`);
|
|
227
|
-
// Custom implementation for bulk creation
|
|
228
|
-
return models.map(model => {
|
|
229
|
-
if (!model.id) {
|
|
230
|
-
model.id = Date.now().toString();
|
|
231
|
-
}
|
|
232
|
-
return model;
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async readAll(keys: string[] | number[], ...args: any[]): Promise<Product[]> {
|
|
237
|
-
console.log(`Bulk reading ${keys.length} products`);
|
|
238
|
-
// Custom implementation for bulk reading
|
|
239
|
-
return keys.map(key => new Product({ id: key }));
|
|
240
|
-
}
|
|
241
149
|
}
|
|
242
150
|
```
|
|
243
151
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
#### Property Transformation Hooks
|
|
152
|
+
## Operation Decorators
|
|
247
153
|
|
|
248
|
-
|
|
154
|
+
Decorators like `@onCreate`, `@onUpdate`, `@onDelete`, and `@onRead` allow you to attach custom logic to specific repository operations.
|
|
249
155
|
|
|
250
156
|
```typescript
|
|
251
|
-
import {
|
|
252
|
-
import { id, onCreate, onUpdate, onCreateUpdate } from "@decaf-ts/db-decorators";
|
|
253
|
-
import { required } from "@decaf-ts/decorator-validation";
|
|
254
|
-
|
|
255
|
-
// Handler function for setting creation timestamp
|
|
256
|
-
function setCreationTimestamp(repo, context, data, key, model) {
|
|
257
|
-
model[key] = new Date().toISOString();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Handler function for setting update timestamp
|
|
261
|
-
function setUpdateTimestamp(repo, context, data, key, model) {
|
|
262
|
-
model[key] = new Date().toISOString();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Handler function for normalizing email
|
|
266
|
-
function normalizeEmail(repo, context, data, key, model) {
|
|
267
|
-
if (model[key]) {
|
|
268
|
-
model[key] = model[key].toLowerCase().trim();
|
|
269
|
-
}
|
|
270
|
-
}
|
|
157
|
+
import { onCreate, onUpdate } from '@decaf-ts/db-decorators';
|
|
271
158
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
@required()
|
|
277
|
-
name: string;
|
|
278
|
-
|
|
279
|
-
@required()
|
|
280
|
-
@onCreateUpdate(normalizeEmail)
|
|
281
|
-
email: string;
|
|
282
|
-
|
|
283
|
-
@onCreate(setCreationTimestamp)
|
|
284
|
-
createdAt: string;
|
|
159
|
+
const logOnCreate = onCreate((context, data, key, model) => {
|
|
160
|
+
console.log(`Creating model: ${model.constructor.name}`);
|
|
161
|
+
});
|
|
285
162
|
|
|
286
|
-
|
|
287
|
-
|
|
163
|
+
@model()
|
|
164
|
+
@logOnCreate
|
|
165
|
+
class Product extends Model {
|
|
166
|
+
// ...
|
|
288
167
|
}
|
|
289
168
|
```
|
|
290
169
|
|
|
291
|
-
|
|
170
|
+
## Context
|
|
292
171
|
|
|
293
|
-
|
|
172
|
+
The `Context` class is used for passing contextual information through the different layers of your application.
|
|
294
173
|
|
|
295
174
|
```typescript
|
|
296
|
-
import {
|
|
297
|
-
import { id, afterCreate, afterUpdate, afterDelete } from "@decaf-ts/db-decorators";
|
|
175
|
+
import { Context } from '@decaf-ts/db-decorators';
|
|
298
176
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
console.log(`User created: ${model.id} - ${model.name}`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Handler function for logging after update
|
|
305
|
-
function logUpdate(repo, context, data, key, model) {
|
|
306
|
-
console.log(`User updated: ${model.id} - ${model.name}`);
|
|
307
|
-
}
|
|
177
|
+
const context = new Context();
|
|
178
|
+
context.set('user', 'admin');
|
|
308
179
|
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
console.log(`User deleted: ${model.id} - ${model.name}`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
class User extends Model {
|
|
315
|
-
@id()
|
|
316
|
-
id: string;
|
|
317
|
-
|
|
318
|
-
@required()
|
|
319
|
-
name: string;
|
|
320
|
-
|
|
321
|
-
@required()
|
|
322
|
-
email: string;
|
|
323
|
-
|
|
324
|
-
@afterCreate(logCreation)
|
|
325
|
-
@afterUpdate(logUpdate)
|
|
326
|
-
@afterDelete(logDeletion)
|
|
327
|
-
_log: any; // This property is just a placeholder for the decorators
|
|
328
|
-
}
|
|
180
|
+
// You can then pass the context to repository methods
|
|
181
|
+
// userRepository.create(user, context);
|
|
329
182
|
```
|
|
330
183
|
|
|
331
|
-
|
|
184
|
+
## Additional Decorators
|
|
332
185
|
|
|
333
|
-
|
|
186
|
+
### `@id`
|
|
334
187
|
|
|
335
|
-
|
|
188
|
+
Marks a property as the primary key.
|
|
336
189
|
|
|
337
190
|
```typescript
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
constructor() {
|
|
343
|
-
super(User);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Implement required CRUD methods
|
|
347
|
-
async create(model: User, ...args: any[]): Promise<User> {
|
|
348
|
-
// Implementation
|
|
349
|
-
return model;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async read(key: string | number, ...args: any[]): Promise<User> {
|
|
353
|
-
// Implementation
|
|
354
|
-
return new User({ id: key });
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
async update(model: User, ...args: any[]): Promise<User> {
|
|
358
|
-
// Implementation
|
|
359
|
-
return model;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async delete(key: string | number, ...args: any[]): Promise<User> {
|
|
363
|
-
// Implementation
|
|
364
|
-
return await this.read(key);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Example of using context
|
|
368
|
-
async createWithAudit(model: User, userId: string): Promise<User> {
|
|
369
|
-
// Create a context with audit information
|
|
370
|
-
const context = new Context().accumulate({
|
|
371
|
-
auditUser: userId,
|
|
372
|
-
auditTimestamp: new Date(),
|
|
373
|
-
skipValidation: false
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// Pass the context to the create method
|
|
377
|
-
return this.create(model, context);
|
|
378
|
-
}
|
|
191
|
+
@model()
|
|
192
|
+
class Product extends Model {
|
|
193
|
+
@id()
|
|
194
|
+
productId: string;
|
|
379
195
|
}
|
|
380
|
-
|
|
381
|
-
// Usage
|
|
382
|
-
const userRepo = new UserRepository();
|
|
383
|
-
const newUser = new User({ name: "John Doe", email: "john@example.com" });
|
|
384
|
-
const createdUser = await userRepo.createWithAudit(newUser, "admin123");
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
#### Context Hierarchies
|
|
388
|
-
|
|
389
|
-
Description: Create hierarchical contexts for complex operations.
|
|
390
|
-
|
|
391
|
-
```typescript
|
|
392
|
-
import { Context, OperationKeys } from "@decaf-ts/db-decorators";
|
|
393
|
-
import { User } from "./models/User";
|
|
394
|
-
|
|
395
|
-
// Create a parent context
|
|
396
|
-
const parentContext = new Context().accumulate({
|
|
397
|
-
transactionId: "tx123",
|
|
398
|
-
batchOperation: true
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// Create a child context for a specific operation
|
|
402
|
-
const childContext = parentContext.child<User, Context<any>>(
|
|
403
|
-
OperationKeys.CREATE,
|
|
404
|
-
User
|
|
405
|
-
).accumulate({
|
|
406
|
-
operationId: "op456",
|
|
407
|
-
validationLevel: "strict"
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
// Access values from the context hierarchy
|
|
411
|
-
console.log(childContext.get("transactionId")); // "tx123" (inherited from parent)
|
|
412
|
-
console.log(childContext.get("operationId")); // "op456" (from child)
|
|
413
196
|
```
|
|
414
197
|
|
|
415
|
-
###
|
|
198
|
+
### `@generated`
|
|
416
199
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
Description: Perform basic CRUD operations using a repository.
|
|
200
|
+
Indicates that a property's value is generated by the database.
|
|
420
201
|
|
|
421
202
|
```typescript
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// Create a new user
|
|
429
|
-
const newUser = new User({
|
|
430
|
-
name: "Alice Smith",
|
|
431
|
-
email: "alice@example.com",
|
|
432
|
-
password: "securePassword123"
|
|
433
|
-
});
|
|
434
|
-
const createdUser = await userRepo.create(newUser);
|
|
435
|
-
console.log("Created user:", createdUser);
|
|
436
|
-
|
|
437
|
-
// Read a user
|
|
438
|
-
const userId = createdUser.id;
|
|
439
|
-
const retrievedUser = await userRepo.read(userId);
|
|
440
|
-
console.log("Retrieved user:", retrievedUser);
|
|
441
|
-
|
|
442
|
-
// Update a user
|
|
443
|
-
retrievedUser.name = "Alice Johnson";
|
|
444
|
-
const updatedUser = await userRepo.update(retrievedUser);
|
|
445
|
-
console.log("Updated user:", updatedUser);
|
|
446
|
-
|
|
447
|
-
// Delete a user
|
|
448
|
-
const deletedUser = await userRepo.delete(userId);
|
|
449
|
-
console.log("Deleted user:", deletedUser);
|
|
203
|
+
@model()
|
|
204
|
+
class Order extends Model {
|
|
205
|
+
@id()
|
|
206
|
+
@generated()
|
|
207
|
+
orderId: number;
|
|
450
208
|
}
|
|
451
209
|
```
|
|
452
210
|
|
|
453
|
-
|
|
211
|
+
### `@hash`
|
|
454
212
|
|
|
455
|
-
|
|
213
|
+
Automatically hashes a property's value.
|
|
456
214
|
|
|
457
215
|
```typescript
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const productRepo = new ProductRepository();
|
|
463
|
-
|
|
464
|
-
// Create multiple products
|
|
465
|
-
const products = [
|
|
466
|
-
new Product({ category: "Electronics", name: "Laptop", variant: "15-inch" }),
|
|
467
|
-
new Product({ category: "Electronics", name: "Laptop", variant: "13-inch" }),
|
|
468
|
-
new Product({ category: "Electronics", name: "Smartphone", variant: "Pro" })
|
|
469
|
-
];
|
|
470
|
-
const createdProducts = await productRepo.createAll(products);
|
|
471
|
-
console.log("Created products:", createdProducts);
|
|
472
|
-
|
|
473
|
-
// Read multiple products
|
|
474
|
-
const productIds = createdProducts.map(p => p.id);
|
|
475
|
-
const retrievedProducts = await productRepo.readAll(productIds);
|
|
476
|
-
console.log("Retrieved products:", retrievedProducts);
|
|
477
|
-
|
|
478
|
-
// Update multiple products
|
|
479
|
-
const updatedProducts = retrievedProducts.map(p => {
|
|
480
|
-
p.name = p.name + " (Updated)";
|
|
481
|
-
return p;
|
|
482
|
-
});
|
|
483
|
-
const savedProducts = await productRepo.updateAll(updatedProducts);
|
|
484
|
-
console.log("Updated products:", savedProducts);
|
|
485
|
-
|
|
486
|
-
// Delete multiple products
|
|
487
|
-
const deletedProducts = await productRepo.deleteAll(productIds);
|
|
488
|
-
console.log("Deleted products:", deletedProducts);
|
|
216
|
+
@model()
|
|
217
|
+
class User extends Model {
|
|
218
|
+
@hash()
|
|
219
|
+
password!: string;
|
|
489
220
|
}
|
|
490
221
|
```
|
|
491
222
|
|
|
492
|
-
###
|
|
493
|
-
|
|
494
|
-
#### Model Validation
|
|
223
|
+
### `@composed`
|
|
495
224
|
|
|
496
|
-
|
|
225
|
+
Composes a property's value from other properties.
|
|
497
226
|
|
|
498
227
|
```typescript
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
228
|
+
@model()
|
|
229
|
+
class Person extends Model {
|
|
230
|
+
@composed(['firstName', 'lastName'], ' ')
|
|
231
|
+
fullName: string;
|
|
502
232
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
id: string;
|
|
506
|
-
|
|
507
|
-
@required()
|
|
508
|
-
@minLength(2)
|
|
509
|
-
@maxLength(50)
|
|
510
|
-
name: string;
|
|
511
|
-
|
|
512
|
-
@required()
|
|
513
|
-
@email()
|
|
514
|
-
emailAddress: string;
|
|
515
|
-
|
|
516
|
-
@required()
|
|
517
|
-
@minLength(8)
|
|
518
|
-
@pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
|
|
519
|
-
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character")
|
|
520
|
-
password: string;
|
|
521
|
-
|
|
522
|
-
// Manual validation example
|
|
523
|
-
hasErrors(): ModelErrorDefinition | undefined {
|
|
524
|
-
const errors = super.hasErrors();
|
|
525
|
-
|
|
526
|
-
// Add custom validation logic
|
|
527
|
-
if (this.name && this.name.includes('admin') && !this.emailAddress.includes('admin')) {
|
|
528
|
-
if (!errors) {
|
|
529
|
-
return {
|
|
530
|
-
name: ["Admin users must have an admin email address"]
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
errors.name = errors.name || [];
|
|
534
|
-
errors.name.push("Admin users must have an admin email address");
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return errors;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Usage in a repository
|
|
542
|
-
class UserRepository extends Repository<User> {
|
|
543
|
-
// ... other methods
|
|
544
|
-
|
|
545
|
-
async create(model: User, ...args: any[]): Promise<User> {
|
|
546
|
-
// The Repository class will automatically validate the model
|
|
547
|
-
// and throw a ValidationError if validation fails
|
|
548
|
-
|
|
549
|
-
// Custom validation can also be performed
|
|
550
|
-
const errors = model.hasErrors();
|
|
551
|
-
if (errors) {
|
|
552
|
-
throw new ValidationError(errors.toString());
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Proceed with creation if validation passes
|
|
556
|
-
return model;
|
|
557
|
-
}
|
|
233
|
+
firstName: string;
|
|
234
|
+
lastName: string;
|
|
558
235
|
}
|
|
559
236
|
```
|
|
560
237
|
|
|
561
|
-
###
|
|
562
|
-
|
|
563
|
-
#### Working with Model IDs
|
|
238
|
+
### `@version`
|
|
564
239
|
|
|
565
|
-
|
|
240
|
+
Automatically manages a version number for optimistic locking.
|
|
566
241
|
|
|
567
242
|
```typescript
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
class Document extends Model {
|
|
573
|
-
@id()
|
|
574
|
-
documentId: string;
|
|
575
|
-
|
|
576
|
-
title: string;
|
|
577
|
-
content: string;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Create a document instance
|
|
581
|
-
const doc = new Document({
|
|
582
|
-
documentId: "doc-123",
|
|
583
|
-
title: "Sample Document",
|
|
584
|
-
content: "This is a sample document."
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
// Find the primary key property
|
|
588
|
-
const pkInfo = findPrimaryKey(doc);
|
|
589
|
-
console.log("Primary key property:", pkInfo.id); // "documentId"
|
|
590
|
-
console.log("Primary key metadata:", pkInfo.props);
|
|
591
|
-
|
|
592
|
-
// Get the primary key value
|
|
593
|
-
const docId = findModelId(doc);
|
|
594
|
-
console.log("Document ID:", docId); // "doc-123"
|
|
595
|
-
|
|
596
|
-
// Try to get ID from a model without an ID value
|
|
597
|
-
const emptyDoc = new Document();
|
|
598
|
-
try {
|
|
599
|
-
const id = findModelId(emptyDoc); // Will throw an error
|
|
600
|
-
} catch (error) {
|
|
601
|
-
console.error("Error:", error.message);
|
|
243
|
+
@model()
|
|
244
|
+
class Account extends Model {
|
|
245
|
+
@version()
|
|
246
|
+
version: number;
|
|
602
247
|
}
|
|
603
|
-
|
|
604
|
-
// Get ID with returnEmpty option
|
|
605
|
-
const emptyId = findModelId(emptyDoc, true); // Returns undefined instead of throwing
|
|
606
|
-
console.log("Empty ID:", emptyId);
|
|
607
248
|
```
|
|
608
249
|
|
|
609
250
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
## Coding Principles
|
|
614
|
-
|
|
615
|
-
- group similar functionality in folders (analog to namespaces but without any namespace declaration)
|
|
616
|
-
- one class per file;
|
|
617
|
-
- one interface per file (unless interface is just used as a type);
|
|
618
|
-
- group types as other interfaces in a types.ts file per folder;
|
|
619
|
-
- group constants or enums in a constants.ts file per folder;
|
|
620
|
-
- group decorators in a decorators.ts file per folder;
|
|
621
|
-
- always import from the specific file, never from a folder or index file (exceptions for dependencies on other packages);
|
|
622
|
-
- prefer the usage of established design patters where applicable:
|
|
623
|
-
- Singleton (can be an anti-pattern. use with care);
|
|
624
|
-
- factory;
|
|
625
|
-
- observer;
|
|
626
|
-
- strategy;
|
|
627
|
-
- builder;
|
|
628
|
-
- etc;
|
|
629
|
-
|
|
630
|
-
## Release Documentation Hooks
|
|
631
|
-
Stay aligned with the automated release pipeline by reviewing [Release Notes](./workdocs/reports/RELEASE_NOTES.md) and [Dependencies](./workdocs/reports/DEPENDENCIES.md) after trying these recipes (updated on 2025-11-26).
|
|
632
|
-
|
|
633
|
-
|
|
634
251
|
### Related
|
|
635
252
|
|
|
636
253
|
[](https://github.com/decaf-ts/decaf-ts)
|