@tstdl/base 0.93.139 → 0.93.140

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 (133) 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.js +2 -2
  28. package/audit/README.md +267 -0
  29. package/authentication/README.md +288 -0
  30. package/authentication/client/authentication.service.d.ts +12 -11
  31. package/authentication/client/authentication.service.js +21 -21
  32. package/authentication/client/http-client.middleware.js +2 -2
  33. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  34. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  35. package/browser/README.md +401 -0
  36. package/cancellation/README.md +156 -0
  37. package/cancellation/tests/coverage.test.d.ts +1 -0
  38. package/cancellation/tests/coverage.test.js +49 -0
  39. package/cancellation/tests/leak.test.js +24 -29
  40. package/cancellation/tests/token.test.d.ts +1 -0
  41. package/cancellation/tests/token.test.js +136 -0
  42. package/cancellation/token.d.ts +53 -177
  43. package/cancellation/token.js +132 -208
  44. package/context/README.md +174 -0
  45. package/cookie/README.md +161 -0
  46. package/css/README.md +157 -0
  47. package/data-structures/README.md +320 -0
  48. package/decorators/README.md +140 -0
  49. package/distributed-loop/README.md +231 -0
  50. package/distributed-loop/distributed-loop.js +1 -1
  51. package/document-management/README.md +403 -0
  52. package/document-management/server/services/document-management.service.js +9 -7
  53. package/document-management/tests/document-management-core.test.js +2 -7
  54. package/document-management/tests/document-management.api.test.js +6 -7
  55. package/document-management/tests/document-statistics.service.test.js +11 -12
  56. package/document-management/tests/document.service.test.js +3 -3
  57. package/document-management/tests/enum-helpers.test.js +2 -3
  58. package/dom/README.md +213 -0
  59. package/enumerable/README.md +259 -0
  60. package/enumeration/README.md +121 -0
  61. package/errors/README.md +267 -0
  62. package/file/README.md +191 -0
  63. package/formats/README.md +210 -0
  64. package/function/README.md +144 -0
  65. package/http/README.md +318 -0
  66. package/http/client/adapters/undici.adapter.js +1 -1
  67. package/http/client/http-client-request.d.ts +6 -5
  68. package/http/client/http-client-request.js +8 -9
  69. package/http/server/node/node-http-server.js +1 -2
  70. package/image-service/README.md +137 -0
  71. package/injector/README.md +491 -0
  72. package/intl/README.md +113 -0
  73. package/json-path/README.md +182 -0
  74. package/jsx/README.md +154 -0
  75. package/key-value-store/README.md +191 -0
  76. package/lock/README.md +249 -0
  77. package/lock/web/web-lock.js +119 -47
  78. package/logger/README.md +287 -0
  79. package/mail/README.md +256 -0
  80. package/memory/README.md +144 -0
  81. package/message-bus/README.md +244 -0
  82. package/message-bus/message-bus-base.js +1 -1
  83. package/module/README.md +182 -0
  84. package/module/module.d.ts +1 -1
  85. package/module/module.js +77 -17
  86. package/module/modules/web-server.module.js +1 -1
  87. package/notification/tests/notification-type.service.test.js +24 -15
  88. package/object-storage/README.md +300 -0
  89. package/openid-connect/README.md +274 -0
  90. package/orm/README.md +423 -0
  91. package/package.json +8 -6
  92. package/password/README.md +164 -0
  93. package/pdf/README.md +246 -0
  94. package/polyfills.js +1 -0
  95. package/pool/README.md +198 -0
  96. package/process/README.md +237 -0
  97. package/promise/README.md +252 -0
  98. package/promise/cancelable-promise.js +1 -1
  99. package/random/README.md +193 -0
  100. package/reflection/README.md +305 -0
  101. package/rpc/README.md +386 -0
  102. package/rxjs-utils/README.md +262 -0
  103. package/schema/README.md +342 -0
  104. package/serializer/README.md +342 -0
  105. package/signals/implementation/README.md +134 -0
  106. package/sse/README.md +278 -0
  107. package/task-queue/README.md +300 -0
  108. package/task-queue/postgres/task-queue.d.ts +2 -1
  109. package/task-queue/postgres/task-queue.js +32 -2
  110. package/task-queue/task-context.js +1 -1
  111. package/task-queue/task-queue.d.ts +17 -0
  112. package/task-queue/task-queue.js +103 -45
  113. package/task-queue/tests/complex.test.js +4 -4
  114. package/task-queue/tests/dependencies.test.js +4 -2
  115. package/task-queue/tests/queue.test.js +111 -0
  116. package/task-queue/tests/worker.test.js +21 -13
  117. package/templates/README.md +287 -0
  118. package/testing/README.md +157 -0
  119. package/text/README.md +346 -0
  120. package/threading/README.md +238 -0
  121. package/types/README.md +311 -0
  122. package/utils/README.md +322 -0
  123. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  124. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  125. package/utils/async-iterable-helpers/take-until.js +4 -4
  126. package/utils/backoff.js +89 -30
  127. package/utils/retry-with-backoff.js +1 -1
  128. package/utils/timer.d.ts +1 -1
  129. package/utils/timer.js +5 -7
  130. package/utils/timing.d.ts +1 -1
  131. package/utils/timing.js +2 -4
  132. package/utils/z-base32.d.ts +1 -0
  133. 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 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.139",
