@tstdl/base 0.93.138 → 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.
- package/README.md +166 -0
- package/ai/genkit/multi-region.plugin.js +5 -3
- package/ai/genkit/tests/multi-region.test.d.ts +1 -0
- package/ai/genkit/tests/multi-region.test.js +5 -2
- package/ai/parser/parser.js +2 -2
- package/ai/prompts/build.js +1 -0
- package/ai/prompts/instructions-formatter.d.ts +15 -2
- package/ai/prompts/instructions-formatter.js +36 -31
- package/ai/prompts/prompt-builder.js +5 -5
- package/ai/prompts/steering.d.ts +3 -2
- package/ai/prompts/steering.js +3 -1
- package/ai/tests/instructions-formatter.test.js +1 -0
- package/api/README.md +403 -0
- package/api/client/client.js +7 -13
- package/api/client/tests/api-client.test.js +10 -10
- package/api/default-error-handlers.js +1 -1
- package/api/response.d.ts +2 -2
- package/api/response.js +22 -33
- package/api/server/api-controller.d.ts +1 -1
- package/api/server/api-controller.js +3 -3
- package/api/server/api-request-token.provider.d.ts +1 -0
- package/api/server/api-request-token.provider.js +1 -0
- package/api/server/middlewares/allowed-methods.middleware.js +2 -1
- package/api/server/middlewares/content-type.middleware.js +2 -1
- package/api/types.d.ts +3 -2
- package/application/README.md +240 -0
- package/application/application.js +2 -2
- package/audit/README.md +267 -0
- package/authentication/README.md +288 -0
- package/authentication/client/authentication.service.d.ts +12 -11
- package/authentication/client/authentication.service.js +21 -21
- package/authentication/client/http-client.middleware.js +2 -2
- package/authentication/tests/authentication.client-error-handling.test.js +2 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
- package/browser/README.md +401 -0
- package/cancellation/README.md +156 -0
- package/cancellation/tests/coverage.test.d.ts +1 -0
- package/cancellation/tests/coverage.test.js +49 -0
- package/cancellation/tests/leak.test.d.ts +1 -0
- package/cancellation/tests/leak.test.js +35 -0
- package/cancellation/tests/token.test.d.ts +1 -0
- package/cancellation/tests/token.test.js +136 -0
- package/cancellation/token.d.ts +53 -177
- package/cancellation/token.js +132 -201
- package/context/README.md +174 -0
- package/cookie/README.md +161 -0
- package/css/README.md +157 -0
- package/data-structures/README.md +320 -0
- package/decorators/README.md +140 -0
- package/distributed-loop/README.md +231 -0
- package/distributed-loop/distributed-loop.js +1 -1
- package/document-management/README.md +403 -0
- package/document-management/server/services/document-management.service.js +9 -7
- package/document-management/tests/document-management-core.test.js +2 -7
- package/document-management/tests/document-management.api.test.js +6 -7
- package/document-management/tests/document-statistics.service.test.js +11 -12
- package/document-management/tests/document.service.test.js +3 -3
- package/document-management/tests/enum-helpers.test.js +2 -3
- package/dom/README.md +213 -0
- package/enumerable/README.md +259 -0
- package/enumeration/README.md +121 -0
- package/errors/README.md +267 -0
- package/file/README.md +191 -0
- package/formats/README.md +210 -0
- package/function/README.md +144 -0
- package/http/README.md +318 -0
- package/http/client/adapters/undici.adapter.js +1 -1
- package/http/client/http-client-request.d.ts +6 -5
- package/http/client/http-client-request.js +8 -9
- package/http/server/node/node-http-server.js +1 -2
- package/image-service/README.md +137 -0
- package/injector/README.md +491 -0
- package/injector/injector.d.ts +1 -0
- package/injector/injector.js +17 -5
- package/injector/tests/leak.test.d.ts +1 -0
- package/injector/tests/leak.test.js +45 -0
- package/intl/README.md +113 -0
- package/json-path/README.md +182 -0
- package/jsx/README.md +154 -0
- package/key-value-store/README.md +191 -0
- package/lock/README.md +249 -0
- package/lock/web/web-lock.js +119 -47
- package/logger/README.md +287 -0
- package/mail/README.md +256 -0
- package/memory/README.md +144 -0
- package/message-bus/README.md +244 -0
- package/message-bus/message-bus-base.js +1 -1
- package/module/README.md +182 -0
- package/module/module.d.ts +1 -1
- package/module/module.js +77 -17
- package/module/modules/web-server.module.js +1 -1
- package/notification/tests/notification-type.service.test.js +24 -15
- package/object-storage/README.md +300 -0
- package/openid-connect/README.md +274 -0
- package/orm/README.md +423 -0
- package/package.json +8 -6
- package/password/README.md +164 -0
- package/pdf/README.md +246 -0
- package/polyfills.js +1 -0
- package/pool/README.md +198 -0
- package/process/README.md +237 -0
- package/promise/README.md +252 -0
- package/promise/cancelable-promise.js +1 -1
- package/random/README.md +193 -0
- package/reflection/README.md +305 -0
- package/rpc/README.md +386 -0
- package/rxjs-utils/README.md +262 -0
- package/schema/README.md +342 -0
- package/serializer/README.md +342 -0
- package/signals/implementation/README.md +134 -0
- package/sse/README.md +278 -0
- package/task-queue/README.md +300 -0
- package/task-queue/postgres/task-queue.d.ts +2 -1
- package/task-queue/postgres/task-queue.js +32 -2
- package/task-queue/task-context.js +1 -1
- package/task-queue/task-queue.d.ts +17 -0
- package/task-queue/task-queue.js +103 -44
- package/task-queue/tests/complex.test.js +4 -4
- package/task-queue/tests/dependencies.test.js +4 -2
- package/task-queue/tests/queue.test.js +111 -0
- package/task-queue/tests/worker.test.js +21 -13
- package/templates/README.md +287 -0
- package/testing/README.md +157 -0
- package/text/README.md +346 -0
- package/threading/README.md +238 -0
- package/types/README.md +311 -0
- package/utils/README.md +322 -0
- package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
- package/utils/async-iterable-helpers/observable-iterable.js +4 -8
- package/utils/async-iterable-helpers/take-until.js +4 -4
- package/utils/backoff.js +89 -30
- package/utils/retry-with-backoff.js +1 -1
- package/utils/timer.d.ts +1 -1
- package/utils/timer.js +5 -7
- package/utils/timing.d.ts +1 -1
- package/utils/timing.js +2 -4
- package/utils/z-base32.d.ts +1 -0
- 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.
|
|
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
|
-
"@
|
|
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.
|
|
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. |
|