@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.
Files changed (138) 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.d.ts +1 -0
  40. package/cancellation/tests/leak.test.js +35 -0
  41. package/cancellation/tests/token.test.d.ts +1 -0
  42. package/cancellation/tests/token.test.js +136 -0
  43. package/cancellation/token.d.ts +53 -177
  44. package/cancellation/token.js +132 -201
  45. package/context/README.md +174 -0
  46. package/cookie/README.md +161 -0
  47. package/css/README.md +157 -0
  48. package/data-structures/README.md +320 -0
  49. package/decorators/README.md +140 -0
  50. package/distributed-loop/README.md +231 -0
  51. package/distributed-loop/distributed-loop.js +1 -1
  52. package/document-management/README.md +403 -0
  53. package/document-management/server/services/document-management.service.js +9 -7
  54. package/document-management/tests/document-management-core.test.js +2 -7
  55. package/document-management/tests/document-management.api.test.js +6 -7
  56. package/document-management/tests/document-statistics.service.test.js +11 -12
  57. package/document-management/tests/document.service.test.js +3 -3
  58. package/document-management/tests/enum-helpers.test.js +2 -3
  59. package/dom/README.md +213 -0
  60. package/enumerable/README.md +259 -0
  61. package/enumeration/README.md +121 -0
  62. package/errors/README.md +267 -0
  63. package/file/README.md +191 -0
  64. package/formats/README.md +210 -0
  65. package/function/README.md +144 -0
  66. package/http/README.md +318 -0
  67. package/http/client/adapters/undici.adapter.js +1 -1
  68. package/http/client/http-client-request.d.ts +6 -5
  69. package/http/client/http-client-request.js +8 -9
  70. package/http/server/node/node-http-server.js +1 -2
  71. package/image-service/README.md +137 -0
  72. package/injector/README.md +491 -0
  73. package/injector/injector.d.ts +1 -0
  74. package/injector/injector.js +17 -5
  75. package/injector/tests/leak.test.d.ts +1 -0
  76. package/injector/tests/leak.test.js +45 -0
  77. package/intl/README.md +113 -0
  78. package/json-path/README.md +182 -0
  79. package/jsx/README.md +154 -0
  80. package/key-value-store/README.md +191 -0
  81. package/lock/README.md +249 -0
  82. package/lock/web/web-lock.js +119 -47
  83. package/logger/README.md +287 -0
  84. package/mail/README.md +256 -0
  85. package/memory/README.md +144 -0
  86. package/message-bus/README.md +244 -0
  87. package/message-bus/message-bus-base.js +1 -1
  88. package/module/README.md +182 -0
  89. package/module/module.d.ts +1 -1
  90. package/module/module.js +77 -17
  91. package/module/modules/web-server.module.js +1 -1
  92. package/notification/tests/notification-type.service.test.js +24 -15
  93. package/object-storage/README.md +300 -0
  94. package/openid-connect/README.md +274 -0
  95. package/orm/README.md +423 -0
  96. package/package.json +8 -6
  97. package/password/README.md +164 -0
  98. package/pdf/README.md +246 -0
  99. package/polyfills.js +1 -0
  100. package/pool/README.md +198 -0
  101. package/process/README.md +237 -0
  102. package/promise/README.md +252 -0
  103. package/promise/cancelable-promise.js +1 -1
  104. package/random/README.md +193 -0
  105. package/reflection/README.md +305 -0
  106. package/rpc/README.md +386 -0
  107. package/rxjs-utils/README.md +262 -0
  108. package/schema/README.md +342 -0
  109. package/serializer/README.md +342 -0
  110. package/signals/implementation/README.md +134 -0
  111. package/sse/README.md +278 -0
  112. package/task-queue/README.md +300 -0
  113. package/task-queue/postgres/task-queue.d.ts +2 -1
  114. package/task-queue/postgres/task-queue.js +32 -2
  115. package/task-queue/task-context.js +1 -1
  116. package/task-queue/task-queue.d.ts +17 -0
  117. package/task-queue/task-queue.js +103 -44
  118. package/task-queue/tests/complex.test.js +4 -4
  119. package/task-queue/tests/dependencies.test.js +4 -2
  120. package/task-queue/tests/queue.test.js +111 -0
  121. package/task-queue/tests/worker.test.js +21 -13
  122. package/templates/README.md +287 -0
  123. package/testing/README.md +157 -0
  124. package/text/README.md +346 -0
  125. package/threading/README.md +238 -0
  126. package/types/README.md +311 -0
  127. package/utils/README.md +322 -0
  128. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  129. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  130. package/utils/async-iterable-helpers/take-until.js +4 -4
  131. package/utils/backoff.js +89 -30
  132. package/utils/retry-with-backoff.js +1 -1
  133. package/utils/timer.d.ts +1 -1
  134. package/utils/timer.js +5 -7
  135. package/utils/timing.d.ts +1 -1
  136. package/utils/timing.js +2 -4
  137. package/utils/z-base32.d.ts +1 -0
  138. package/utils/z-base32.js +1 -0
