@decaf-ts/for-typeorm 0.0.16 → 0.0.18
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 +596 -18
- package/dist/for-typeorm.cjs +367 -79
- package/dist/for-typeorm.esm.cjs +368 -79
- package/lib/TypeORMAdapter.cjs +29 -27
- package/lib/TypeORMAdapter.d.ts +3 -3
- package/lib/TypeORMDispatch.cjs +25 -30
- package/lib/TypeORMDispatch.d.ts +12 -20
- package/lib/TypeORMRepository.cjs +13 -5
- package/lib/TypeORMRepository.d.ts +14 -8
- package/lib/constants.cjs +7 -1
- package/lib/constants.d.ts +6 -0
- package/lib/decorators.cjs +145 -1
- package/lib/decorators.d.ts +117 -0
- package/lib/esm/TypeORMAdapter.d.ts +3 -3
- package/lib/esm/TypeORMAdapter.js +29 -27
- package/lib/esm/TypeORMDispatch.d.ts +12 -20
- package/lib/esm/TypeORMDispatch.js +25 -30
- package/lib/esm/TypeORMRepository.d.ts +14 -8
- package/lib/esm/TypeORMRepository.js +13 -5
- package/lib/esm/constants.d.ts +6 -0
- package/lib/esm/constants.js +7 -1
- package/lib/esm/decorators.d.ts +117 -0
- package/lib/esm/decorators.js +145 -1
- package/lib/esm/index.d.ts +3 -2
- package/lib/esm/index.js +3 -4
- package/lib/esm/overrides/Column.d.ts +21 -0
- package/lib/esm/overrides/Column.js +4 -1
- package/lib/esm/overrides/CreateDateColumn.d.ts +18 -0
- package/lib/esm/overrides/CreateDateColumn.js +19 -1
- package/lib/esm/overrides/ManyToMany.d.ts +5 -7
- package/lib/esm/overrides/ManyToMany.js +5 -1
- package/lib/esm/overrides/ManyToOne.d.ts +5 -7
- package/lib/esm/overrides/ManyToOne.js +5 -1
- package/lib/esm/overrides/OneToMany.d.ts +4 -0
- package/lib/esm/overrides/OneToMany.js +5 -1
- package/lib/esm/overrides/OneToOne.d.ts +5 -6
- package/lib/esm/overrides/OneToOne.js +5 -1
- package/lib/esm/overrides/PrimaryColumn.d.ts +15 -0
- package/lib/esm/overrides/PrimaryColumn.js +23 -1
- package/lib/esm/overrides/PrimaryGeneratedColumn.d.ts +7 -0
- package/lib/esm/overrides/PrimaryGeneratedColumn.js +21 -1
- package/lib/esm/overrides/UpdateDateColumn.d.ts +18 -0
- package/lib/esm/overrides/UpdateDateColumn.js +19 -1
- package/lib/esm/query/Paginator.js +2 -2
- package/lib/esm/query/Statement.d.ts +1 -2
- package/lib/esm/query/Statement.js +3 -6
- package/lib/esm/sequences/Sequence.js +27 -11
- package/lib/esm/types.d.ts +25 -0
- package/lib/esm/types.js +26 -1
- package/lib/index.cjs +4 -5
- package/lib/index.d.ts +3 -2
- package/lib/overrides/Column.cjs +4 -1
- package/lib/overrides/Column.d.ts +21 -0
- package/lib/overrides/CreateDateColumn.cjs +19 -1
- package/lib/overrides/CreateDateColumn.d.ts +18 -0
- package/lib/overrides/ManyToMany.cjs +5 -1
- package/lib/overrides/ManyToMany.d.ts +5 -7
- package/lib/overrides/ManyToOne.cjs +5 -1
- package/lib/overrides/ManyToOne.d.ts +5 -7
- package/lib/overrides/OneToMany.cjs +5 -1
- package/lib/overrides/OneToMany.d.ts +4 -0
- package/lib/overrides/OneToOne.cjs +5 -1
- package/lib/overrides/OneToOne.d.ts +5 -6
- package/lib/overrides/PrimaryColumn.cjs +23 -1
- package/lib/overrides/PrimaryColumn.d.ts +15 -0
- package/lib/overrides/PrimaryGeneratedColumn.cjs +21 -1
- package/lib/overrides/PrimaryGeneratedColumn.d.ts +7 -0
- package/lib/overrides/UpdateDateColumn.cjs +19 -1
- package/lib/overrides/UpdateDateColumn.d.ts +18 -0
- package/lib/query/Paginator.cjs +2 -2
- package/lib/query/Statement.cjs +3 -6
- package/lib/query/Statement.d.ts +1 -2
- package/lib/sequences/Sequence.cjs +27 -11
- package/lib/types.cjs +26 -1
- package/lib/types.d.ts +25 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
## Typescript Template
|
|
1
|
+
# Decaf.ts — TypeORM Integration
|
|
3
2
|
|
|
4
|
-
|
|
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
|

|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|