3
+ "version": "0.93.140",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -10,7 +10,7 @@
10
10
  "build": "tsc && tsc-alias && npm run copy:orm",
11
11
  "build:watch": "concurrently --raw --kill-others npm:tsc:watch npm:tsc-alias:watch",
12
12
  "build:production": "rm -rf dist && npm run build && npm run build:production:copy-files",
13
- "build:production:copy-files": "cp package.json eslint.config.js tsconfig.server.json dist/ && cp tsconfig.base.json dist/tsconfig.json && npm run copy:orm",
13
+ "build:production:copy-files": "cp package.json eslint.config.js tsconfig.server.json dist/ && cp tsconfig.base.json dist/tsconfig.json && npm run copy:orm && npm run copy:readmes",
14
14
  "build:dts": "rm -rf dist && tsc -p tsconfig.dts.json && tsc-alias",
15
15
  "build:docs": "typedoc",
16
16
  "build:docs:watch": "typedoc --watch",
@@ -20,6 +20,7 @@
20
20
  "tsc:watch": "tsc --watch",
21
21
  "tsc-alias:watch": "tsc-alias --watch",
22
22
  "cleanup:dist": "rm -vrf dist/tools/",
23
+ "copy:readmes": "cp README.md dist/ && cd source && find . -name \"README.md\" -exec cp --parents {} ../dist/ \\;",
23
24
  "generate:migration": "./scripts/manage-orm.sh generate && npm run copy:orm",
24
25
  "generate:readmes": "deno run --allow-run=code2prompt --allow-read --allow-write=source --allow-net=generativelanguage.googleapis.com --allow-env=GEMINI_API_KEY generate-readmes.ts",
25
26
  "generate:readmes:new-only": "npm run generate:readmes -- --skip-existing",
@@ -150,7 +151,9 @@
150
151
  "type-fest": "^5.4"
151
152
  },
152
153
  "peerDependencies": {
153
- "@genkit-ai/google-genai": "^1.28",
154
+ "@aws-sdk/client-s3": "^3.994",
155
+ "@aws-sdk/s3-request-presigner": "^3.994",
156
+ "@genkit-ai/google-genai": "^1.29",
154
157
  "@google-cloud/storage": "^7.19",
155
158
  "@toon-format/toon": "^2.1.0",
156
159
  "@tstdl/angular": "^0.93",
@@ -160,10 +163,8 @@
160
163
  "@zxcvbn-ts/language-en": "^3.0",
161
164
  "drizzle-orm": "^0.45",
162
165
  "file-type": "^21.3",
163
- "genkit": "^1.28",
166
+ "genkit": "^1.29",
164
167
  "handlebars": "^4.7",
165
- "@aws-sdk/client-s3": "^3.993",
166
- "@aws-sdk/s3-request-presigner": "^3.993",
167
168
  "mjml": "^4.18",
168
169
  "nodemailer": "^8.0",
169
170
  "pg": "^8.18",
@@ -181,6 +182,7 @@
181
182
  }
182
183
  },
