@tstdl/base 0.93.139 → 0.93.141

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 (218) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.d.ts +1 -1
  28. package/application/application.js +3 -3
  29. package/application/providers.d.ts +20 -2
  30. package/application/providers.js +34 -7
  31. package/audit/README.md +267 -0
  32. package/audit/module.d.ts +5 -0
  33. package/audit/module.js +9 -1
  34. package/authentication/README.md +288 -0
  35. package/authentication/client/authentication.service.d.ts +12 -11
  36. package/authentication/client/authentication.service.js +21 -21
  37. package/authentication/client/http-client.middleware.js +2 -2
  38. package/authentication/server/module.d.ts +5 -0
  39. package/authentication/server/module.js +9 -1
  40. package/authentication/tests/authentication.api-controller.test.js +1 -1
  41. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  42. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  43. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  44. package/authentication/tests/authentication.client-service.test.js +1 -1
  45. package/browser/README.md +401 -0
  46. package/cancellation/README.md +156 -0
  47. package/cancellation/tests/coverage.test.d.ts +1 -0
  48. package/cancellation/tests/coverage.test.js +49 -0
  49. package/cancellation/tests/leak.test.js +24 -29
  50. package/cancellation/tests/token.test.d.ts +1 -0
  51. package/cancellation/tests/token.test.js +136 -0
  52. package/cancellation/token.d.ts +53 -177
  53. package/cancellation/token.js +132 -208
  54. package/circuit-breaker/postgres/module.d.ts +1 -0
  55. package/circuit-breaker/postgres/module.js +5 -1
  56. package/context/README.md +174 -0
  57. package/cookie/README.md +161 -0
  58. package/css/README.md +157 -0
  59. package/data-structures/README.md +320 -0
  60. package/decorators/README.md +140 -0
  61. package/distributed-loop/README.md +231 -0
  62. package/distributed-loop/distributed-loop.js +1 -1
  63. package/document-management/README.md +403 -0
  64. package/document-management/server/configure.js +5 -1
  65. package/document-management/server/module.d.ts +1 -1
  66. package/document-management/server/module.js +1 -1
  67. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  68. package/document-management/server/services/document-management.service.js +9 -7
  69. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  70. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  71. package/document-management/tests/document-management-core.test.js +2 -7
  72. package/document-management/tests/document-management.api.test.js +6 -7
  73. package/document-management/tests/document-statistics.service.test.js +11 -12
  74. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  75. package/document-management/tests/document.service.test.js +3 -3
  76. package/document-management/tests/enum-helpers.test.js +2 -3
  77. package/dom/README.md +213 -0
  78. package/enumerable/README.md +259 -0
  79. package/enumeration/README.md +121 -0
  80. package/errors/README.md +267 -0
  81. package/examples/document-management/main.d.ts +1 -0
  82. package/examples/document-management/main.js +14 -11
  83. package/file/README.md +191 -0
  84. package/formats/README.md +210 -0
  85. package/function/README.md +144 -0
  86. package/http/README.md +318 -0
  87. package/http/client/adapters/undici.adapter.js +1 -1
  88. package/http/client/http-client-request.d.ts +6 -5
  89. package/http/client/http-client-request.js +8 -9
  90. package/http/server/node/node-http-server.js +1 -2
  91. package/image-service/README.md +137 -0
  92. package/injector/README.md +491 -0
  93. package/intl/README.md +113 -0
  94. package/json-path/README.md +182 -0
  95. package/jsx/README.md +154 -0
  96. package/key-value-store/README.md +191 -0
  97. package/key-value-store/postgres/module.d.ts +1 -0
  98. package/key-value-store/postgres/module.js +5 -1
  99. package/lock/README.md +249 -0
  100. package/lock/postgres/module.d.ts +1 -0
  101. package/lock/postgres/module.js +5 -1
  102. package/lock/web/web-lock.js +119 -47
  103. package/logger/README.md +287 -0
  104. package/mail/README.md +256 -0
  105. package/mail/module.d.ts +5 -1
  106. package/mail/module.js +11 -6
  107. package/memory/README.md +144 -0
  108. package/message-bus/README.md +244 -0
  109. package/message-bus/message-bus-base.js +1 -1
  110. package/module/README.md +182 -0
  111. package/module/module.d.ts +1 -1
  112. package/module/module.js +77 -17
  113. package/module/modules/web-server.module.js +3 -4
  114. package/notification/server/module.d.ts +1 -0
  115. package/notification/server/module.js +5 -1
  116. package/notification/tests/notification-flow.test.js +2 -2
  117. package/notification/tests/notification-type.service.test.js +24 -15
  118. package/object-storage/README.md +300 -0
  119. package/openid-connect/README.md +274 -0
  120. package/orm/README.md +423 -0
  121. package/orm/decorators.d.ts +5 -1
  122. package/orm/decorators.js +1 -1
  123. package/orm/server/drizzle/schema-converter.js +17 -30
  124. package/orm/server/encryption.d.ts +0 -1
  125. package/orm/server/encryption.js +1 -4
  126. package/orm/server/index.d.ts +1 -6
  127. package/orm/server/index.js +1 -6
  128. package/orm/server/migration.d.ts +19 -0
  129. package/orm/server/migration.js +72 -0
  130. package/orm/server/repository.d.ts +1 -1
  131. package/orm/server/transaction.d.ts +5 -10
  132. package/orm/server/transaction.js +22 -26
  133. package/orm/server/transactional.js +3 -3
  134. package/orm/tests/database-migration.test.d.ts +1 -0
  135. package/orm/tests/database-migration.test.js +82 -0
  136. package/orm/tests/encryption.test.js +3 -4
  137. package/orm/utils.d.ts +17 -2
  138. package/orm/utils.js +49 -1
  139. package/package.json +9 -6
  140. package/password/README.md +164 -0
  141. package/pdf/README.md +246 -0
  142. package/polyfills.js +1 -0
  143. package/pool/README.md +198 -0
  144. package/process/README.md +237 -0
  145. package/promise/README.md +252 -0
  146. package/promise/cancelable-promise.js +1 -1
  147. package/random/README.md +193 -0
  148. package/rate-limit/postgres/module.d.ts +1 -0
  149. package/rate-limit/postgres/module.js +5 -1
  150. package/reflection/README.md +305 -0
  151. package/reflection/decorator-data.js +11 -12
  152. package/rpc/README.md +386 -0
  153. package/rxjs-utils/README.md +262 -0
  154. package/schema/README.md +342 -0
  155. package/serializer/README.md +342 -0
  156. package/signals/implementation/README.md +134 -0
  157. package/sse/README.md +278 -0
  158. package/task-queue/README.md +293 -0
  159. package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
  160. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
  161. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  162. package/task-queue/postgres/module.d.ts +1 -0
  163. package/task-queue/postgres/module.js +5 -1
  164. package/task-queue/postgres/schemas.d.ts +9 -6
  165. package/task-queue/postgres/schemas.js +4 -3
  166. package/task-queue/postgres/task-queue.d.ts +4 -13
  167. package/task-queue/postgres/task-queue.js +462 -355
  168. package/task-queue/postgres/task.model.d.ts +12 -5
  169. package/task-queue/postgres/task.model.js +51 -25
  170. package/task-queue/task-context.d.ts +2 -2
  171. package/task-queue/task-context.js +8 -8
  172. package/task-queue/task-queue.d.ts +53 -19
  173. package/task-queue/task-queue.js +121 -55
  174. package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
  175. package/task-queue/tests/cascading-cancellations.test.js +38 -0
  176. package/task-queue/tests/complex.test.js +45 -229
  177. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  178. package/task-queue/tests/coverage-branch.test.js +407 -0
  179. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  180. package/task-queue/tests/coverage-enhancement.test.js +144 -0
  181. package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
  182. package/task-queue/tests/dag-dependencies.test.js +41 -0
  183. package/task-queue/tests/dependencies.test.js +28 -26
  184. package/task-queue/tests/extensive-dependencies.test.js +64 -139
  185. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  186. package/task-queue/tests/fan-out-spawning.test.js +53 -0
  187. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  188. package/task-queue/tests/idempotent-replacement.test.js +61 -0
  189. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  190. package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
  191. package/task-queue/tests/queue.test.js +128 -8
  192. package/task-queue/tests/worker.test.js +39 -16
  193. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  194. package/task-queue/tests/zombie-parent.test.js +45 -0
  195. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  196. package/task-queue/tests/zombie-recovery.test.js +51 -0
  197. package/templates/README.md +287 -0
  198. package/test5.js +5 -5
  199. package/testing/README.md +157 -0
  200. package/testing/integration-setup.d.ts +4 -4
  201. package/testing/integration-setup.js +54 -29
  202. package/text/README.md +346 -0
  203. package/text/localization.service.js +2 -2
  204. package/threading/README.md +238 -0
  205. package/types/README.md +311 -0
  206. package/utils/README.md +322 -0
  207. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  208. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  209. package/utils/async-iterable-helpers/take-until.js +4 -4
  210. package/utils/backoff.js +89 -30
  211. package/utils/file-reader.js +1 -2
  212. package/utils/retry-with-backoff.js +1 -1
  213. package/utils/timer.d.ts +1 -1
  214. package/utils/timer.js +5 -7
  215. package/utils/timing.d.ts +1 -1
  216. package/utils/timing.js +2 -4
  217. package/utils/z-base32.d.ts +1 -0
  218. package/utils/z-base32.js +1 -0
