@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.
Files changed (68) hide show
  1. package/README.md +89 -472
  2. package/dist/db-decorators.cjs +1 -1
  3. package/dist/db-decorators.cjs.map +1 -1
  4. package/dist/db-decorators.js +1 -1
  5. package/dist/db-decorators.js.map +1 -1
  6. package/lib/esm/identity/index.d.ts +5 -0
  7. package/lib/esm/identity/index.js +5 -0
  8. package/lib/esm/identity/index.js.map +1 -1
  9. package/lib/esm/index.d.ts +1 -6
  10. package/lib/esm/index.js +6 -6
  11. package/lib/esm/index.js.map +1 -1
  12. package/lib/esm/interfaces/index.d.ts +5 -0
  13. package/lib/esm/interfaces/index.js +5 -0
  14. package/lib/esm/interfaces/index.js.map +1 -1
  15. package/lib/esm/model/index.d.ts +5 -0
  16. package/lib/esm/model/index.js +5 -0
  17. package/lib/esm/model/index.js.map +1 -1
  18. package/lib/esm/operations/Operations.js +1 -5
  19. package/lib/esm/operations/Operations.js.map +1 -1
  20. package/lib/esm/operations/OperationsRegistry.d.ts +7 -0
  21. package/lib/esm/operations/OperationsRegistry.js +18 -3
  22. package/lib/esm/operations/OperationsRegistry.js.map +1 -1
  23. package/lib/esm/operations/index.d.ts +5 -0
  24. package/lib/esm/operations/index.js +5 -0
  25. package/lib/esm/operations/index.js.map +1 -1
  26. package/lib/esm/overrides/index.d.ts +5 -0
  27. package/lib/esm/overrides/index.js +5 -0
  28. package/lib/esm/overrides/index.js.map +1 -1
  29. package/lib/esm/overrides/overrides.js +1 -1
  30. package/lib/esm/overrides/overrides.js.map +1 -1
  31. package/lib/esm/repository/errors.d.ts +3 -0
  32. package/lib/esm/repository/errors.js +5 -0
  33. package/lib/esm/repository/errors.js.map +1 -1
  34. package/lib/esm/repository/index.d.ts +5 -0
  35. package/lib/esm/repository/index.js +5 -0
  36. package/lib/esm/repository/index.js.map +1 -1
  37. package/lib/identity/index.cjs +5 -0
  38. package/lib/identity/index.d.ts +5 -0
  39. package/lib/identity/index.js.map +1 -1
  40. package/lib/index.cjs +6 -6
  41. package/lib/index.d.ts +1 -6
  42. package/lib/index.js.map +1 -1
  43. package/lib/interfaces/index.cjs +5 -0
  44. package/lib/interfaces/index.d.ts +5 -0
  45. package/lib/interfaces/index.js.map +1 -1
  46. package/lib/model/index.cjs +5 -0
  47. package/lib/model/index.d.ts +5 -0
  48. package/lib/model/index.js.map +1 -1
  49. package/lib/operations/Operations.cjs +1 -5
  50. package/lib/operations/Operations.js.map +1 -1
  51. package/lib/operations/OperationsRegistry.cjs +18 -3
  52. package/lib/operations/OperationsRegistry.d.ts +7 -0
  53. package/lib/operations/OperationsRegistry.js.map +1 -1
  54. package/lib/operations/index.cjs +5 -0
  55. package/lib/operations/index.d.ts +5 -0
  56. package/lib/operations/index.js.map +1 -1
  57. package/lib/overrides/index.cjs +5 -0
  58. package/lib/overrides/index.d.ts +5 -0
  59. package/lib/overrides/index.js.map +1 -1
  60. package/lib/overrides/overrides.cjs +1 -1
  61. package/lib/overrides/overrides.js.map +1 -1
  62. package/lib/repository/errors.cjs +7 -1
  63. package/lib/repository/errors.d.ts +3 -0
  64. package/lib/repository/errors.js.map +1 -1
  65. package/lib/repository/index.cjs +5 -0
  66. package/lib/repository/index.d.ts +5 -0
  67. package/lib/repository/index.js.map +1 -1
  68. 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
  ![Licence](https://img.shields.io/github/license/decaf-ts/db-decorators.svg?style=plastic)
9
17
  ![GitHub language count](https://img.shields.io/github/languages/count/decaf-ts/db-decorators?style=plastic)
10
18
  ![GitHub top language](https://img.shields.io/github/languages/top/decaf-ts/db-decorators?style=plastic)
@@ -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
- ### How to Use
79
+ # How to Use
72
80
 
73
- - [Initial Setup](./workdocs/tutorials/For%20Developers.md#_initial-setup_)
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
- ## DB-Decorators Examples
83
+ ## Repository
77
84
 
78
- ### 1. Defining Models with Decorators
85
+ The `Repository` class is an abstract base class that implements the repository pattern.
79
86
 
80
- #### Basic Model Definition
87
+ ### Creating a Repository
81
88
 
82
- Description: Create a User model with ID, name, email, and password fields. The ID is marked as required and readonly, the password is hashed, and the email is required.
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 "@decaf-ts/decorator-validation";
86
- import { id, hash, version, transient } from "@decaf-ts/db-decorators";
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
- ```typescript
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, ...args: any[]): Promise<User> {
160
- // Implementation for creating a user in the database
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 | number, ...args: any[]): Promise<User> {
170
- // Implementation for reading a user from the database
171
- console.log(`Reading user with ID: ${key}`);
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, ...args: any[]): Promise<User> {
176
- // Implementation for updating a user in the database
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 | number, ...args: any[]): Promise<User> {
182
- // Implementation for deleting a user from the database
183
- console.log(`Deleting user with ID: ${key}`);
184
- const user = await this.read(key);
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
- #### Using Bulk Operations
132
+ ### Operation Hooks
191
133
 
192
- Description: Implement bulk operations for efficient batch processing of multiple models.
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
- import { Repository } from "@decaf-ts/db-decorators";
196
- import { Product } from "./models/Product";
137
+ class UserRepository extends Repository<User, any> {
138
+ // ...
197
139
 
198
- class ProductRepository extends Repository<Product> {
199
- constructor() {
200
- super(Product);
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
- // Implement required CRUD methods
204
- async create(model: Product, ...args: any[]): Promise<Product> {
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
- ### 3. Using Operation Hooks
245
-
246
- #### Property Transformation Hooks
152
+ ## Operation Decorators
247
153
 
248
- Description: Use operation hooks to transform property values during database operations.
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 { Model } from "@decaf-ts/decorator-validation";
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
- class User extends Model {
273
- @id()
274
- id: string;
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
- @onUpdate(setUpdateTimestamp)
287
- updatedAt: string;
163
+ @model()
164
+ @logOnCreate
165
+ class Product extends Model {
166
+ // ...
288
167
  }
289
168
  ```
290
169
 
291
- #### Post-Operation Hooks
170
+ ## Context
292
171
 
293
- Description: Use post-operation hooks to perform actions after database operations.
172
+ The `Context` class is used for passing contextual information through the different layers of your application.
294
173
 
295
174
  ```typescript
296
- import { Model } from "@decaf-ts/decorator-validation";
297
- import { id, afterCreate, afterUpdate, afterDelete } from "@decaf-ts/db-decorators";
175
+ import { Context } from '@decaf-ts/db-decorators';
298
176
 
299
- // Handler function for logging after creation
300
- function logCreation(repo, context, data, key, model) {
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
- // Handler function for logging after deletion
310
- function logDeletion(repo, context, data, key, model) {
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
- ### 4. Working with Contexts
184
+ ## Additional Decorators
332
185
 
333
- #### Creating and Using Contexts
186
+ ### `@id`
334
187
 
335
- Description: Create and use contexts to manage operation state and configuration.
188
+ Marks a property as the primary key.
336
189
 
337
190
  ```typescript
338
- import { Context, Repository } from "@decaf-ts/db-decorators";
339
- import { User } from "./models/User";
340
-
341
- class UserRepository extends Repository<User> {
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
- ### 5. Performing CRUD Operations
198
+ ### `@generated`
416
199
 
417
- #### Basic CRUD Operations
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
- import { User } from "./models/User";
423
- import { UserRepository } from "./repositories/UserRepository";
424
-
425
- async function userCrudExample() {
426
- const userRepo = new UserRepository();
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
- #### Bulk Operations
211
+ ### `@hash`
454
212
 
455
- Description: Perform bulk operations for efficient batch processing.
213
+ Automatically hashes a property's value.
456
214
 
457
215
  ```typescript
458
- import { Product } from "./models/Product";
459
- import { ProductRepository } from "./repositories/ProductRepository";
460
-
461
- async function productBulkExample() {
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
- ### 6. Validation
493
-
494
- #### Model Validation
223
+ ### `@composed`
495
224
 
496
- Description: Validate models during CRUD operations to ensure data integrity.
225
+ Composes a property's value from other properties.
497
226
 
498
227
  ```typescript
499
- import { Model, ModelErrorDefinition } from "@decaf-ts/decorator-validation";
500
- import { id } from "@decaf-ts/db-decorators";
501
- import { required, minLength, maxLength, email, pattern } from "@decaf-ts/decorator-validation";
228
+ @model()
229
+ class Person extends Model {
230
+ @composed(['firstName', 'lastName'], ' ')
231
+ fullName: string;
502
232
 
503
- class User extends Model {
504
- @id()
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
- ### 7. Identity Management
562
-
563
- #### Working with Model IDs
238
+ ### `@version`
564
239
 
565
- Description: Use the identity module to work with model IDs.
240
+ Automatically manages a version number for optimistic locking.
566
241
 
567
242
  ```typescript
568
- import { Model } from "@decaf-ts/decorator-validation";
569
- import { id } from "@decaf-ts/db-decorators";
570
- import { findPrimaryKey, findModelId } from "@decaf-ts/db-decorators";
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
  [![decaf-ts](https://github-readme-stats.vercel.app/api/pin/?username=decaf-ts&repo=decaf-ts)](https://github.com/decaf-ts/decaf-ts)