@@ -0,0 +1,300 @@
1
+ # @tstdl/base/object-storage
2
+
3
+ A flexible and extensible module for handling object storage, providing a strong abstraction layer over concrete implementations like S3. It simplifies file management with module-based isolation, automatic lifecycle management, and seamless dependency injection integration.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [🚀 Basic Usage](#-basic-usage)
10
+ - [Configuration](#configuration)
11
+ - [Injecting and Using ObjectStorage](#injecting-and-using-objectstorage)
12
+ - [🔧 Advanced Topics](#-advanced-topics)
13
+ - [Lifecycle Management (Expiration)](#lifecycle-management-expiration)
14
+ - [Pre-signed URLs](#pre-signed-urls)
15
+ - [Streaming I/O](#streaming-io)
16
+ - [Listing Objects](#listing-objects)
17
+ - [Moving and Copying](#moving-and-copying)
18
+ - [📚 API](#-api)
19
+
20
+ ## ✨ Features
21
+
22
+ - **Abstract `ObjectStorage` Interface**: Decouples your application logic from specific storage vendors.
23
+ - **S3-Compatible Implementation**: Includes a robust `S3ObjectStorage` implementation working with AWS S3, MinIO, Google Cloud Storage (via S3 interoperability), etc.
24
+ - **Google Cloud Storage Implementation**: Native `GoogleObjectStorage` implementation for direct GCS support.
25
+ - **Module-based Isolation**: Organizes objects into logical "modules". These map to either key prefixes in a shared bucket or separate buckets per module.
26
+ - **Automatic Lifecycle Management**: Configure object expiration policies (e.g., delete temp files after 24h) directly via injection tokens.
27
+ - **Stream Support**: Native support for `ReadableStream` and `Uint8Array` for memory-efficient handling of large files.
28
+ - **Pre-signed URLs**: Generate secure, temporary URLs for direct client-side uploads and downloads.
29
+ - **Dependency Injection**: Designed for `@tstdl/base/injector`, allowing context-aware resolution of storage instances.
30
+
31
+ ## Core Concepts
32
+
33
+ ### ObjectStorage
34
+
35
+ The central abstract class defining the contract for storage operations (upload, download, delete, exists, etc.). You rarely instantiate this directly; instead, you request it via dependency injection.
36
+
37
+ ### Modules
38
+
39
+ A **Module** is a logical namespace for a collection of objects (e.g., `user-avatars`, `invoices`, `temp-uploads`).
40
+
41
+ - **Shared Bucket Mode**: Modules are treated as directory prefixes within a single S3 bucket (e.g., `my-bucket/user-avatars/image.png`).
42
+ - **Bucket-Per-Module Mode**: Each module gets its own dedicated S3 bucket (e.g., bucket `user-avatars` contains `image.png`).
43
+
44
+ ### ObjectStorageProvider
45
+
46
+ A factory responsible for creating `ObjectStorage` instances for specific modules. The `S3ObjectStorageProvider` handles the creation of S3 clients and ensures buckets exist.
47
+
48
+ ### ObjectStorageObject
49
+
50
+ Represents a stored file. It provides methods to access metadata, size, content, and resource URIs without loading the entire file into memory immediately.
51
+
52
+ ## 🚀 Basic Usage
53
+
54
+ ### Configuration
55
+
56
+ #### S3 (AWS, MinIO, etc.)
57
+
58
+ Configure the S3 provider at your application's entry point (e.g., `bootstrap.ts`).
59
+
60
+ ```typescript
61
+ import { configureS3ObjectStorage } from '@tstdl/base/object-storage';
62
+
63
+ // Option A: Single Shared Bucket (Recommended for most use cases)
64
+ configureS3ObjectStorage({
65
+ endpoint: 'http://localhost:9000', // S3 Endpoint
66
+ accessKey: 'minioadmin',
67
+ secretKey: 'minioadmin',
68
+ bucket: 'my-app-storage', // All modules will be subfolders in this bucket
69
+ forcePathStyle: true, // Required for local s3 server
70
+ });
71
+
72
+ // Option B: Bucket Per Module
73
+ // configureS3ObjectStorage({
74
+ // endpoint: '...',
75
+ // accessKey: '...',
76
+ // secretKey: '...',
77
+ // bucketPerModule: true // Each module creates a new bucket
78
+ // });
79
+ ```
80
+
81
+ #### Google Cloud Storage
82
+
83
+ ```typescript
84
+ import { configureGoogleObjectStorage } from '@tstdl/base/object-storage/google';
85
+
86
+ configureGoogleObjectStorage({
87
+ projectId: 'my-project-id',
88
+ keyFilename: '/path/to/keyfile.json',
89
+ bucket: 'my-app-storage',
90
+ });
91
+ ```
92
+
93
+ ### Injecting and Using ObjectStorage
94
+
95
+ Inject `ObjectStorage` into your services using the module name as the injection argument.
96
+
97
+ ```typescript
98
+ import { inject, Singleton } from '@tstdl/base/injector';
99
+ import { ObjectStorage } from '@tstdl/base/object-storage';
100
+
101
+ @Singleton()
102
+ export class ProfilePictureService {
103
+ // Inject storage specifically for the 'profile-pictures' module
104
+ readonly #storage = inject(ObjectStorage, 'profile-pictures');
105
+
106
+ async savePicture(userId: string, content: Uint8Array): Promise<void> {
107
+ const key = `${userId}.jpg`;
108
+
109
+ // Upload content
110
+ await this.#storage.uploadObject(key, content, {
111
+ contentType: 'image/jpeg',
112
+ metadata: { userId },
113
+ });
114
+ }
115
+
116
+ async getPicture(userId: string): Promise<Uint8Array> {
117
+ const key = `${userId}.jpg`;
118
+
119
+ // Check existence
120
+ if (!(await this.#storage.exists(key))) {
121
+ throw new Error('Picture not found');
122
+ }
123
+
124
+ // Download content
125
+ return this.#storage.getContent(key);
126
+ }
127
+
128
+ async deletePicture(userId: string): Promise<void> {
129
+ await this.#storage.deleteObject(`${userId}.jpg`);
130
+ }
131
+ }
132
+ ```
133
+
134
+ ## 🔧 Advanced Topics
135
+
136
+ ### Lifecycle Management (Expiration)
137
+
138
+ You can configure objects to automatically expire (be deleted) after a certain duration. This is configured when injecting the storage instance. The implementation (e.g., S3) will apply lifecycle rules to the bucket.
139
+
140
+ ```typescript
141
+ import { inject, Singleton } from '@tstdl/base/injector';
142
+ import { ObjectStorage } from '@tstdl/base/object-storage';
143
+ import { secondsPerDay } from '@tstdl/base/utils';
144
+
145
+ @Singleton()
146
+ export class TemporaryUploadService {
147
+ // Objects in 'temp-files' will be deleted 1 day after creation
148
+ readonly #tempStorage = inject(ObjectStorage, {
149
+ module: 'temp-files',
150
+ configuration: {
151
+ lifecycle: {
152
+ expiration: {
153
+ after: 1 * secondsPerDay,
154
+ },
155
+ },
156
+ },
157
+ });
158
+
159
+ async saveTempFile(filename: string, data: Uint8Array): Promise<void> {
160
+ await this.#tempStorage.uploadObject(filename, data);
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### Pre-signed URLs
166
+
167
+ Offload bandwidth from your server by generating pre-signed URLs that allow clients to upload or download directly to/from the storage provider.
168
+
169
+ ```typescript
170
+ import { inject, Singleton } from '@tstdl/base/injector';
171
+ import { ObjectStorage } from '@tstdl/base/object-storage';
172
+ import { millisecondsPerMinute, now } from '@tstdl/base/utils';
173
+
174
+ @Singleton()
175
+ export class DownloadService {
176
+ readonly #storage = inject(ObjectStorage, 'reports');
177
+
178
+ async getDownloadLink(reportId: string): Promise<string> {
179
+ const key = `${reportId}.pdf`;
180
+ const expiration = now().getTime() + 15 * millisecondsPerMinute;
181
+
182
+ // Generate a URL valid for 15 minutes
183
+ return this.#storage.getDownloadUrl(key, expiration, {
184
+ 'response-content-disposition': 'attachment; filename="report.pdf"',
185
+ });
186
+ }
187
+
188
+ async getUploadLink(reportId: string): Promise<string> {
189
+ const key = `${reportId}.pdf`;
190
+ const expiration = now().getTime() + 15 * millisecondsPerMinute;
191
+
192
+ // Generate a PUT URL for uploading
193
+ return this.#storage.getUploadUrl(key, expiration, {
194
+ contentType: 'application/pdf',
195
+ contentLength: 1024 * 1024 * 5, // 5MB expected size
196
+ });
197
+ }
198
+ }
199
+ ```
200
+
201
+ ### Streaming I/O
202
+
203
+ For large files, use streams to avoid loading the entire file into memory.
204
+
205
+ ```typescript
206
+ import { inject } from '@tstdl/base/injector';
207
+ import { ObjectStorage } from '@tstdl/base/object-storage';
208
+
209
+ class LargeFileService {
210
+ readonly #storage = inject(ObjectStorage, 'backups');
211
+
212
+ async uploadStream(filename: string, stream: ReadableStream<Uint8Array>, size: number): Promise<void> {
213
+ await this.#storage.uploadObject(filename, stream, {
214
+ contentLength: size,
215
+ });
216
+ }
217
+
218
+ async streamDownload(filename: string): Promise<ReadableStream<Uint8Array>> {
219
+ return this.#storage.getContentStream(filename);
220
+ }
221
+ }
222
+ ```
223
+
224
+ ### Listing Objects
225
+
226
+ Use `getObjectsCursor()` to iterate over objects efficiently using an async iterable.
227
+
228
+ ```typescript
229
+ async function listAllFiles(storage: ObjectStorage) {
230
+ for await (const obj of storage.getObjectsCursor()) {
231
+ console.log(`Found file: ${obj.key}, Size: ${await obj.getContentLength()}`);
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Moving and Copying
237
+
238
+ You can move or copy objects within the same module or across different modules (and buckets).
239
+
240
+ ```typescript
241
+ import { inject } from '@tstdl/base/injector';
242
+ import { ObjectStorage } from '@tstdl/base/object-storage';
243
+
244
+ class FileOrganizer {
245
+ readonly #inbox = inject(ObjectStorage, 'inbox');
246
+ readonly #archive = inject(ObjectStorage, 'archive');
247
+
248
+ async archiveFile(filename: string): Promise<void> {
249
+ // Move from 'inbox' module to 'archive' module
250
+ await this.#inbox.moveObject(filename, [this.#archive, `2024/${filename}`]);
251
+ }
252
+
253
+ async duplicateInInbox(filename: string): Promise<void> {
254
+ // Copy within the same module
255
+ await this.#inbox.copyObject(filename, `copy_of_${filename}`);
256
+ }
257
+ }
258
+ ```
259
+
260
+ ## 📚 API
261
+
262
+ ### `ObjectStorage` (Abstract Class)
263
+
264
+ | Method | Description |
265
+ | :--------------------------------------------- | :------------------------------------------------------ |
266
+ | `exists(key: string): Promise<boolean>` | Checks if an object exists. |
267
+ | `uploadObject(key, content, options?)` | Uploads data (`Uint8Array` or `ReadableStream`). |
268
+ | `getContent(key): Promise<Uint8Array>` | Downloads the full object content into memory. |
269
+ | `getContentStream(key): ReadableStream` | Gets a stream of the object content. |
270
+ | `getDownloadUrl(key, expiration, headers?)` | Generates a pre-signed download URL. |
271
+ | `getUploadUrl(key, expiration, options?)` | Generates a pre-signed upload URL. |
272
+ | `deleteObject(key): Promise<void>` | Deletes a single object. |
273
+ | `deleteObjects(keys): Promise<void>` | Deletes multiple objects. |
274
+ | `copyObject(source, dest, options?)` | Copies an object (intra- or inter-module). |
275
+ | `moveObject(source, dest, options?)` | Moves an object (copy + delete). |
276
+ | `getObjects(): Promise<ObjectStorageObject[]>` | Lists all objects in the module. |
277
+ | `getObjectsCursor(): AsyncIterable` | Iterates over objects efficiently. |
278
+ | `getObject(key): Promise<ObjectStorageObject>` | Gets a handle to an object without downloading content. |
279
+
280
+ ### `S3ObjectStorageProviderConfig`
281
+
282
+ | Property | Type | Description |
283
+ | :---------------- | :-------- | :----------------------------------------------------------------------------- |
284
+ | `endpoint` | `string` | S3 API endpoint (e.g., `https://s3.amazonaws.com` or `http://localhost:9000`). |
285
+ | `region` | `string` | S3 Region (e.g., `us-east-1`). |
286
+ | `accessKey` | `string` | S3 Access Key ID. |
287
+ | `secretKey` | `string` | S3 Secret Access Key. |
288
+ | `bucket` | `string` | Name of the shared bucket. Mutually exclusive with `bucketPerModule`. |
289
+ | `bucketPerModule` | `boolean` | If true, creates a separate bucket for each module. |
290
+ | `forcePathStyle` | `boolean` | Whether to use path-style addressing. Useful for local s3 server. |
291
+
292
+ ### `ObjectStorageObject`
293
+
294
+ | Method | Description |
295
+ | :--------------------------------------- | :----------------------------------------- |
296
+ | `getContentLength(): Promise<number>` | Returns the size of the object in bytes. |
297
+ | `getMetadata(): Promise<ObjectMetadata>` | Returns user-defined metadata. |
298
+ | `getContent(): Promise<Uint8Array>` | Downloads content. |
299
+ | `getContentStream(): ReadableStream` | Streams content. |
300
+ | `getResourceUri(): Promise<string>` | Returns the URI (e.g., `s3://bucket/key`). |
@@ -0,0 +1,274 @@
1
+ # OpenID Connect
2
+
3
+ A robust, type-safe OpenID Connect (OIDC) client implementation for TypeScript applications. It simplifies integration with OIDC providers by handling configuration discovery, secure state management, PKCE, and token exchanges.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [Prerequisites](#prerequisites)
10
+ - [🚀 Basic Usage](#-basic-usage)
11
+ - [1. Setup & Configuration](#1-setup--configuration)
12
+ - [2. Initiating Authorization (Login)](#2-initiating-authorization-login)
13
+ - [3. Handling the Callback](#3-handling-the-callback)
14
+ - [🔧 Advanced Topics](#-advanced-topics)
15
+ - [Client Credentials Flow](#client-credentials-flow)
16
+ - [Refreshing Tokens](#refreshing-tokens)
17
+ - [Fetching User Info](#fetching-user-info)
18
+ - [Custom State Data](#custom-state-data)
19
+ - [📚 API](#-api)
20
+
21
+ ## ✨ Features
22
+
23
+ - **Auto-Discovery**: Automatically fetches and caches provider configuration from `.well-known/openid-configuration`.
24
+ - **Secure by Default**: Implements Authorization Code Flow with **PKCE** (Proof Key for Code Exchange, SHA-256) and cryptographically secure state generation.
25
+ - **State Persistence**: Persists authorization state via the ORM to securely validate callbacks and prevent CSRF/replay attacks.
26
+ - **Multiple Flows**: Supports Authorization Code and Client Credentials flows.
27
+ - **Automated Authentication**: Supports both `body` and `basic-auth` authentication methods for token endpoints.
28
+ - **Token Management**: Simple methods to exchange codes for tokens, refresh existing tokens, and fetch user info.
29
+ - **Type-Safe**: Full TypeScript support for token responses and custom state data.
30
+
31
+ ## Core Concepts
32
+
33
+ ### OidcService
34
+
35
+ The central service class. It orchestrates the entire flow: fetching configuration, generating secure parameters, storing state in the database, and communicating with the OIDC provider to exchange tokens.
36
+
37
+ ### OidcState
38
+
39
+ An entity model used to persist the state of an authentication attempt. It stores:
40
+
41
+ - The generated `state` string (for CSRF protection).
42
+ - The `codeVerifier` (for PKCE).
43
+ - Configuration details (endpoint, client ID).
44
+ - Optional custom `data` attached to the flow.
45
+
46
+ This entity must be registered with your ORM configuration so the service can save and retrieve it during the callback phase.
47
+
48
+ ## Prerequisites
49
+
50
+ This module relies on the `@tstdl/base/orm` module to store the `OidcState`. Ensure your application has the ORM configured and the `OidcState` entity is included in your database schema.
51
+
52
+ ## 🚀 Basic Usage
53
+
54
+ This example demonstrates the standard **Authorization Code Flow** with PKCE, commonly used for user login.
55
+
56
+ ### 1. Setup & Configuration
57
+
58
+ Ensure `OidcState` is registered in your ORM configuration (e.g., in your `bootstrap.ts` or module configuration).
59
+
60
+ ```ts
61
+ import { configureOrm } from '@tstdl/base/orm/server';
62
+ import { OidcState } from '@tstdl/base/openid-connect';
63
+
64
+ // In your bootstrap/configuration file
65
+ configureOrm({
66
+ // ... connection settings
67
+ entities: [
68
+ // ... other entities
69
+ OidcState, // <--- Register the OidcState entity
70
+ ],
71
+ });
72
+ ```
73
+
74
+ ### 2. Initiating Authorization (Login)
75
+
76
+ When a user clicks "Login", generate the authorization URL. This creates a state record in the database.
77
+
78
+ ```ts
79
+ import { inject } from '@tstdl/base/injector';
80
+ import { OidcService } from '@tstdl/base/openid-connect';
81
+ import { millisecondsPerMinute } from '@tstdl/base/utils/units';
82
+
83
+ class AuthService {
84
+ // Inject the OidcService. You can type the generic to define custom data stored in state.
85
+ readonly oidcService = inject(OidcService<void>);
86
+
87
+ async getLoginUrl(): Promise<string> {
88
+ const result = await this.oidcService.initAuthorization({
89
+ endpoint: 'https://accounts.google.com', // The OIDC provider URL
90
+ clientId: 'my-client-id',
91
+ clientSecret: 'my-client-secret',
92
+ scope: 'openid profile email',
93
+ expiration: 5 * millisecondsPerMinute, // How long the login attempt is valid
94
+ data: undefined, // No custom data for this example
95
+ });
96
+
97
+ // Construct the URL to redirect the user to
98
+ const url = new URL(result.authorizationEndpoint);
99
+ url.searchParams.set('response_type', 'code');
100
+ url.searchParams.set('client_id', result.clientId);
101
+ url.searchParams.set('scope', result.scope);
102
+ url.searchParams.set('redirect_uri', 'https://myapp.com/callback');
103
+ url.searchParams.set('state', result.state);
104
+ url.searchParams.set('code_challenge', result.codeChallenge);
105
+ url.searchParams.set('code_challenge_method', result.codeChallengeMethod);
106
+
107
+ return url.toString();
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### 3. Handling the Callback
113
+
114
+ When the user is redirected back to your application (e.g., `https://myapp.com/callback?code=...&state=...`), validate the state and exchange the code for tokens.
115
+
116
+ ```ts
117
+ import { inject } from '@tstdl/base/injector';
118
+ import { OidcService } from '@tstdl/base/openid-connect';
119
+
120
+ class CallbackController {
121
+ readonly oidcService = inject(OidcService<void>);
122
+
123
+ async handleCallback(code: string, state: string): Promise<void> {
124
+ // 1. Validate and retrieve the stored state.
125
+ // This throws if the state is invalid, expired, or missing.
126
+ // It also deletes the state from the DB to prevent replay attacks.
127
+ const storedState = await this.oidcService.validateState(state);
128
+
129
+ // 2. Exchange the authorization code for tokens
130
+ const tokenResponse = await this.oidcService.getToken({
131
+ grantType: 'authorization_code',
132
+ endpoint: storedState.endpoint,
133
+ clientId: storedState.clientId,
134
+ clientSecret: storedState.clientSecret,
135
+ code: code,
136
+ codeVerifier: storedState.codeVerifier, // Retrieved from DB
137
+ redirectUri: 'https://myapp.com/callback',
138
+ authType: 'body', // or 'basic-auth' depending on provider
139
+ });
140
+
141
+ console.log('Access Token:', tokenResponse.accessToken);
142
+ console.log('ID Token:', tokenResponse.idToken);
143
+ }
144
+ }
145
+ ```
146
+
147
+ ## 🔧 Advanced Topics
148
+
149
+ ### Client Credentials Flow
150
+
151
+ Used for machine-to-machine communication where no user is involved.
152
+
153
+ ```ts
154
+ const token = await oidcService.getToken({
155
+ grantType: 'client_credentials',
156
+ endpoint: 'https://auth.example.com',
157
+ clientId: 'service-account-id',
158
+ clientSecret: 'service-account-secret',
159
+ scope: 'api:read',
160
+ });
161
+ ```
162
+
163
+ ### Refreshing Tokens
164
+
165
+ If you received a `refresh_token` in the initial flow, you can use it to get a new access token.
166
+
167
+ ```ts
168
+ const newToken = await oidcService.refreshToken({
169
+ endpoint: 'https://accounts.google.com',
170
+ clientId: 'my-client-id',
171
+ clientSecret: 'my-client-secret',
172
+ refreshToken: 'existing-refresh-token',
173
+ });
174
+ ```
175
+
176
+ ### Fetching User Info
177
+
178
+ Retrieve the user's profile information using the `userinfo_endpoint` discovered from the configuration.
179
+
180
+ ```ts
181
+ const userInfo = await oidcService.getUserInfo(
182
+ 'https://accounts.google.com',
183
+ tokenResponse, // The object returned from getToken
184
+ );
185
+
186
+ console.log(userInfo);
187
+ ```
188
+
189
+ ### Custom State Data
190
+
191
+ You can attach custom data to the `OidcState` when initializing authorization. This is useful for remembering where to redirect the user after login, storing a nonce, or keeping track of the original request's context.
192
+
193
+ ```ts
194
+ type MyCustomData = { returnUrl: string };
195
+
196
+ // Define a service or inject directly with the desired type
197
+ const oidcService = inject(OidcService<MyCustomData>);
198
+
199
+ // Initialize
200
+ const result = await oidcService.initAuthorization({
201
+ // ... other params
202
+ data: { returnUrl: '/dashboard/settings' },
203
+ });
204
+
205
+ // ... later in callback ...
206
+
207
+ const storedState = await oidcService.validateState(state);
208
+ const returnUrl = storedState.data.returnUrl; // Properly typed as string
209
+ ```
210
+
211
+ ### Additional Parameters and Auth Types
212
+
213
+ Some OIDC providers require additional parameters during token exchange or use a different authentication method.
214
+
215
+ ```ts
216
+ const tokenResponse = await oidcService.getToken({
217
+ grantType: 'authorization_code',
218
+ // ...
219
+ authType: 'basic-auth', // Uses HTTP Basic Auth (clientId:clientSecret)
220
+ }, {
221
+ // Additional form-data parameters (some providers require 'resource')
222
+ resource: 'https://api.example.com',
223
+ });
224
+ ```
225
+
226
+ ## ⚠️ Error Handling
227
+
228
+ The `OidcService` utilizes standard errors from the library:
229
+
230
+ - `ForbiddenError`: Thrown by `validateState` if the state is missing, invalid, or already used (to prevent replay attacks).
231
+ - `NotImplementedError`: Thrown if an unsupported grant type or authentication type is requested.
232
+ - `Error`: Thrown if required endpoints (like `userinfo_endpoint`) are not present in the provider's configuration.
233
+
234
+ Always wrap callback logic in a `try...catch` block to handle these cases gracefully.
235
+
236
+ ## 📚 API
237
+
238
+ ### Services
239
+
240
+ | Class | Description |
241
+ | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
242
+ | `OidcService<Data>` | Main service for OIDC operations. Handles state creation, validation, token exchange, and user info retrieval. |
243
+ | `OidcConfigurationService` | Fetches OIDC configuration from the provider's well-known endpoint. |
244
+ | `CachedOidcConfigurationService` | A cached implementation of `OidcConfigurationService`. Used by default with a 5-minute cache duration (configurable via dependency injection). |
245
+
246
+ ### OidcService Methods
247
+
248
+ | Method | Description |
249
+ | :------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------ |
250
+ | `initAuthorization(params)` | Discovers configuration, creates a secure state in the DB, and returns data to build the redirect URL. |
251
+ | `validateState(state)` | Attempts to load a state by its value, then **deletes it**. Throws if not found. Essential for security. |
252
+ | `getState(state)` | Just retrieves the state without deleting it. |
253
+ | `deleteState(state)` | Manually removes a state from the database. |
254
+ | `getToken(params, additionalData?)` | Exchanges a code (or credentials) for tokens. Supports `authorization_code` and `client_credentials`. |
255
+ | `refreshToken(params)` | Uses a refresh token to obtain a new set of tokens. |
256
+ | `getUserInfo(endpoint, token)` | Fetches the user's profile information using the access token. |
257
+ | `oidcConfigurationService.getConfiguration` | Directly fetches (and potentially caches) the OIDC provider's configuration. |
258
+
259
+ ### Models
260
+
261
+ | Class | Description |
262
+ | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------- |
263
+ | `OidcState<Data>` | Database entity representing the authorization state. Must be registered with ORM. Uses the `oidc` schema and `state` table by default. |
264
+
265
+ ### Types & Interfaces
266
+
267
+ | Type | Description |
268
+ | :--------------------------- | :----------------------------------------------------------------------------------------------- |
269
+ | `OidcInitParameters<Data>` | Parameters for `initAuthorization` (endpoint, client details, scope, expiration, custom data). |
270
+ | `OidcInitResult` | Result of initialization, containing `authorizationEndpoint`, `state`, and PKCE `codeChallenge`. |
271
+ | `OidcToken<Raw>` | Represents the token response, including `accessToken`, `idToken`, `refreshToken`, and `raw` JSON. |
272
+ | `OidcGetTokenParameters` | Union type for token requests. Includes `authType` (`body` \| `basic-auth`). |
273
+ | `OidcRefreshTokenParameters` | Parameters for refreshing a token. |
274
+ | `OidcConfiguration` | Discovered provider endpoints (authorization, token, userInfo, etc.). |