package/orm/README.md ADDED
@@ -0,0 +1,423 @@
1
+ # ORM Module
2
+
3
+ A robust, code-first Object-Relational Mapping (ORM) library for PostgreSQL, built on top of [Drizzle ORM](https://orm.drizzle.team/). It simplifies data access through a repository pattern, provides advanced type-safe querying (including full-text search and ParadeDB integration), and integrates seamlessly with the dependency injection system.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [Entities](#entities)
10
+ - [Repositories](#repositories)
11
+ - [Decorators](#decorators)
12
+ - [Querying](#querying)
13
+ - [Transactions](#transactions)
14
+ - [🚀 Basic Usage](#-basic-usage)
15
+ - [1. Configuration](#1-configuration)
16
+ - [2. Defining Entities](#2-defining-entities)
17
+ - [3. Using Repositories](#3-using-repositories)
18
+ - [🔧 Advanced Topics](#-advanced-topics)
19
+ - [Full-Text Search](#full-text-search)
20
+ - [ParadeDB Integration](#paradedb-integration)
21
+ - [Transparent Encryption](#transparent-encryption)
22
+ - [Automatic Expiration (TTL)](#automatic-expiration-ttl)
23
+ - [Embedded Properties](#embedded-properties)
24
+ - [Transactions](#transaction-management)
25
+ - [Soft Deletes](#soft-deletes)
26
+ - [SQL Helper Functions](#sql-helper-functions)
27
+ - [📚 API](#-api)
28
+
29
+ ## ✨ Features
30
+
31
+ - **Code-First Schema**: Define database tables, columns, and relationships using TypeScript classes and decorators.
32
+ - **Repository Pattern**: Standardized API for CRUD operations, bulk actions, and complex querying.
33
+ - **Type-Safe Querying**: MongoDB-like query syntax (`$eq`, `$gt`, `$in`, `$or`) fully typed to your entities.
34
+ - **Advanced Search**: Built-in support for PostgreSQL `tsvector`, `pg_trgm`, and ParadeDB (`bm25`) full-text search.
35
+ - **Transparent Encryption**: Encrypt sensitive columns automatically at the application level using AES-GCM.
36
+ - **Automatic Expiration**: Built-in Time-To-Live (TTL) support for auto-deleting old records.
37
+ - **Transaction Management**: Robust transaction handling with automatic commit/rollback and context propagation.
38
+ - **Soft Deletes**: Built-in support for soft deletion when using the standard `Entity` base class.
39
+ - **Dependency Injection**: Seamless integration with `@tstdl/base/injector`.
40
+
41
+ ## Core Concepts
42
+
43
+ ### Entities
44
+
45
+ Entities are classes that map directly to database tables. The module provides base classes to standardize common fields:
46
+
47
+ - **`Entity`**: The standard base class. Includes an `id` (UUIDv7 primary key) and metadata fields: `revision`, `createTimestamp`, `deleteTimestamp` (for soft deletes), and `attributes` (JSONB).
48
+ - **`BaseEntity`**: A minimal base class providing only the `id` primary key. Useful for join tables or simple configuration data.
49
+ - **`TenantEntity`**: Extends `Entity` with a `tenantId` column for multi-tenant applications.
50
+
51
+ ### Repositories
52
+
53
+ Repositories provide the interface to the database.
54
+
55
+ - **`EntityRepository<T>`**: The generic class providing methods like `load`, `insert`, `update`, `search`, etc.
56
+ - **`injectRepository(Entity)`**: Helper to inject the default repository for an entity.
57
+ - **`getRepository(Entity)`**: Helper to create a custom repository class extending the base functionality.
58
+
59
+ ### Decorators
60
+
61
+ Decorators configure the mapping between TypeScript properties and database columns/constraints.
62
+
63
+ - **Class**: `@Table`, `@Index`, `@Unique`, `@Check`, `@ParadeIndex`, `@TimeToLive`.
64
+ - **Property**: `@StringProperty`, `@Integer`, `@UuidProperty`, `@TimestampProperty`, `@EncryptedProperty`, `@References`, `@EmbeddedProperty`, `@GeneratedTsVector`, `@TrigramIndex`.
65
+
66
+ ### Querying
67
+
68
+ The ORM uses a structured query object syntax instead of raw SQL builders for most operations.
69
+
70
+ - **Comparison**: `$eq`, `$neq`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$regex`.
71
+ - **Logical**: `$and`, `$or`, `$nor`, `$not`.
72
+ - **Search**: `$tsvector`, `$trigram`, `$parade`.
73
+
74
+ ### Transactions
75
+
76
+ The `Transactional` base class allows services to manage transactions easily.
77
+
78
+ - **`this.transaction(async (tx) => { ... })`**: Starts a transaction scope.
79
+ - **`repository.withTransaction(tx)`**: Binds a repository to an active transaction.
80
+
81
+ ## 🚀 Basic Usage
82
+
83
+ ### 1. Configuration
84
+
85
+ Configure the ORM in your application bootstrap.
86
+
87
+ ```typescript
88
+ import { configureOrm } from '@tstdl/base/orm/server';
89
+
90
+ configureOrm({
91
+ connection: {
92
+ host: 'localhost',
93
+ port: 5432,
94
+ user: 'postgres',
95
+ password: 'password',
96
+ database: 'my_app',
97
+ },
98
+ // Optional: Secret for column encryption (32 bytes)
99
+ encryptionSecret: new Uint8Array([
100
+ /* ... 32 bytes ... */
101
+ ]),
102
+ });
103
+ ```
104
+
105
+ ### 2. Defining Entities
106
+
107
+ Define your data model using classes and decorators. Note that standard schema decorators (like `@StringProperty`) are combined with ORM-specific decorators.
108
+
109
+ ```typescript
110
+ import { Entity, Table, Unique, References } from '@tstdl/base/orm';
111
+ import { UuidProperty } from '@tstdl/base/orm/schemas';
112
+ import { StringProperty } from '@tstdl/base/schema';
113
+
114
+ @Table({ name: 'users' })
115
+ export class User extends Entity {
116
+ @StringProperty()
117
+ name: string;
118
+
119
+ @StringProperty()
120
+ @Unique()
121
+ email: string;
122
+ }
123
+
124
+ @Table({ name: 'posts' })
125
+ export class Post extends Entity {
126
+ @StringProperty()
127
+ title: string;
128
+
129
+ @UuidProperty()
130
+ @References(() => User)
131
+ authorId: string;
132
+ }
133
+ ```
134
+
135
+ ### 3. Using Repositories
136
+
137
+ Inject repositories into your services to interact with the database.
138
+
139
+ ```typescript
140
+ import { Singleton } from '@tstdl/base/injector';
141
+ import { injectRepository, Transactional } from '@tstdl/base/orm/server';
142
+ import { User } from './user.model.js';
143
+
144
+ @Singleton()
145
+ export class UserService extends Transactional {
146
+ // Inject the default repository for User
147
+ readonly #userRepository = injectRepository(User);
148
+
149
+ async createUser(name: string, email: string): Promise<User> {
150
+ // Insert a new entity
151
+ return await this.#userRepository.insert({
152
+ name,
153
+ email,
154
+ });
155
+ }
156
+
157
+ async findByEmail(email: string): Promise<User | undefined> {
158
+ // Query using the object syntax
159
+ return await this.#userRepository.tryLoadByQuery({
160
+ email: { $eq: email },
161
+ });
162
+ }
163
+
164
+ async updateName(id: string, newName: string): Promise<void> {
165
+ // Update specific fields
166
+ await this.#userRepository.update(id, {
167
+ name: newName,
168
+ });
169
+ }
170
+ }
171
+ ```
172
+
173
+ ## 🔧 Advanced Topics
174
+
175
+ ### Full-Text Search
176
+
177
+ The ORM supports PostgreSQL's native full-text search (`tsvector`) and trigram similarity (`pg_trgm`).
178
+
179
+ ```typescript
180
+ import { Entity, Table, GeneratedTsVector } from '@tstdl/base/orm';
181
+ import { StringProperty } from '@tstdl/base/schema';
182
+
183
+ @Table({ name: 'articles' })
184
+ export class Article extends Entity {
185
+ @StringProperty()
186
+ title: string;
187
+
188
+ @StringProperty()
189
+ content: string;
190
+
191
+ // Automatically generated tsvector column combining title (weight A) and content (weight B)
192
+ @GeneratedTsVector({
193
+ sources: ['title', 'content'],
194
+ weights: { title: 'A', content: 'B' },
195
+ language: 'english',
196
+ })
197
+ searchVector: string;
198
+ }
199
+
200
+ // In your service:
201
+ const results = await articleRepository.search({
202
+ query: {
203
+ $tsvector: {
204
+ fields: ['searchVector'],
205
+ query: 'typescript & orm',
206
+ language: 'english',
207
+ },
208
+ },
209
+ highlight: 'content', // Optional: highlight matches in content
210
+ });
211
+ ```
212
+
213
+ ### ParadeDB Integration
214
+
215
+ If you are using [ParadeDB](https://www.paradedb.com/), you can use the `@ParadeIndex` decorator for BM25 relevance scoring and advanced search capabilities.
216
+
217
+ ```typescript
218
+ import { Entity, Table, ParadeIndex } from '@tstdl/base/orm';
219
+ import { StringProperty } from '@tstdl/base/schema';
220
+
221
+ @Table({ name: 'products' })
222
+ @ParadeIndex({
223
+ // Define a BM25 index on these columns
224
+ columns: ['name', 'description'],
225
+ })
226
+ export class Product extends Entity {
227
+ @StringProperty()
228
+ name: string;
229
+
230
+ @StringProperty()
231
+ description: string;
232
+ }
233
+
234
+ // Searching with ParadeDB
235
+ const results = await productRepository.search({
236
+ query: {
237
+ $parade: {
238
+ fields: ['name', 'description'],
239
+ query: 'shoes OR boots',
240
+ distance: 1, // Fuzzy matching
241
+ },
242
+ },
243
+ });
244
+ ```
245
+
246
+ ### Transparent Encryption
247
+
248
+ Encrypt sensitive data at rest using the `@EncryptedProperty` decorator. The data is automatically encrypted on insert/update and decrypted on load. The underlying database column type will be `bytea`.
249
+
250
+ ```typescript
251
+ import { Entity, EncryptedProperty } from '@tstdl/base/orm';
252
+ import { StringProperty } from '@tstdl/base/schema';
253
+
254
+ export class UserSecret extends Entity {
255
+ @StringProperty()
256
+ @EncryptedProperty()
257
+ apiKey: string;
258
+ }
259
+ ```
260
+
261
+ ### Automatic Expiration (TTL)
262
+
263
+ Automatically delete records after a specified duration. This relies on the `createTimestamp` field of `Entity` or a specific property marked with `@Expires`.
264
+
265
+ ```typescript
266
+ import { Entity, TimeToLive, Expires } from '@tstdl/base/orm';
267
+ import { TimestampProperty } from '@tstdl/base/orm/schemas';
268
+
269
+ // Soft delete records 24 hours after creation
270
+ @TimeToLive(24 * 60 * 60 * 1000, 'soft')
271
+ export class Session extends Entity {
272
+ // ...
273
+ }
274
+
275
+ // Or expire based on a specific column
276
+ export class ApiKey extends Entity {
277
+ @TimestampProperty()
278
+ @Expires({ after: 0, mode: 'hard' }) // Hard delete immediately when validUntil is reached
279
+ validUntil: number;
280
+ }
281
+ ```
282
+
283
+ ### Embedded Properties
284
+
285
+ Group related columns into a reusable class and embed them into entities. This flattens the structure in the database table but keeps it structured in your code.
286
+
287
+ ```typescript
288
+ import { Entity, EmbeddedProperty } from '@tstdl/base/orm';
289
+ import { StringProperty } from '@tstdl/base/schema';
290
+
291
+ class Address {
292
+ @StringProperty()
293
+ street: string;
294
+
295
+ @StringProperty()
296
+ city: string;
297
+ }
298
+
299
+ export class User extends Entity {
300
+ @EmbeddedProperty(Address, { prefix: 'billing_' })
301
+ billingAddress: Address; // DB Columns: billing_street, billing_city
302
+
303
+ @EmbeddedProperty(Address, { prefix: 'shipping_' })
304
+ shippingAddress: Address; // DB Columns: shipping_street, shipping_city
305
+ }
306
+ ```
307
+
308
+ ### Transaction Management
309
+
310
+ Services extending `Transactional` can manage transactions. The repository automatically participates in the active transaction context.
311
+
312
+ ```typescript
313
+ @Singleton()
314
+ export class OrderService extends Transactional {
315
+ readonly #orderRepository = injectRepository(Order);
316
+ readonly #itemRepository = injectRepository(OrderItem);
317
+
318
+ async createOrder(data: OrderData): Promise<void> {
319
+ // Start a transaction scope
320
+ await this.transaction(async (tx) => {
321
+ // Repositories automatically use the transaction 'tx'
322
+ // because they are accessed within the scope of 'this.transaction'
323
+ // and injected via injectRepository which handles context propagation.
324
+
325
+ // Alternatively, explicitly bind:
326
+ // const txRepo = this.#orderRepository.withTransaction(tx);
327
+
328
+ const order = await this.#orderRepository.insert({ ... });
329
+ await this.#itemRepository.insertMany(data.items.map(i => ({ ...i, orderId: order.id })));
330
+
331
+ // If an error is thrown here, everything is rolled back.
332
+ });
333
+ }
334
+ }
335
+ ```
336
+
337
+ ### Soft Deletes
338
+
339
+ When using the `Entity` base class, the `delete` and `deleteByQuery` methods perform a soft delete by setting the `deleteTimestamp`. The `load` and `loadByQuery` methods automatically filter out soft-deleted records.
340
+
341
+ To permanently remove records, use `hardDelete` or `hardDeleteByQuery`.
342
+
343
+ ### SQL Helper Functions
344
+
345
+ The module exports SQL helpers in `@tstdl/base/orm/sqls` for complex queries.
346
+
347
+ ```typescript
348
+ import { sql } from 'drizzle-orm';
349
+ import { interval, TRANSACTION_TIMESTAMP } from '@tstdl/base/orm/sqls';
350
+
351
+ // Find records created in the last 7 days
352
+ const recent = await repository.loadManyByQuery({
353
+ createTimestamp: { $gt: sql`${TRANSACTION_TIMESTAMP} - ${interval(7, 'days')}` },
354
+ });
355
+ ```
356
+
357
+ ## 📚 API
358
+
359
+ ### Decorators
360
+
361
+ | Decorator | Target | Description |
362
+ | :----------------------------------- | :--------- | :------------------------------------------------------ |
363
+ | `@Table({ name?, schema? })` | Class | Configures table name and schema. |
364
+ | `@Unique(columns?, options?)` | Class/Prop | Defines unique constraints. |
365
+ | `@Index(columns?, options?)` | Class/Prop | Defines database indexes. |
366
+ | `@Check(name, builder)` | Class | Defines a SQL CHECK constraint. |
367
+ | `@ForeignKey(...)` | Class | Defines a foreign key constraint. |
368
+ | `@ParadeIndex(options)` | Class/Prop | Configures a ParadeDB BM25 index. |
369
+ | `@TimeToLive(ttl, mode)` | Class | Sets TTL for automatic deletion based on creation time. |
370
+ | `@PrimaryKeyProperty()` | Prop | Marks the primary key column. |
371
+ | `@References(target)` | Prop | Defines a foreign key relationship. |
372
+ | `@EncryptedProperty()` | Prop | Marks column for encryption (`bytea`). |
373
+ | `@EmbeddedProperty(type)` | Prop | Embeds another class's properties. |
374
+ | `@GeneratedTsVector(opts)` | Prop | Creates a generated `tsvector` column. |
375
+ | `@TrigramIndex(opts)` | Prop | Creates a GIN/GIST trigram index. |
376
+ | `@Expires(opts)` | Prop | Sets expiration based on the property value. |
377
+ | `@UuidProperty()` | Prop | Maps to `uuid` column. |
378
+ | `@TimestampProperty()` | Prop | Maps to `timestamp with time zone` (number). |
379
+ | `@NumericDateProperty()` | Prop | Maps to `date` (number YYYYMMDD). |
380
+ | `@JsonProperty()` | Prop | Maps to `jsonb` column. |
381
+ | `@NumericProperty(precision, scale)` | Prop | Maps to `numeric` column. |
382
+ | `@TsVectorProperty()` | Prop | Maps to `tsvector` column. |
383
+
384
+ ### Repository Methods
385
+
386
+ | Method | Description |
387
+ | :---------------------------------- | :------------------------------------------------------- |
388
+ | `load(id)` | Loads an entity by ID. Throws if not found. |
389
+ | `tryLoad(id)` | Loads an entity by ID. Returns `undefined` if not found. |
390
+ | `loadByQuery(query)` | Loads first match. Throws if not found. |
391
+ | `tryLoadByQuery(query)` | Loads first match. Returns `undefined` if not found. |
392
+ | `loadMany(ids)` | Loads multiple entities by ID. |
393
+ | `loadManyByQuery(query)` | Loads all matches. |
394
+ | `loadAll()` | Loads all entities in the table. |
395
+ | `insert(entity)` | Inserts a new entity. |
396
+ | `insertMany(entities)` | Inserts multiple entities. |
397
+ | `insertIfNotExists(target, entity)` | Inserts if no conflict on target columns. |
398
+ | `update(id, update)` | Updates an entity by ID. |
399
+ | `updateByQuery(query, update)` | Updates entities matching query. |
400
+ | `upsert(target, entity)` | Inserts or updates on conflict. |
401
+ | `delete(id)` | Soft deletes (if supported) or hard deletes by ID. |
402
+ | `deleteByQuery(query)` | Soft deletes matching entity. |
403
+ | `hardDelete(id)` | Permanently removes the record. |
404
+ | `search(options)` | Performs full-text search (TsVector, Trigram, Parade). |
405
+ | `count()` | Returns total count of entities. |
406
+ | `countByQuery(query)` | Returns the count of matching records. |
407
+ | `has(id)` | Checks if an ID exists. |
408
+ | `transaction(handler)` | Executes handler in a transaction. |
409
+
410
+ ### Query Operators
411
+
412
+ | Operator | Description |
413
+ | :---------------------- | :----------------------------------- |
414
+ | `$eq` / `$neq` | Equal / Not Equal |
415
+ | `$gt` / `$gte` | Greater Than / Greater Than or Equal |
416
+ | `$lt` / `$lte` | Less Than / Less Than or Equal |
417
+ | `$in` / `$nin` | In Array / Not In Array |
418
+ | `$regex` | Regular Expression match |
419
+ | `$and` / `$or` / `$nor` | Logical operators |
420
+ | `$not` | Negation |
421
+ | `$tsvector` | PostgreSQL Text Search |
422
+ | `$trigram` | Trigram Similarity Search |
423
+ | `$parade` | ParadeDB Search |
@@ -3,7 +3,7 @@
3
3
  * Defines decorators for ORM entities and columns, used to configure database schema mapping.
4
4
  */
5
5
  import type { SQL } from 'drizzle-orm';
6
- import type { ExtraConfigColumn } from 'drizzle-orm/pg-core';
6
+ import type { ExtraConfigColumn, UpdateDeleteAction } from 'drizzle-orm/pg-core';
7
7
  import type { LiteralUnion, SetRequired } from 'type-fest';
8
8
  import { type SpecificCreateDecoratorOptions } from '../reflection/index.js';
9
9
  import type { AbstractConstructor, Record, TypedOmit } from '../types/index.js';
@@ -87,6 +87,8 @@ type ReferenceReflectionData<T extends AnyEntity = AnyEntity> = {
87
87
  target: () => EntityType<T>;
88
88
  targetColumn?: TargetColumnPath<T>;
89
89
  excludeTenant?: boolean;
90
+ onDelete?: UpdateDeleteAction;
91
+ onUpdate?: UpdateDeleteAction;
90
92
  };
91
93
  /**
92
94
  * Reflection data for unique constraints.
@@ -122,6 +124,8 @@ export type ForeignKeyReflectionData = {
122
124
  options?: {
123
125
  name?: string;
124
126
  naming?: NamingStrategy;
127
+ onDelete?: UpdateDeleteAction;
128
+ onUpdate?: UpdateDeleteAction;
125
129
  };
126
130
  };
127
131
  export type IndexOptions<T extends BaseEntity> = {
package/orm/decorators.js CHANGED
@@ -71,7 +71,7 @@ export function PrimaryKeyProperty() {
71
71
  export function Reference(target, targetColumnOrOptions) {
72
72
  const targetColumn = (isString(targetColumnOrOptions) ? targetColumnOrOptions : targetColumnOrOptions?.targetColumn);
73
73
  const options = isString(targetColumnOrOptions) ? undefined : targetColumnOrOptions;
74
- return createColumnDecorator({ references: [{ target, targetColumn, excludeTenant: options?.excludeTenant }] });
74
+ return createColumnDecorator({ references: [{ target, targetColumn, excludeTenant: options?.excludeTenant, onDelete: options?.onDelete, onUpdate: options?.onUpdate }] });
75
75
  }
76
76
  export {
77
77
  /** @deprecated use {@link Reference} instead. */
@@ -15,13 +15,13 @@ import { typeExtends } from '../../../utils/index.js';
15
15
  import { merge } from '../../../utils/merge.js';
16
16
  import { compileDereferencer } from '../../../utils/object/dereference.js';
17
17
  import { fromEntries, mapObjectKeysToSnakeCase, objectEntries } from '../../../utils/object/object.js';
18
- import { assertDefined, assertDefinedPass, isArray, isDefined, isNotNull, isNotNullOrUndefined, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
18
+ import { assertDefined, isArray, isDefined, isNotNull, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
19
19
  import { resolveValueOrProvider } from '../../../utils/value-or-provider.js';
20
20
  import { bytea, numericDate, timestamp, tsvector } from '../../data-types/index.js';
21
21
  import { TenantBaseEntity, TenantEntity } from '../../entity.js';
22
22
  import { getEnumName } from '../../enums.js';
23
23
  import { JsonSchema, NumericDateSchema, NumericSchema, TimestampSchema, TsVectorSchema, UuidSchema } from '../../schemas/index.js';
24
- import { getInheritanceMetadata, isChildEntity } from '../../utils.js';
24
+ import { getEntitySchema, getEntityTableName, getInheritanceMetadata, getTableReflectionDatas, isChildEntity } from '../../utils.js';
25
25
  import { decryptBytes, encryptBytes } from '../encryption.js';
26
26
  import { convertQuery, resolveTargetColumn, resolveTargetColumns } from '../query-converter.js';
27
27
  const getDbSchema = memoizeSingle(pgSchema);
@@ -42,8 +42,8 @@ export function getColumnDefinitionsMap(table) {
42
42
  export function _getDrizzleTableFromType(type, fallbackSchemaName) {
43
43
  const tableReflectionDatas = getTableReflectionDatas(type);
44
44
  const mergedTableReflectionData = tableReflectionDatas.reduceRight((merged, data) => ({ ...merged, ...data }), {});
45
- const schema = assertDefinedPass(mergedTableReflectionData.schema ?? fallbackSchemaName, 'Table schema not provided');
46
- const tableName = getTableName(type);
45
+ const schema = getEntitySchema(type, fallbackSchemaName);
46
+ const tableName = getEntityTableName(type);
47
47
  const dbSchema = getDbSchema(schema);
48
48
  const inheritanceMetadata = getInheritanceMetadata(type);
49
49
  const allColumnDefinitions = getPostgresColumnEntries(type, dbSchema, tableName, undefined, '', { filterInherited: false }, type);
@@ -130,7 +130,7 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
130
130
  if (isDefined(inheritanceMetadata) && isDefined(childEntityMetadata)) {
131
131
  const hasTenantId = isDefined(table['tenantId']);
132
132
  constraints.push(check(getIdentifier(tableName, discriminatorColumn, 'check'), eq(table[discriminatorColumn], sql.raw(`'${childEntityMetadata.discriminatorValue}'`))), foreignKey({
133
- name: getForeignKeyName(tableName, getTableName(reflectionRegistry.getMetadata(type).parent), [...(hasTenantId ? ['tenantId'] : []), discriminatorColumn, 'id']),
133
+ name: getForeignKeyName(tableName, getEntityTableName(reflectionRegistry.getMetadata(type).parent), [...(hasTenantId ? ['tenantId'] : []), discriminatorColumn, 'id']),
134
134
  columns: [
135
135
  ...(hasTenantId ? [table['tenantId']] : []),
136
136
  table[discriminatorColumn],
@@ -247,7 +247,7 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
247
247
  const foreignTable = getDrizzleTableFromType(tenantReferenceData.target(), dbSchema.schemaName);
248
248
  const nonTenantColumn = tenantReferenceData.targetColumn ?? 'id';
249
249
  return foreignKey({
250
- name: getForeignKeyName(tableName, getTableName(tenantReferenceData.target()), [nonTenantColumn]),
250
+ name: getForeignKeyName(tableName, getEntityTableName(tenantReferenceData.target()), [nonTenantColumn]),
251
251
  columns: [getColumn(table, 'tenantId'), getColumn(table, columnDefinition.name)],
252
252
  foreignColumns: [getColumn(foreignTable, 'tenantId'), getColumn(foreignTable, nonTenantColumn)],
253
253
  });
@@ -257,11 +257,18 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
257
257
  return tableReflectionData.foreignKeys?.map((foreignKeyData) => {
258
258
  const foreignKeyTarget = foreignKeyData.target();
259
259
  const foreignTable = getDrizzleTableFromType(foreignKeyTarget, dbSchema.schemaName);
260
- return foreignKey({
261
- name: foreignKeyData.options?.name ?? getForeignKeyName(tableName, getTableName(foreignKeyData.target()), foreignKeyData.columns, { naming: foreignKeyData.options?.naming }),
260
+ let builder = foreignKey({
261
+ name: foreignKeyData.options?.name ?? getForeignKeyName(tableName, getEntityTableName(foreignKeyData.target()), foreignKeyData.columns, { naming: foreignKeyData.options?.naming }),
262
262
  columns: foreignKeyData.columns.map((column) => getColumn(table, column)),
263
263
  foreignColumns: foreignKeyData.foreignColumns.map((column) => getColumn(foreignTable, column)),
264
264
  });
265
+ if (isDefined(foreignKeyData.options?.onDelete)) {
266
+ builder = builder.onDelete(foreignKeyData.options.onDelete);
267
+ }
268
+ if (isDefined(foreignKeyData.options?.onUpdate)) {
269
+ builder = builder.onUpdate(foreignKeyData.options.onUpdate);
270
+ }
271
+ return builder;
265
272
  }) ?? [];
266
273
  }),
267
274
  ...tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.unique).filter(isDefined).map((data) => {
@@ -398,7 +405,7 @@ function getPostgresColumn(tableName, columnName, dbSchema, propertySchema, refl
398
405
  if (((reflectionData.primaryKey == true) || (context.property == 'id' && reflectionData.primaryKey != false)) && (options.skipPrimaryKey != true)) {
399
406
  column = column.primaryKey();
400
407
  }
401
- for (const { target, targetColumn, excludeTenant } of reflectionData.references ?? []) {
408
+ for (const { target, targetColumn, excludeTenant, onDelete, onUpdate } of reflectionData.references ?? []) {
402
409
  column = column.references(() => {
403
410
  const targetType = target();
404
411
  if ((excludeTenant != true) && (typeExtends(context.type, TenantBaseEntity) || typeExtends(context.type, TenantEntity)) && (typeExtends(targetType, TenantBaseEntity) || typeExtends(targetType, TenantEntity))) {
@@ -406,7 +413,7 @@ function getPostgresColumn(tableName, columnName, dbSchema, propertySchema, refl
406
413
  }
407
414
  const targetTable = getDrizzleTableFromType(targetType, dbSchema.schemaName);
408
415
  return targetTable[(targetColumn ?? 'id')];
409
- });
416
+ }, { onDelete, onUpdate });
410
417
  }
411
418
  return column;
412
419
  }
@@ -462,9 +469,6 @@ export function getPgEnum(schema, enumeration, context) {
462
469
  }
463
470
  return dbEnum;
464
471
  }
465
- function getDefaultTableName(type) {
466
- return toSnakeCase(isString(type.entityName) ? type.entityName : type.name.replace(/\d+$/u, ''));
467
- }
468
472
  function getPrimaryKeyName(tableName, columnsOrBaseName, options) {
469
473
  return getIdentifier(tableName, columnsOrBaseName, 'pk', options);
470
474
  }
@@ -486,23 +490,6 @@ function getForeignKeyName(tableName, foreignTableName, columnsOrBaseName, optio
486
490
  }
487
491
  return identifier;
488
492
  }
489
- function getTableName(type) {
490
- const tableReflectionDatas = getTableReflectionDatas(type);
491
- const tableReflectionData = tableReflectionDatas[0];
492
- return tableReflectionData?.name ?? getDefaultTableName(type);
493
- }
494
- function getTableReflectionDatas(type) {
495
- const metadata = reflectionRegistry.getMetadata(type);
496
- assertDefined(metadata, `Type ${type.name} does not have reflection metadata.`);
497
- const tableReflectionDatas = [];
498
- for (let currentMetadata = metadata; isNotNullOrUndefined(currentMetadata?.parent); currentMetadata = reflectionRegistry.getMetadata(currentMetadata.parent)) {
499
- const tableReflectionData = currentMetadata.data.tryGet('orm');
500
- if (isDefined(tableReflectionData)) {
501
- tableReflectionDatas.push(tableReflectionData);
502
- }
503
- }
504
- return tableReflectionDatas;
505
- }
506
493
  function getIdentifier(tableName, columnsOrBaseName, suffix, options) {
507
494
  const middle = isString(columnsOrBaseName) ? columnsOrBaseName : getColumnNames(columnsOrBaseName).join('_');
508
495
  const identifier = `${getTablePrefix(tableName, options?.naming)}_${middle}_${suffix}`;
@@ -12,7 +12,6 @@ export declare function encryptBytes(bytes: Uint8Array<ArrayBuffer>, key: Crypto
12
12
  * @param bytes The byte array to decrypt (must include version and IV).
13
13
  * @param key The CryptoKey to use for decryption.
14
14
  * @returns A promise that resolves to the original decrypted byte array.
15
- * @throws {DetailsError} If decryption fails (e.g., wrong key, corrupted data).
16
15
  * @throws {Error} If the encryption version is invalid.
17
16
  */
18
17
  export declare function decryptBytes(bytes: Uint8Array, key: CryptoKey): Promise<Uint8Array<ArrayBuffer>>;
@@ -3,7 +3,6 @@
3
3
  * Provides utility functions for encrypting and decrypting byte arrays using AES-GCM.
4
4
  * It includes versioning to handle potential future changes in the encryption format.
5
5
  */
6
- import { DetailsError } from '../../errors/index.js';
7
6
  import { decrypt, encrypt } from '../../utils/cryptography.js';
8
7
  import { getRandomBytes } from '../../utils/random.js';
9
8
  import { assert } from '../../utils/type-guards.js';
@@ -36,7 +35,6 @@ export async function encryptBytes(bytes, key) {
36
35
  * @param bytes The byte array to decrypt (must include version and IV).
37
36
  * @param key The CryptoKey to use for decryption.
38
37
  * @returns A promise that resolves to the original decrypted byte array.
39
- * @throws {DetailsError} If decryption fails (e.g., wrong key, corrupted data).
40
38
  * @throws {Error} If the encryption version is invalid.
41
39
  */
42
40
  export async function decryptBytes(bytes, key) {
@@ -49,7 +47,6 @@ export async function decryptBytes(bytes, key) {
49
47
  return new Uint8Array(decrypted);
50
48
  }
51
49
  catch (error) {
52
- // Wrap decryption errors for better context
53
- throw new DetailsError('Decrypt error', error);
50
+ throw new Error('Decryption failed.', { cause: error });
54
51
  }
55
52
  }
@@ -1,11 +1,6 @@
1
- /**
2
- * @module
3
- * Barrel file exporting core server-side ORM functionalities.
4
- * Includes database connection, schema management, repositories, transactions,
5
- * and query conversion utilities.
6
- */
7
1
  export * from './database-schema.js';
8
2
  export * from './database.js';
3
+ export * from './migration.js';
9
4
  export * from './module.js';
10
5
  export * from './query-converter.js';
11
6
  export * from './repository-config.js';
@@ -1,11 +1,6 @@
1
- /**
2
- * @module
3
- * Barrel file exporting core server-side ORM functionalities.
4
- * Includes database connection, schema management, repositories, transactions,
5
- * and query conversion utilities.
6
- */
7
1
  export * from './database-schema.js';
8
2
  export * from './database.js';
3
+ export * from './migration.js';
9
4
  export * from './module.js';
10
5
  export * from './query-converter.js';
11
6
  export * from './repository-config.js';
@@ -0,0 +1,19 @@
1
+ import { Injector, type ProvidersItem } from '../../injector/index.js';
2
+ export type DatabaseMigration = {
3
+ name: string;
4
+ migrate: () => void | Promise<void>;
5
+ dependencies?: string[];
6
+ };
7
+ export declare const DATABASE_MIGRATION: import("../../injector/index.js").InjectionToken<DatabaseMigration, never>;
8
+ /**
9
+ * Registers a database migration in the provided injector.
10
+ * @param name - The unique name of the migration.
11
+ * @param migrate - The migration function.
12
+ * @param options - Optional injector and dependencies.
13
+ */
14
+ export declare function registerDatabaseMigration(name: string, migrate: () => void | Promise<void>, { injector, dependencies }?: {
15
+ injector?: Injector;
16
+ dependencies?: string[];
17
+ }): void;
18
+ export declare function provideDatabaseMigrator(): ProvidersItem;
19
+ export declare function runDatabaseMigrations(): Promise<void>;