183
184
  "devDependencies": {
185
+ "@biomejs/biome": "2.4",
184
186
  "@stylistic/eslint-plugin": "5.9",
185
187
  "@types/koa__router": "12.0",
186
188
  "@types/luxon": "3.7",
@@ -0,0 +1,164 @@
1
+ # @tstdl/base/password
2
+
3
+ A robust password strength estimation module that combines the advanced heuristic analysis of `zxcvbn-ts` with secure checks against the "Have I Been Pwned" breach database.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [Strength Analysis](#strength-analysis-zxcvbn)
10
+ - [Breach Check](#breach-check-hibp)
11
+ - [🚀 Basic Usage](#-basic-usage)
12
+ - [🔧 Advanced Topics](#-advanced-topics)
13
+ - [Localization](#localization)
14
+ - [Offline Checks](#offline-checks)
15
+ - [Schema Integration](#schema-integration)
16
+ - [📚 API](#-api)
17
+
18
+ ## ✨ Features
19
+
20
+ - **Advanced Heuristics:** Utilizes `zxcvbn-ts` to detect common patterns, dictionary words, names, keyboard sequences, and dates.
21
+ - **Breach Detection:** Securely checks if a password has appeared in known data breaches via the "Have I Been Pwned" (HIBP) API.
22
+ - **Secure by Design:** Uses k-Anonymity for HIBP checks; the full password hash is never sent over the network.
23
+ - **Unified Scoring:** Provides a consolidated strength score from 0 (Very Weak) to 4 (Very Strong).
24
+ - **Localized Feedback:** Returns localization keys for warnings and suggestions, supporting English and German out of the box.
25
+ - **Schema & Validation Ready:** `PasswordCheckResult` is fully decorated with `@tstdl/base/schema`, making it ready for API contracts and data models.
26
+ - **Performance Optimized:** Dynamically imports heavy analysis libraries only when needed to keep initial bundle sizes low.
27
+
28
+ ## Core Concepts
29
+
30
+ ### Strength Analysis (zxcvbn)
31
+
32
+ The module uses `zxcvbn-ts` to calculate entropy and estimate crack time. It looks for:
33
+
34
+ - **Dictionaries:** Common passwords, names, and words in multiple languages.
35
+ - **Patterns:** L33t speak, keyboard walks (e.g., "qwerty"), and repetitions.
36
+ - **Personal Info:** Dates and years.
37
+
38
+ ### Breach Check (HIBP)
39
+
40
+ To protect against credential stuffing, the module checks the "Have I Been Pwned" database.
41
+
42
+ 1. The password is hashed using SHA-1.
43
+ 2. Only the first 5 characters of the hash are sent to the API (k-Anonymity).
44
+ 3. The API returns all suffixes matching that prefix.
45
+ 4. The module checks locally if the full hash exists in the response.
46
+
47
+ If a password is found in the breach database, its strength score is automatically downgraded to **0 (Very Weak)**, regardless of its complexity.
48
+
49
+ ## 🚀 Basic Usage
50
+
51
+ The primary entry point is the `checkPassword` function. It returns a `PasswordCheckResult` containing the score, breach count, and feedback keys.
52
+
53
+ ```typescript
54
+ import { checkPassword, PasswordStrength } from '@tstdl/base/password';
55
+
56
+ async function validateUserPassword(password: string) {
57
+ const result = await checkPassword(password);
58
+
59
+ console.log(`Score: ${result.strength} (${PasswordStrength[result.strength]})`);
60
+
61
+ if (result.pwned && result.pwned > 0) {
62
+ console.warn(`⚠️ This password has appeared in ${result.pwned} data breaches!`);
63
+ }
64
+
65
+ if (result.strength < PasswordStrength.Strong) {
66
+ console.log('Password is too weak.');
67
+ }
68
+ }
69
+
70
+ validateUserPassword('correct-horse-battery-staple');
71
+ ```
72
+
73
+ ## 🔧 Advanced Topics
74
+
75
+ ### Localization
76
+
77
+ The `warnings` and `suggestions` arrays in the result contain **localization keys** (e.g., `tstdl.passwordCheck.warnings.simpleRepeat`) rather than raw text. This allows you to easily translate feedback using the `@tstdl/base/text` module.
78
+
79
+ The module exports `englishPasswordCheckLocalization` and `germanPasswordCheckLocalization`.
80
+
81
+ ```typescript
82
+ import { checkPassword, englishPasswordCheckLocalization } from '@tstdl/base/password';
83
+ import { LocalizationService } from '@tstdl/base/text';
84
+
85
+ // 1. Setup the localization service with the provided language pack
86
+ const localization = new LocalizationService([englishPasswordCheckLocalization]);
87
+ localization.setLanguage('en');
88
+
89
+ async function showFeedback(password: string) {
90
+ const result = await checkPassword(password);
91
+
92
+ // 2. Translate the keys to human-readable text
93
+ const warnings = result.warnings.map((key) => localization.localize(key));
94
+ const suggestions = result.suggestions.map((key) => localization.localize(key));
95
+
96
+ console.log('Warnings:', warnings);
97
+ console.log('Suggestions:', suggestions);
98
+ }
99
+ ```
100
+
101
+ ### Offline Checks
102
+
103
+ If you are in an environment without internet access, or if you want to skip the HTTP request to "Have I Been Pwned" for performance reasons, you can disable it via options.
104
+
105
+ ```typescript
106
+ import { checkPassword } from '@tstdl/base/password';
107
+
108
+ async function checkOffline(password: string) {
109
+ // Disables the HIBP API call
110
+ const result = await checkPassword(password, { checkForPwned: false });
111
+
112
+ // result.pwned will be undefined
113
+ console.log(`Local Strength Score: ${result.strength}`);
114
+ }
115
+ ```
116
+
117
+ ### Schema Integration
118
+
119
+ The `PasswordCheckResult` is a class decorated with `@tstdl/base/schema`. This makes it easy to use as a return type in your API definitions or as a nested property in other models.
120
+
121
+ ```typescript
122
+ import { PasswordCheckResult } from '@tstdl/base/password';
123
+ import { defineApi } from '@tstdl/base/api';
124
+
125
+ export const myApiDefinition = defineApi({
126
+ endpoints: {
127
+ checkStrength: {
128
+ method: 'GET',
129
+ parameters: { password: String },
130
+ result: PasswordCheckResult,
131
+ },
132
+ },
133
+ });
134
+ ```
135
+
136
+ ### Authentication Integration
137
+
138
+ This module is used by the `@tstdl/base/authentication` framework to enforce password requirements during registration or password changes. See `AuthenticationSecretRequirementsValidator` for more details on how to customize these checks in your application.
139
+
140
+ ## 📚 API
141
+
142
+ ### Functions
143
+
144
+ | Function | Signature | Description |
145
+ | :--------------- | :----------------------------------------------------------------------------------- | :------------------------------------------------------------------------------ |
146
+ | `checkPassword` | `(password: string, options?: CheckPasswordOptions) => Promise<PasswordCheckResult>` | Analyzes password strength and checks for breaches. |
147
+ | `haveIBeenPwned` | `(password: string) => Promise<number>` | Checks the password against the HIBP database and returns the occurrence count. |
148
+
149
+ ### Classes & Interfaces
150
+
151
+ | Name | Type | Description |
152
+ | :-------------------------- | :------ | :----------------------------------------------------------------------------------------- |
153
+ | `PasswordCheckResult` | `class` | The result object containing `strength`, `pwned` count, `warnings`, and `suggestions`. |
154
+ | `CheckPasswordOptions` | `type` | Options object. Property `checkForPwned` (boolean, default `true`) controls the API check. |
155
+ | `PasswordCheckLocalization` | `type` | Type definition for the localization structure used by this module. |
156
+
157
+ ### Enums & Constants
158
+
159
+ | Name | Type | Description |
160
+ | :--------------------------------- | :------ | :------------------------------------------------------------------------ |
161
+ | `PasswordStrength` | `enum` | `VeryWeak` (0), `Weak` (1), `Medium` (2), `Strong` (3), `VeryStrong` (4). |
162
+ | `englishPasswordCheckLocalization` | `const` | English localization definitions for warnings and suggestions. |
163
+ | `germanPasswordCheckLocalization` | `const` | German localization definitions for warnings and suggestions. |
164
+ | `passwordCheckLocalizationKeys` | `const` | Helper object containing the localization key paths. |