@decaf-ts/for-typeorm 0.0.15 → 0.0.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 (78) hide show
  1. package/README.md +596 -18
  2. package/dist/for-typeorm.cjs +456 -175
  3. package/dist/for-typeorm.esm.cjs +458 -176
  4. package/lib/TypeORMAdapter.cjs +29 -27
  5. package/lib/TypeORMAdapter.d.ts +3 -3
  6. package/lib/TypeORMDispatch.cjs +25 -30
  7. package/lib/TypeORMDispatch.d.ts +12 -20
  8. package/lib/TypeORMRepository.cjs +13 -5
  9. package/lib/TypeORMRepository.d.ts +14 -8
  10. package/lib/constants.cjs +7 -1
  11. package/lib/constants.d.ts +6 -0
  12. package/lib/decorators.cjs +145 -1
  13. package/lib/decorators.d.ts +117 -0
  14. package/lib/esm/TypeORMAdapter.d.ts +3 -3
  15. package/lib/esm/TypeORMAdapter.js +29 -27
  16. package/lib/esm/TypeORMDispatch.d.ts +12 -20
  17. package/lib/esm/TypeORMDispatch.js +25 -30
  18. package/lib/esm/TypeORMRepository.d.ts +14 -8
  19. package/lib/esm/TypeORMRepository.js +13 -5
  20. package/lib/esm/constants.d.ts +6 -0
  21. package/lib/esm/constants.js +7 -1
  22. package/lib/esm/decorators.d.ts +117 -0
  23. package/lib/esm/decorators.js +145 -1
  24. package/lib/esm/index.d.ts +3 -2
  25. package/lib/esm/index.js +3 -4
  26. package/lib/esm/overrides/Column.d.ts +21 -0
  27. package/lib/esm/overrides/Column.js +4 -1
  28. package/lib/esm/overrides/CreateDateColumn.d.ts +18 -0
  29. package/lib/esm/overrides/CreateDateColumn.js +19 -1
  30. package/lib/esm/overrides/ManyToMany.d.ts +5 -7
  31. package/lib/esm/overrides/ManyToMany.js +5 -1
  32. package/lib/esm/overrides/ManyToOne.d.ts +5 -7
  33. package/lib/esm/overrides/ManyToOne.js +5 -1
  34. package/lib/esm/overrides/OneToMany.d.ts +4 -0
  35. package/lib/esm/overrides/OneToMany.js +5 -1
  36. package/lib/esm/overrides/OneToOne.d.ts +5 -6
  37. package/lib/esm/overrides/OneToOne.js +5 -1
  38. package/lib/esm/overrides/PrimaryColumn.d.ts +15 -0
  39. package/lib/esm/overrides/PrimaryColumn.js +23 -1
  40. package/lib/esm/overrides/PrimaryGeneratedColumn.d.ts +7 -0
  41. package/lib/esm/overrides/PrimaryGeneratedColumn.js +21 -1
  42. package/lib/esm/overrides/UpdateDateColumn.d.ts +18 -0
  43. package/lib/esm/overrides/UpdateDateColumn.js +19 -1
  44. package/lib/esm/query/Paginator.js +2 -2
  45. package/lib/esm/query/Statement.d.ts +1 -2
  46. package/lib/esm/query/Statement.js +13 -11
  47. package/lib/esm/sequences/Sequence.js +27 -11
  48. package/lib/esm/types.d.ts +25 -0
  49. package/lib/esm/types.js +26 -1
  50. package/lib/esm/utils.js +1 -13
  51. package/lib/index.cjs +4 -5
  52. package/lib/index.d.ts +3 -2
  53. package/lib/overrides/Column.cjs +4 -1
  54. package/lib/overrides/Column.d.ts +21 -0
  55. package/lib/overrides/CreateDateColumn.cjs +19 -1
  56. package/lib/overrides/CreateDateColumn.d.ts +18 -0
  57. package/lib/overrides/ManyToMany.cjs +5 -1
  58. package/lib/overrides/ManyToMany.d.ts +5 -7
  59. package/lib/overrides/ManyToOne.cjs +5 -1
  60. package/lib/overrides/ManyToOne.d.ts +5 -7
  61. package/lib/overrides/OneToMany.cjs +5 -1
  62. package/lib/overrides/OneToMany.d.ts +4 -0
  63. package/lib/overrides/OneToOne.cjs +5 -1
  64. package/lib/overrides/OneToOne.d.ts +5 -6
  65. package/lib/overrides/PrimaryColumn.cjs +23 -1
  66. package/lib/overrides/PrimaryColumn.d.ts +15 -0
  67. package/lib/overrides/PrimaryGeneratedColumn.cjs +21 -1
  68. package/lib/overrides/PrimaryGeneratedColumn.d.ts +7 -0
  69. package/lib/overrides/UpdateDateColumn.cjs +19 -1
  70. package/lib/overrides/UpdateDateColumn.d.ts +18 -0
  71. package/lib/query/Paginator.cjs +2 -2
  72. package/lib/query/Statement.cjs +13 -11
  73. package/lib/query/Statement.d.ts +1 -2
  74. package/lib/sequences/Sequence.cjs +27 -11
  75. package/lib/types.cjs +26 -1
  76. package/lib/types.d.ts +25 -0
  77. package/lib/utils.cjs +1 -13
  78. package/package.json +2 -1
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
- [![Banner](./workdocs/assets/Banner.png)](https://decaf-ts.github.io/ts-workspace/)
2
- ## Typescript Template
1
+ # Decaf.ts — TypeORM Integration
3
2
 
4
- This repository is meant to provide an enterprise template for any standard Typescript project
3
+ A thin, focused TypeORM-backed adapter that plugs Decaf.ts models, repositories and query primitives into relational databases via TypeORM, keeping the same API you use across other Decaf adapters. It provides:
4
+ - TypeORMAdapter: connection management, CRUD/bulk ops, raw SQL, schema helpers, sequences, indexes, error translation
5
+ - TypeORMRepository: typed CRUD with validation, context/flags, observers, and access to the native TypeORM repository
6
+ - Query layer: TypeORMStatement and TypeORMPaginator for translating Decaf statements to TypeORM options/builders and paginating results
7
+ - Decorator wiring: automatically wires Decaf decorators to TypeORM metadata on import (no need to use TypeORM decorators directly)
8
+ - Utilities and types: constants, operator translation, raw Postgres types, and small helpers like convertJsRegexToPostgres
5
9
 
6
10
 
7
11
  ![Licence](https://img.shields.io/github/license/decaf-ts/ts-workspace.svg?style=plastic)
@@ -27,29 +31,603 @@ This repository is meant to provide an enterprise template for any standard Type
27
31
 
28
32
  Documentation available [here](https://decaf-ts.github.io/ts-workspace/)
29
33
 
30
- ### Description
34
+ # Decaf.ts for TypeORM — Detailed Description
35
+
36
+ Decaf.ts for TypeORM provides a complete implementation of Decaf.ts' data access abstractions backed by a TypeORM DataSource. It bridges Decaf models, repositories, and query primitives with TypeORM's ORM facilities, while keeping a consistent API across different database adapters in the Decaf.ts ecosystem.
37
+
38
+ Core capabilities include:
39
+ - An Adapter (TypeORMAdapter) that encapsulates connection management, CRUD operations, schema creation helpers, index generation, raw execution, error translation, and wiring of decorators.
40
+ - A Repository (TypeORMRepository) that exposes typed CRUD and batch operations for a given Model, validating data via @decaf-ts/db-decorators and @decaf-ts/decorator-validation.
41
+ - Query composition via TypeORMStatement, which converts the Decaf.ts core Statement API into TypeORM Find options and QueryBuilder calls. Combined with TypeORMPaginator to paginate results.
42
+ - Decorator overrides that mirror TypeORM’s decorators (Entity, Column, PrimaryColumn, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, JoinColumn, OneToOne, OneToMany, ManyToOne) ensuring compatible metadata is emitted for the adapter.
43
+ - Sequence utilities (TypeORMSequence) for generating and reading database sequence values.
44
+ - Index generation helpers (generateIndexes) to produce SQL index creation statements based on model metadata.
45
+ - Typed constants, enums, and helper types for SQL operators, query containers, and PostgreSQL result shapes.
46
+
47
+ Architecture and responsibilities
48
+
49
+ 1. Adapter layer
50
+ - TypeORMAdapter is the central integration point. It:
51
+ - Holds a TypeORM DataSource and provides dataSource(), connect(), createDatabase(), createUser(), and related helpers.
52
+ - Implements CRUD over entities: create, read, update, delete, and their batch counterparts (createAll, readAll, updateAll, deleteAll).
53
+ - Provides Statement(), Sequence(), Repository() factories returning TypeORM-specific implementations.
54
+ - Indexing support via index(models) which uses generateIndexes to build SQL statements for indexes.
55
+ - Raw execution via raw({ query, values }).
56
+ - Error translation through parseError() mapping DB errors to Decaf.ts errors.
57
+ - Schema creation helpers: parseTypeToPostgres, parseValidationToPostgres, parseRelationsToPostgres, createTable.
58
+ - Decoration() static initializer: wires Decaf.ts model decorators and relation metadata to TypeORM’s metadata storage using the overrides in src/overrides.
59
+
60
+ 2. Repository layer
61
+ - TypeORMRepository<M> extends the Decaf.ts core Repository and provides:
62
+ - Validation enforcement (enforceDBDecorators) and Context propagation.
63
+ - Standard CRUD and batch operations that delegate to the adapter, applying OperationKeys flags and TypeORMFlags.
64
+ - Query builder access via queryBuilder() to get a TypeORMStatement for fluent querying.
65
+
66
+ 3. Query layer
67
+ - TypeORMStatement<M, R> extends core Statement and composes queries as TypeORM Find options/QueryBuilder calls.
68
+ - build() resolves the internal statement into a TypeORMQuery container.
69
+ - raw() executes a SelectQueryBuilder and returns getMany() results.
70
+ - paginate(size) returns a TypeORMPaginator bound to this statement.
71
+ - translateOperators() maps Decaf.ts Operator/GroupOperator to SQL via TypeORMOperator/TypeORMGroupOperator.
72
+ - TypeORMPaginator<M, R> implements page navigation using TypeORM’s repository.findAndCount with take/skip and maps rows back to models via the adapter’s revert().
73
+
74
+ 4. Decorator overrides
75
+ - Functions mirroring TypeORM decorators but routed through our overrides/utilities to control metadata aggregation:
76
+ - Entity, Column, PrimaryColumn, PrimaryGeneratedColumn.
77
+ - CreateDateColumn, UpdateDateColumn.
78
+ - JoinColumn.
79
+ - OneToOne, OneToMany, ManyToOne.
80
+ - These register metadata through getMetadataArgsStorage() and the helper aggregateOrNewColumn to avoid duplicates and merge options.
81
+
82
+ 5. Sequences
83
+ - TypeORMSequence implements the Decaf.ts Sequence abstraction using Adapter.raw to query and increment PostgreSQL sequences, parsing values according to the configured type.
84
+
85
+ 6. Index generation
86
+ - generateIndexes(models) inspects Repository.indexes metadata and returns a list of TypeORMQuery statements to create indexes. The Adapter can execute them via raw().
87
+
88
+ 7. Dispatching and events
89
+ - TypeORMDispatch extends core Dispatch to subscribe a TypeORM DataSource to a TypeORMEventSubscriber, translating TypeORM entity events (insert/update/delete) into OperationKeys notifications for Decaf.ts observers.
90
+ - TypeORMEventSubscriber listens to afterInsert/afterUpdate/afterRemove, resolves the model/table via Repository.table, and calls adapter.updateObservers.
91
+
92
+ 8. Constants, types, and utilities
93
+ - constants: reservedAttributes regex, TypeORMFlavour identifier, TypeORMKeys for common DB keys.
94
+ - query/constants: TypeORMQueryLimit and mappings for TypeORMOperator (comparison operators) and TypeORMGroupOperator (logical operators), plus TypeORMConst.
95
+ - types: SQLOperator enum; TypeORMQuery container; TypeORMFlags; TypeORMTableSpec.
96
+ - raw/postgres: FieldDef, QueryResultBase, QueryResult, QueryArrayResult for typing raw Postgres results.
97
+ - utils: convertJsRegexToPostgres() to transform JS RegExp into PostgreSQL POSIX pattern strings.
98
+
99
+ Typical usage flow
100
+
101
+ 1. Initialize and decorate
102
+ - Import from `@decaf-ts/for-typeorm` index. It calls TypeORMAdapter.decoration() on import to ensure decorators are wired.
103
+ 2. Configure adapter and data source
104
+ - Construct a TypeORMAdapter with DataSourceOptions, then initialize/connect.
105
+ 3. Define models with decaf-ts decorators, keeping it consistent decorators:
106
+ - use @table() instead of @Entity();
107
+ - use @column() instead of @Column();
108
+ - always use decaf-ts decorators instead of TypeORM decorators. Decaf's will be wired to TypeORM's metadata storage.
109
+ 4. Use repositories
110
+ - Use Repository.forModel to get a decaf repository for your model.
111
+ - Get a TypeORMRepository native features use repository.nativeRepository().
112
+ 5. Build queries
113
+ - Using the decaf query api, all queries are guaranteed to use prepared statements via repository.select()
114
+ - Use repository.queryBuilder() to use native typeorm query builder for edge cases or advanced queries.
115
+ 6. Sequences and indexes
116
+ - Use TypeORMSequence for sequence values and generateIndexes to pre-create DB indexes.
117
+ 7. Observe changes
118
+ - Use TypeORMDispatch to subscribe to entity events and update observers in real time.
119
+
120
+ Error handling
121
+
122
+ - IndexError signals issues with index generation/handling.
123
+ - Adapter.parseError translates TypeORM/DB errors into Decaf.ts error types (ConflictError, NotFoundError, etc.) for consistent error semantics across adapters.
124
+
125
+ Database and compatibility notes
126
+
127
+ - The adapter targets TypeORM; many helper utilities assume PostgreSQL (e.g., regex operators, sequence queries). The code converts JS regex to PostgreSQL-compatible patterns and defines raw result typings for Postgres.
128
+
129
+ Exports overview (primary)
130
+
131
+ - Classes: TypeORMAdapter, TypeORMRepository, TypeORMDispatch, TypeORMEventSubscriber, TypeORMStatement, TypeORMPaginator, TypeORMSequence, IndexError.
132
+ - Decorators and helpers: Entity, Column, PrimaryColumn, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, JoinColumn, OneToOne, OneToMany, ManyToOne, aggregateOrNewColumn.
133
+ - Query utilities: TypeORMQueryLimit, TypeORMOperator, TypeORMGroupOperator, TypeORMConst, translateOperators.
134
+ - Types: SQLOperator, TypeORMQuery, TypeORMFlags, TypeORMTableSpec.
135
+ - Constants: reservedAttributes, TypeORMKeys, TypeORMFlavour.
136
+ - Utils: convertJsRegexToPostgres.
137
+ - Raw typing: FieldDef, QueryResultBase, QueryResult, QueryArrayResult.
138
+
139
+
140
+ # How to Use — Decaf.ts for TypeORM
141
+
142
+ This guide provides practical, TypeScript examples for the public APIs exported by `@decaf-ts/for-typeorm`.
143
+
144
+ Notes:
145
+ - Importing from `@decaf-ts/for-typeorm` runs `TypeORMAdapter.decoration()` automatically, wiring Decaf decorators to TypeORM metadata.
146
+ - Examples mirror the usage found in the package tests under `for-typeorm/tests`.
147
+
148
+ ## Setup the adapter and DataSource
149
+
150
+ ```ts
151
+ import { TypeORMAdapter, TypeORMFlavour } from "@decaf-ts/for-typeorm";
152
+ import { DataSource, DataSourceOptions } from "typeorm";
153
+
154
+ // Admin connection (used to create db/user)
155
+ const adminOptions: DataSourceOptions = {
156
+ type: "postgres",
157
+ host: "localhost",
158
+ port: 5432,
159
+ username: "postgres",
160
+ password: "password",
161
+ database: "postgres",
162
+ };
163
+
164
+ // App database name and user
165
+ const dbName = "app_db";
166
+ const appUser = "app_user";
167
+ const appPass = "password";
168
+
169
+ // 1) Connect as admin and prepare database and user
170
+ const admin = await TypeORMAdapter.connect(adminOptions);
171
+ try {
172
+ await TypeORMAdapter.createDatabase(admin, dbName);
173
+ await TypeORMAdapter.createUser(admin, dbName, appUser, appPass);
174
+ await TypeORMAdapter.createNotifyFunction(admin, appUser);
175
+ } finally {
176
+ await admin.destroy();
177
+ }
178
+
179
+ // 2) Application DataSource and adapter
180
+ const appOptions: DataSourceOptions = {
181
+ type: "postgres",
182
+ host: "localhost",
183
+ port: 5432,
184
+ username: appUser,
185
+ password: appPass,
186
+ database: dbName,
187
+ synchronize: true,
188
+ logging: false,
189
+ };
190
+
191
+ const adapter = new TypeORMAdapter(appOptions);
192
+ // Optionally inject an existing DataSource instance
193
+ adapter["_dataSource"] = new DataSource(appOptions);
194
+ ```
195
+
196
+ ## Define a model with Decaf decorators
197
+
198
+ Use Decaf decorators (@model, @table, @pk, @column, etc.). They are wired to TypeORM automatically by this package.
199
+
200
+ ```ts
201
+ import { model, ModelArg } from "@decaf-ts/decorator-validation";
202
+ import {
203
+ table,
204
+ pk,
205
+ column,
206
+ required,
207
+ oneToOne,
208
+ oneToMany,
209
+ manyToOne,
210
+ manyToMany,
211
+ Repository,
212
+ uses,
213
+ } from "@decaf-ts/core";
214
+ import { TypeORMFlavour, TypeORMRepository } from "@decaf-ts/for-typeorm";
215
+ import { Cascade } from "@decaf-ts/db-decorators";
216
+
217
+ /**
218
+ * User ↔ Profile: one-to-one
219
+ * User → Post: one-to-many
220
+ * Post → User: many-to-one
221
+ * Post ↔ Tag: many-to-many
222
+ */
223
+
224
+ @uses(TypeORMFlavour)
225
+ @table("app_user")
226
+ @model()
227
+ class AppUser extends Model {
228
+ @pk({ type: "Number" })
229
+ id!: number;
230
+
231
+ @required()
232
+ @column("name")
233
+ @minlength(3)
234
+ @maxlength(255)
235
+ @index()
236
+ name!: string;
237
+
238
+ @required()
239
+ @column("email")
240
+ @email()
241
+ @index()
242
+ email!: string;
243
+
244
+ @column("is_active")
245
+ isActive: boolean = true;
246
+
247
+ @column("created_at")
248
+ @createdAt()
249
+ createdAt: Date;
250
+
251
+ // oneToOne: each user has exactly one profile
252
+ @oneToOne(
253
+ () => UserProfile,
254
+ {
255
+ update: Cascade.CASCADE,
256
+ delete: Cascade.SET_NULL,
257
+ },
258
+ true // populate
259
+ )
260
+ @required()
261
+ profile!: UserProfile;
262
+
263
+ // oneToMany: user has many posts (inverse in Post.author as manyToOne)
264
+ @oneToMany(
265
+ () => Post,
266
+ {
267
+ update: Cascade.CASCADE,
268
+ delete: Cascade.CASCADE,
269
+ },
270
+ true // populate
271
+ )
272
+ posts?: Post[];
273
+
274
+ constructor(arg?: ModelArg<AppUser>) {
275
+ super(arg);
276
+ }
277
+ }
278
+
279
+ @uses(TypeORMFlavour)
280
+ @table("user_profile")
281
+ @model()
282
+ class UserProfile extends Model {
283
+ @pk({ type: "Number" })
284
+ id!: number;
285
+
286
+ @column("bio")
287
+ bio?: string;
288
+
289
+ @column("age")
290
+ @min(0)
291
+ @max(150)
292
+ @step(1)
293
+ @required()
294
+ age!: number;
295
+
296
+ @column("avatar_url")
297
+ @url()
298
+ avatarUrl?: string;
299
+
300
+ @column("phone")
301
+ @index()
302
+ phone?: string;
303
+
304
+ @column("created_at")
305
+ @createdAt()
306
+ updatedAt: Date;
307
+
308
+ @column("updated_at")
309
+ @updatedAt()
310
+ updatedAt: Date;
311
+
312
+ // Optional back-reference to the user (many projects omit the reverse one-to-one)
313
+ @oneToOne(
314
+ () => AppUser,
315
+ {
316
+ update: Cascade.CASCADE,
317
+ delete: Cascade.CASCADE,
318
+ },
319
+ true
320
+ )
321
+ @required()
322
+ user!: AppUser;
323
+
324
+ constructor(arg?: ModelArg<UserProfile>) {
325
+ super(arg)
326
+ }
327
+ }
328
+
329
+ @uses(TypeORMFlavour)
330
+ @table("post")
331
+ @model()
332
+ class Post extends Model {
333
+ @pk({ type: "Number" })
334
+ id!: number;
335
+
336
+ @required()
337
+ @column("title")
338
+ @index()
339
+ title!: string;
340
+
341
+ @required()
342
+ @column("body")
343
+ body!: string;
344
+
345
+ @column("published_at")
346
+ publishedAt?: Date;
347
+
348
+ @column("is_published")
349
+ isPublished: boolean = false;
350
+
351
+ // manyToOne: each post belongs to a single user (inverse of AppUser.posts)
352
+ @manyToOne(
353
+ () => AppUser,
354
+ {
355
+ update: Cascade.NONE,
356
+ delete: Cascade.SET_NULL,
357
+ },
358
+ false // only one side of the relation can be eager
359
+ )
360
+ author!: AppUser;
361
+
362
+ // manyToMany: posts can have many tags and tags can belong to many posts
363
+ @manyToMany(
364
+ () => Tag,
365
+ {
366
+ update: Cascade.NONE,
367
+ delete: Cascade.NONE,
368
+ },
369
+ true // populate
370
+ )
371
+ tags?: Tag[];
372
+
373
+ constructor(arg?: ModelArg<Post>) {
374
+ super(arg)
375
+ }
376
+ }
377
+
378
+ @uses(TypeORMFlavour)
379
+ @table()
380
+ @model()
381
+ class Tag extends Model {
382
+ @pk({ type: "Number" })
383
+ id!: number;
384
+
385
+ @required()
386
+ @column()
387
+ @index()
388
+ name!: string;
389
+
390
+ @column()
391
+ color?: string;
392
+
393
+ // Optional reverse manyToMany side
394
+ @manyToMany(
395
+ () => Post,
396
+ {
397
+ update: Cascade.NONE,
398
+ delete: Cascade.NONE,
399
+ },
400
+ true
401
+ )
402
+ posts?: Post[];
403
+
404
+ constructor(arg?: ModelArg<Tag>) {
405
+ super(arg)
406
+ }
407
+ }
408
+
409
+ // Example: create a user with profile, posts and tags in one go
410
+ const userRepo: TypeORMRepository<AppUser> = Repository.forModel(AppUser);
411
+ const tagRepo: TypeORMRepository<Tag> = Repository.forModel(Tag);
412
+
413
+ const t1 = await tagRepo.create(new Tag({ name: "typescript", color: "#3178C6" }));
414
+ const t2 = await tagRepo.create(new Tag({ name: "orm" }));
415
+
416
+ const createdUser = await userRepo.create(
417
+ new AppUser({
418
+ name: "Alice",
419
+ email: "alice@example.com",
420
+ profile: {
421
+ bio: "Full-stack dev",
422
+ phone: "+1-555-1234"
423
+ },
424
+ posts: [
425
+ {
426
+ title: "Hello World",
427
+ body: "My first post",
428
+ isPublished: true,
429
+ tags: [t1, t2],
430
+ },
431
+ {
432
+ title: "TypeORM Tips",
433
+ body: "Relations and cascading",
434
+ tags: [t1],
435
+ },
436
+ ],
437
+ })
438
+ );
439
+
440
+ // Read back with relations populated (populate=true in decorators)
441
+ const fetched = await userRepo.read(createdUser.id);
442
+ ```
443
+
444
+ ## Repository CRUD operations
445
+
446
+ ```ts
447
+ import { OperationKeys } from "@decaf-ts/db-decorators";
448
+ import { Observer } from "@decaf-ts/core";
449
+
450
+ // Observe changes
451
+ const mock = jest.fn(); // or any function
452
+ const observer: Observer = { refresh: (...args) => Promise.resolve(mock(...args)) };
453
+ repo.observe(observer);
454
+
455
+ // Create
456
+ const created = await repo.create(new User({ name: "Alice", nif: "123456789" }));
457
+ // Read
458
+ const fetched = await repo.read(created.id);
459
+ // Update
460
+ created.name = "Alice Doe";
461
+ const updated = await repo.update(created);
462
+ // Delete
463
+ await repo.delete(updated.id);
464
+
465
+ // Bulk operations
466
+ const many = [
467
+ new User({ name: "u1", nif: "111111111" }),
468
+ new User({ name: "u2", nif: "222222222" }),
469
+ ];
470
+ const createdAll = await repo.createAll(many);
471
+ const readAll = await repo.readAll(createdAll.map(u => u.id));
472
+ const updatedAll = await repo.updateAll(readAll.map(u => ({ ...u, name: u.name + "!" }) as User));
473
+ await repo.deleteAll(updatedAll.map(u => u.id));
474
+ ```
475
+
476
+ ## Native TypeORM repository and QueryBuilder access
477
+
478
+ ```typescript
479
+ import { TypeORMRepository } from "@decaf-ts/for-typeorm"
480
+ // Access the underlying TypeORM Repository
481
+ const repo: TypeORMRepository = Repository.forModel(User);
482
+ const nativeRepo = repo.nativeRepository();
483
+
484
+ // Or build a TypeORM query using queryBuilder()
485
+ const qb = repo.queryBuilder<User>(); // returns a QueryBuilder<User>
486
+ const rows = await qb.select("user").where({ name: "Alice" }).getMany();
487
+ ```
488
+
489
+ ## Decaf Statement with pagination
490
+
491
+ ```ts
492
+ import { Operator, Condition, Repository } from "@decaf-ts/core";
493
+ const repo: TypeORMRepository = Repository.forModel(User);
494
+
495
+ // Build a Decaf statement and paginate
496
+ const stmt = repo
497
+ .select()
498
+ .where(Condition.attr<User>("name").eq("Alice"))
499
+ .orderBy("id")
500
+ .paginate(10); // TypeORMPaginator under the hood
501
+
502
+ const page1 = await stmt.page(1); // User[]
503
+ ```
504
+
505
+
506
+ ## TypeORMDispatch and live updates
507
+
508
+ Description: Subscribe to TypeORM entity events and notify Decaf observers on CREATE/UPDATE/DELETE. Based on tests/integration/dispatch-subscriber.test.ts.
509
+
510
+ ```ts
511
+ import { TypeORMDispatch, TypeORMAdapter } from "@decaf-ts/for-typeorm";
512
+ import { Repository, Observer } from "@decaf-ts/core";
513
+ import { OperationKeys } from "@decaf-ts/db-decorators";
514
+ import { DataSourceOptions } from "typeorm";
31
515
 
32
- No one needs the hassle of setting up new repos every time.
516
+ // Assume you already created the DB and user
517
+ const options: DataSourceOptions = { /* postgres options */ } as any;
518
+ const adapter = new TypeORMAdapter(options);
519
+
520
+ // Observe repository changes
521
+ const repo = Repository.forModel(User);
522
+ const spy = jest.fn();
523
+ const observer: Observer = { refresh: (t, op, ids) => Promise.resolve(spy(t, op, ids)) };
524
+ repo.observe(observer);
525
+
526
+ // Start dispatch
527
+ const dispatch = new TypeORMDispatch();
528
+ await dispatch.observe(adapter, options);
529
+
530
+ // After create/update/delete through the repo, your observer will be notified
531
+ await repo.create(new User({ name: "Bob", nif: "999999990" }));
532
+ expect(spy).toHaveBeenCalledWith(repo.table, OperationKeys.CREATE, expect.any(Array));
533
+ ```
33
534
 
34
- Now you can create new repositories from this template and enjoy having everything set up for you.
535
+ ## TypeORMEventSubscriber (manual registration)
35
536
 
537
+ Description: Register the subscriber in a DataSource to forward TypeORM events. Used implicitly by TypeORMDispatch; shown here for completeness.
36
538
 
539
+ ```ts
540
+ import { TypeORMEventSubscriber } from "@decaf-ts/for-typeorm";
541
+ import { DataSource, DataSourceOptions } from "typeorm";
37
542
 
38
- ### How to Use
543
+ const options: DataSourceOptions = { /* postgres options */ } as any;
544
+ const ds = new DataSource({ ...options, subscribers: [new TypeORMEventSubscriber((table, op, ids) => {
545
+ console.log("Changed:", table, op, ids);
546
+ })] });
547
+ await ds.initialize();
548
+ ```
39
549
 
40
- - [Initial Setup](./tutorials/For%20Developers.md#_initial-setup_)
41
- - [Installation](./tutorials/For%20Developers.md#installation)
42
- - [Scripts](./tutorials/For%20Developers.md#scripts)
43
- - [Linting](./tutorials/For%20Developers.md#testing)
44
- - [CI/CD](./tutorials/For%20Developers.md#continuous-integrationdeployment)
45
- - [Publishing](./tutorials/For%20Developers.md#publishing)
46
- - [Structure](./tutorials/For%20Developers.md#repository-structure)
47
- - [IDE Integrations](./tutorials/For%20Developers.md#ide-integrations)
48
- - [VSCode(ium)](./tutorials/For%20Developers.md#visual-studio-code-vscode)
49
- - [WebStorm](./tutorials/For%20Developers.md#webstorm)
50
- - [Considerations](./tutorials/For%20Developers.md#considerations)
550
+ ## translateOperators and SQLOperator
51
551
 
552
+ Description: Translate Decaf operators to TypeORM SQL operators; useful when building custom WHERE clauses. Based on src/query/translate.ts and query/constants.
52
553
 
554
+ ```ts
555
+ import { translateOperators } from "@decaf-ts/for-typeorm";
556
+ import { Operator, GroupOperator } from "@decaf-ts/core";
557
+
558
+ const eq = translateOperators(Operator.EQUAL); // "="
559
+ const ne = translateOperators(Operator.DIFFERENT); // "<>"
560
+ const and = translateOperators(GroupOperator.AND); // "AND"
561
+ ```
562
+
563
+ ## convertJsRegexToPostgres
564
+
565
+ Description: Convert a JS RegExp or string form to a PostgreSQL POSIX pattern string for use with ~ / ~*.
566
+
567
+ ```ts
568
+ import { convertJsRegexToPostgres } from "@decaf-ts/for-typeorm";
569
+
570
+ convertJsRegexToPostgres(/foo.*/i); // "foo.*"
571
+ convertJsRegexToPostgres("/bar.+/g"); // "bar.+"
572
+ ```
573
+
574
+ ## splitEagerRelations
575
+
576
+ Description: Compute eager vs. non-eager relations for a Model class based on relation decorators. Mirrors behavior used by the adapter and statement builder.
577
+
578
+ ```ts
579
+ import { splitEagerRelations } from "@decaf-ts/for-typeorm";
580
+
581
+ const { relations, nonEager } = splitEagerRelations(User);
582
+ // relations might include ["posts", "profile", "posts.tags"] depending on your decorators
583
+ ```
584
+
585
+ ## Sequences (TypeORMSequence)
586
+
587
+ Description: Work with PostgreSQL sequences through the adapter. Based on tests/integration/sequences.test.ts.
588
+
589
+ ```ts
590
+ import { TypeORMSequence } from "@decaf-ts/for-typeorm";
591
+
592
+ const seq = new TypeORMSequence({ name: "user_id_seq", type: "Number", startWith: 1, incrementBy: 1 }, adapter);
593
+ const nextValue = await seq.next();
594
+ const batch = await seq.range(5); // e.g., [2,3,4,5,6]
595
+ ```
596
+
597
+ ## Index generation (generateIndexes)
598
+
599
+ Description: Generate SQL statements to create indexes defined via decorators. Use adapter.raw to execute them. Based on adapter.index() and indexes/generator.
600
+
601
+ ```ts
602
+ import { generateIndexes, TypeORMAdapter } from "@decaf-ts/for-typeorm";
603
+
604
+ const stmts = generateIndexes([User, Post]); // returns TypeORMQuery[] with raw SQL and values
605
+ for (const st of stmts) {
606
+ await adapter.raw(st);
607
+ }
608
+ ```
609
+
610
+
611
+ ## Decorator mapping: decaf-ts decorators ➜ TypeORM
612
+
613
+ The TypeORM adapter wires Decaf decorators into TypeORM metadata automatically on import. The following table summarizes the mapping observed in the adapter code and tests (including the vanilla TypeORM comparison tests):
614
+
615
+ | Decaf decorator | TypeORM counterpart | Notes |
616
+ |---|------------------------------------------------------------------------------------------------------------------------------------|---|
617
+ | @model() + @table(name?) | @Entity({ name }) | Decaf models are entities; when no name is provided, TypeORM uses the class/table naming strategy. |
618
+ | @pk({ type, generated? }) | @PrimaryGeneratedColumn() or @PrimaryColumn() | Generated numeric/bigint keys map to PrimaryGeneratedColumn; otherwise PrimaryColumn with the given type. |
619
+ | @column(name?) | @Column({ name }) | Additional type/length/precision options flow through from the Decaf type metadata. |
620
+ | @unique() | @Column({ unique: true }) | Marks the column as unique. |
621
+ | @required() | @Column({ nullable: false }) | Forces NOT NULL at the column level. |
622
+ | (no @required()) | @Column({nullable: true}) | Column nullability follows TypeORM defaults unless overridden by other constraints/validators. |
623
+ | @version() | @VersionColumn() | Optimistic locking/version field. |
624
+ | @createdAt() | @CreateDateColumn() | Auto-managed creation timestamp. |
625
+ | @updatedAt() | @UpdateDateColumn() | Auto-managed update timestamp. |
626
+ | @oneToOne(() => Clazz, cascade, populate, joinColumnOpts?, fkName?) | @OneToOne(() => Clazz, { cascade, onDelete, onUpdate, eager, nullable: true }) + @JoinColumn({ foreignKeyConstraintName: fkName? }) | populate => eager; Cascade.DELETE/UPDATE map to CASCADE, else DEFAULT. Owning side uses JoinColumn. |
627
+ | @manyToOne(() => Clazz, cascade, populate, joinOpts?, fkName?) | @ManyToOne(() => Clazz, { cascade, onDelete, onUpdate, eager, nullable: true }) | Owning side; FK constraint name may be set via metadata.name; JoinColumn is not explicitly added by the adapter (TypeORM will create the FK). |
628
+ | @oneToMany(() => Clazz, cascade, populate, joinOpts?) | @OneToMany(() => Clazz, inversePropertyResolver, { cascade, onDelete, onUpdate, eager, nullable: true }) | Inverse side of many-to-one; no JoinColumn/JoinTable applied. |
629
+ | @manyToMany(() => Clazz, cascade, populate, joinTableOpts?) | @ManyToMany(() => Clazz, { cascade, onDelete, onUpdate, eager, nullable: true }) + @JoinTable(joinTableOpts?) | Owning side applies JoinTable. |
630
+ | @index(directionsOrName?, compositionsOrName?) | @Index() | Single or composite indexes are registered; when compositions are present, Index([prop, ...compositions]). |
53
631
 
54
632
 
55
633
  ### Related