@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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Function
|
|
2
|
+
|
|
3
|
+
A utility module providing tools for function manipulation and debugging. It currently focuses on transparently wrapping functions to log execution details, arguments, and return values.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
11
|
+
- [Custom Logger](#custom-logger)
|
|
12
|
+
- [Enabling Stack Traces](#enabling-stack-traces)
|
|
13
|
+
- [📚 API](#-api)
|
|
14
|
+
|
|
15
|
+
## ✨ Features
|
|
16
|
+
|
|
17
|
+
- **Transparent Logging**: Wrap any function to automatically log when it is called and what it returns.
|
|
18
|
+
- **Argument Formatting**: Automatically formats primitive arguments and arrays for readable log output.
|
|
19
|
+
- **Custom Loggers**: Integrate with the `@tstdl/base/logger` or any compatible logger interface.
|
|
20
|
+
- **Stack Traces**: Optionally print stack traces on every call for deep debugging.
|
|
21
|
+
- **Metadata Control**: Override the function name used in logs for better context.
|
|
22
|
+
|
|
23
|
+
## Core Concepts
|
|
24
|
+
|
|
25
|
+
The `function` module provides high-order functions that take an existing function and return a new one with added behavior.
|
|
26
|
+
|
|
27
|
+
### Function Wrapping
|
|
28
|
+
|
|
29
|
+
The primary utility, `wrapLog`, acts as a functional decorator. It intercepts calls to the target function, logs the arguments, executes the original function, and then logs the result. This is particularly useful for debugging complex logic flows or inspecting data transformations without modifying the original source code with temporary `console.log` statements.
|
|
30
|
+
|
|
31
|
+
## 🚀 Basic Usage
|
|
32
|
+
|
|
33
|
+
The most common use case is wrapping a function to see its inputs and outputs in the console.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { wrapLog } from '@tstdl/base/function';
|
|
37
|
+
|
|
38
|
+
function calculateSum(a: number, b: number): number {
|
|
39
|
+
return a + b;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Wrap the function. By default, it uses console.log (via console.log.bind(console))
|
|
43
|
+
const loggedCalculateSum = wrapLog(calculateSum);
|
|
44
|
+
|
|
45
|
+
// Execute the wrapped function
|
|
46
|
+
loggedCalculateSum(5, 10);
|
|
47
|
+
|
|
48
|
+
// Console Output:
|
|
49
|
+
// [call: calculateSum(5, 10)]
|
|
50
|
+
// [return: calculateSum => 15]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 🔧 Advanced Topics
|
|
54
|
+
|
|
55
|
+
### Custom Logger
|
|
56
|
+
|
|
57
|
+
You can direct the output to a specific logger instance (e.g., from `@tstdl/base/logger`) instead of `console.log`. The wrapper uses the `trace` method of the logger.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { wrapLog } from '@tstdl/base/function';
|
|
61
|
+
import { Logger } from '@tstdl/base/logger';
|
|
62
|
+
import { inject } from '@tstdl/base/injector';
|
|
63
|
+
|
|
64
|
+
class MathService {
|
|
65
|
+
private logger = inject(Logger, 'MathService');
|
|
66
|
+
|
|
67
|
+
add(a: number, b: number): number {
|
|
68
|
+
return a + b;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
constructor() {
|
|
72
|
+
// Replace the method with a logged version using the service's logger
|
|
73
|
+
this.add = wrapLog(this.add.bind(this), {
|
|
74
|
+
logger: this.logger,
|
|
75
|
+
fnName: 'MathService.add', // Custom name for clearer logs
|
|
76
|
+
}) as any;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Enabling Stack Traces
|
|
82
|
+
|
|
83
|
+
For debugging call chains, you can enable the `trace` option. This will output a stack trace every time the function is called.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { wrapLog } from '@tstdl/base/function';
|
|
87
|
+
|
|
88
|
+
function criticalOperation() {
|
|
89
|
+
// ... logic
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const debugOperation = wrapLog(criticalOperation, {
|
|
93
|
+
trace: true,
|
|
94
|
+
logResult: false, // Only log the call and trace, ignore the return value
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
debugOperation();
|
|
98
|
+
// Output:
|
|
99
|
+
// [call: criticalOperation()]
|
|
100
|
+
// Trace: ... (stack trace)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 📚 API
|
|
104
|
+
|
|
105
|
+
### Functions
|
|
106
|
+
|
|
107
|
+
| Function | Description |
|
|
108
|
+
| :---------------------- | :--------------------------------------------------------------- |
|
|
109
|
+
| `wrapLog(fn, options?)` | Wraps a function to log its calls, arguments, and return values. |
|
|
110
|
+
|
|
111
|
+
### Types
|
|
112
|
+
|
|
113
|
+
| Type | Description |
|
|
114
|
+
| :--------------- | :---------------------------------- |
|
|
115
|
+
| `WrapLogOptions` | Configuration object for `wrapLog`. |
|
|
116
|
+
|
|
117
|
+
#### WrapLogOptions
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
type WrapLogOptions = {
|
|
121
|
+
/**
|
|
122
|
+
* The name to use in log messages. Defaults to fn.name.
|
|
123
|
+
*/
|
|
124
|
+
fnName?: string;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Whether to log the return value of the function.
|
|
128
|
+
* Default: true
|
|
129
|
+
*/
|
|
130
|
+
logResult?: boolean;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A Logger instance to use. If not provided, defaults to console.log.
|
|
134
|
+
* The wrapper calls logger.trace().
|
|
135
|
+
*/
|
|
136
|
+
logger?: Logger;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Whether to print a stack trace (console.trace) on execution.
|
|
140
|
+
* Default: false
|
|
141
|
+
*/
|
|
142
|
+
trace?: boolean;
|
|
143
|
+
};
|
|
144
|
+
```
|
package/http/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# @tstdl/base/http
|
|
2
|
+
|
|
3
|
+
A powerful, isomorphic, middleware-based HTTP client and server library for TypeScript. This module provides a comprehensive suite of tools for handling HTTP communication, featuring a flexible client with a middleware pipeline and a modern, async-iterable-based server abstraction.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [HTTP Client](#http-client)
|
|
10
|
+
- [HTTP Server](#http-server)
|
|
11
|
+
- [Isomorphic Utilities](#isomorphic-utilities)
|
|
12
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
13
|
+
- [Client Setup](#client-setup)
|
|
14
|
+
- [Making Requests](#making-requests)
|
|
15
|
+
- [Server Setup](#server-setup)
|
|
16
|
+
- [Handling Requests](#handling-requests)
|
|
17
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
18
|
+
- [Client Middleware](#client-middleware)
|
|
19
|
+
- [Streaming Data](#streaming-data)
|
|
20
|
+
- [Error Handling](#error-handling)
|
|
21
|
+
- [Automatic Parameter Mapping](#automatic-parameter-mapping)
|
|
22
|
+
- [📚 API](#-api)
|
|
23
|
+
|
|
24
|
+
## ✨ Features
|
|
25
|
+
|
|
26
|
+
- **Isomorphic Design:** Shared abstractions for client and server, with specific adapters for different environments (e.g., `undici` for Node.js).
|
|
27
|
+
- **Middleware-Driven Client:** Intercept and modify requests/responses using a flexible async middleware pipeline for concerns like logging, authentication, and caching.
|
|
28
|
+
- **Pluggable Client Adapter:** The `HttpClientAdapter` allows swapping the underlying HTTP engine. `UndiciHttpClientAdapter` is provided for high-performance Node.js applications.
|
|
29
|
+
- **Modern Server Abstraction:** The `HttpServer` uses an async iterator pattern (`for await...of`) for elegant and efficient request handling.
|
|
30
|
+
- **Advanced Body Handling:** Seamlessly works with JSON, text, forms (`x-www-form-urlencoded`), `FormData`, binary (`Uint8Array`, `Blob`), and `ReadableStream`.
|
|
31
|
+
- **Automatic Parameter Mapping:** Intelligently maps request parameters to URL path segments, query strings, or the request body.
|
|
32
|
+
- **Typed API:** Strongly typed interfaces for requests, responses, headers, and query parameters to catch errors at compile time.
|
|
33
|
+
- **Utility Classes:** Helpers like `HttpHeaders`, `HttpQuery`, and `HttpForm` simplify common tasks.
|
|
34
|
+
|
|
35
|
+
## Core Concepts
|
|
36
|
+
|
|
37
|
+
### HTTP Client
|
|
38
|
+
|
|
39
|
+
The client is designed around three main components: `HttpClient`, `HttpClientAdapter`, and `HttpClientMiddleware`.
|
|
40
|
+
|
|
41
|
+
- **`HttpClient`**: The primary interface for making requests. It manages configuration, default headers, and the middleware pipeline. It offers convenience methods like `getJson()`, `post()`, etc. The flow of a request is: `HttpClient` -> `Middleware Chain` -> `HttpClientAdapter`.
|
|
42
|
+
- **`HttpClientAdapter`**: The "engine" that performs the actual HTTP call. The library is decoupled from any specific implementation. For Node.js, the `UndiciHttpClientAdapter` is provided, which uses the high-performance `undici` library.
|
|
43
|
+
- **`HttpClientMiddleware`**: A function that intercepts a request before it's sent and the response after it's received. Middleware is composed into a pipeline.
|
|
44
|
+
|
|
45
|
+
### HTTP Server
|
|
46
|
+
|
|
47
|
+
The server implementation is based on the `HttpServer` abstract class.
|
|
48
|
+
|
|
49
|
+
- **`HttpServer`**: An async iterable that yields a `HttpServerRequestContext` for each incoming connection. This design promotes a clean, modern loop for processing requests (`for await (const context of server)`).
|
|
50
|
+
- **`HttpServerRequestContext`**: Encapsulates the `request` (incoming) and a `respond` function (outgoing).
|
|
51
|
+
- **`NodeHttpServer`**: The default implementation of `HttpServer` for Node.js, built on top of the native `node:http` module.
|
|
52
|
+
|
|
53
|
+
### Isomorphic Utilities
|
|
54
|
+
|
|
55
|
+
- **`HttpHeaders`**: A map-like class for managing HTTP headers with typed getters for common headers (e.g., `contentType`, `contentLength`).
|
|
56
|
+
- **`HttpBody`**: A unified interface for reading request/response bodies. It supports reading as JSON, text, buffer, or stream, regardless of the source (Node stream, Blob, etc.).
|
|
57
|
+
|
|
58
|
+
## 🚀 Basic Usage
|
|
59
|
+
|
|
60
|
+
### Client Setup
|
|
61
|
+
|
|
62
|
+
To use the client in a Node.js environment, you need to configure the `UndiciHttpClientAdapter`.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { HttpClient, configureHttpClient } from '@tstdl/base/http';
|
|
66
|
+
import { configureUndiciHttpClientAdapter } from '@tstdl/base/http/undici';
|
|
67
|
+
import { Injector } from '@tstdl/base/injector';
|
|
68
|
+
|
|
69
|
+
// 1. Configure the adapter (Node.js specific)
|
|
70
|
+
configureUndiciHttpClientAdapter({ register: true });
|
|
71
|
+
|
|
72
|
+
// 2. Configure the client (optional base URL)
|
|
73
|
+
configureHttpClient({
|
|
74
|
+
baseUrl: 'https://api.example.com',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 3. Resolve the client
|
|
78
|
+
const httpClient = Injector.resolve(HttpClient);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Making Requests
|
|
82
|
+
|
|
83
|
+
The `HttpClient` provides methods for standard HTTP verbs (`get`, `post`, `put`, `patch`, `delete`, `head`).
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// GET JSON
|
|
87
|
+
type User = { id: number; name: string };
|
|
88
|
+
const user = await httpClient.getJson<User>('/users/1');
|
|
89
|
+
console.log(user.name);
|
|
90
|
+
|
|
91
|
+
// POST JSON
|
|
92
|
+
const newUser = { name: 'Alice' };
|
|
93
|
+
const createdUser = await httpClient.postJson<User>('/users', {
|
|
94
|
+
body: { json: newUser },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// GET with Query Parameters
|
|
98
|
+
// Result: GET /search?q=typescript&limit=10
|
|
99
|
+
const results = await httpClient.getJson('/search', {
|
|
100
|
+
query: { q: 'typescript', limit: 10 },
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Server Setup
|
|
105
|
+
|
|
106
|
+
Configure and run the `NodeHttpServer`.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { HttpServer } from '@tstdl/base/http/server';
|
|
110
|
+
import { configureNodeHttpServer } from '@tstdl/base/http/node';
|
|
111
|
+
import { Injector } from '@tstdl/base/injector';
|
|
112
|
+
|
|
113
|
+
// 1. Register the Node.js server implementation
|
|
114
|
+
configureNodeHttpServer();
|
|
115
|
+
|
|
116
|
+
// 2. Resolve the server
|
|
117
|
+
const httpServer = Injector.resolve(HttpServer);
|
|
118
|
+
|
|
119
|
+
// 3. Start listening
|
|
120
|
+
await httpServer.listen(3000);
|
|
121
|
+
console.log('Server listening on port 3000');
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Handling Requests
|
|
125
|
+
|
|
126
|
+
Use the async iterator pattern to handle incoming requests sequentially or concurrently.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { HttpServerResponse } from '@tstdl/base/http/server';
|
|
130
|
+
|
|
131
|
+
// Loop through incoming requests
|
|
132
|
+
for await (const context of httpServer) {
|
|
133
|
+
const { request, respond } = context;
|
|
134
|
+
|
|
135
|
+
// Handle request asynchronously
|
|
136
|
+
(async () => {
|
|
137
|
+
try {
|
|
138
|
+
if (request.url.pathname === '/hello') {
|
|
139
|
+
await respond(
|
|
140
|
+
new HttpServerResponse({
|
|
141
|
+
statusCode: 200,
|
|
142
|
+
body: { text: 'Hello World!' },
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
} else {
|
|
146
|
+
await respond(
|
|
147
|
+
new HttpServerResponse({
|
|
148
|
+
statusCode: 404,
|
|
149
|
+
statusMessage: 'Not Found',
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(error);
|
|
155
|
+
await respond(new HttpServerResponse({ statusCode: 500 }));
|
|
156
|
+
} finally {
|
|
157
|
+
// Ensure the connection is closed if needed (handled by respond usually)
|
|
158
|
+
await context.close();
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 🔧 Advanced Topics
|
|
165
|
+
|
|
166
|
+
### Client Middleware
|
|
167
|
+
|
|
168
|
+
Middleware allows you to intercept requests and responses. This is useful for authentication, logging, or modifying headers.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { HttpClient, configureHttpClient, type HttpClientMiddleware, type HttpClientMiddlewareContext, type HttpClientMiddlewareNext } from '@tstdl/base/http';
|
|
172
|
+
import { configureUndiciHttpClientAdapter } from '@tstdl/base/http/undici';
|
|
173
|
+
import { Injector } from '@tstdl/base/injector';
|
|
174
|
+
|
|
175
|
+
const authMiddleware: HttpClientMiddleware = async (context: HttpClientMiddlewareContext, next: HttpClientMiddlewareNext) => {
|
|
176
|
+
// Pre-request logic
|
|
177
|
+
context.request.headers.set('Authorization', 'Bearer my-token');
|
|
178
|
+
|
|
179
|
+
// Execute next middleware / adapter
|
|
180
|
+
await next();
|
|
181
|
+
|
|
182
|
+
// Post-response logic
|
|
183
|
+
if (context.response?.statusCode === 401) {
|
|
184
|
+
console.warn('Unauthorized request!');
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
configureUndiciHttpClientAdapter({ register: true });
|
|
189
|
+
configureHttpClient({
|
|
190
|
+
middleware: [authMiddleware],
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const client = Injector.resolve(HttpClient);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Streaming Data
|
|
197
|
+
|
|
198
|
+
Both the client and server support streaming, which is essential for large files or real-time data.
|
|
199
|
+
|
|
200
|
+
**Client Streaming (Download):**
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const stream = httpClient.getBinaryStream('/large-file.zip');
|
|
204
|
+
// stream is a ReadableStream<Uint8Array>
|
|
205
|
+
|
|
206
|
+
for await (const chunk of stream) {
|
|
207
|
+
console.log(`Received chunk of size: ${chunk.length}`);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Server Streaming (Response):**
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { HttpServerResponse } from '@tstdl/base/http/server';
|
|
215
|
+
import { readableStreamFromPromise } from '@tstdl/base/utils/stream';
|
|
216
|
+
|
|
217
|
+
// ... inside request loop
|
|
218
|
+
await respond(
|
|
219
|
+
new HttpServerResponse({
|
|
220
|
+
statusCode: 200,
|
|
221
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
222
|
+
body: {
|
|
223
|
+
// Create a stream that yields data
|
|
224
|
+
stream: readableStreamFromPromise(async function* () {
|
|
225
|
+
yield new TextEncoder().encode('Chunk 1\n');
|
|
226
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
227
|
+
yield new TextEncoder().encode('Chunk 2\n');
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Error Handling
|
|
235
|
+
|
|
236
|
+
The `HttpClient` automatically throws an `HttpError` for non-2xx responses if `throwOnNon200` is true (default).
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { HttpError, HttpErrorReason } from '@tstdl/base/http';
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await httpClient.get('/non-existent');
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (error instanceof HttpError) {
|
|
245
|
+
console.log('Reason:', error.reason); // e.g., HttpErrorReason.StatusCode
|
|
246
|
+
console.log('Status:', error.response?.statusCode); // 404
|
|
247
|
+
|
|
248
|
+
// Access the response body if needed
|
|
249
|
+
const body = await error.responseInstance?.body.readAsText();
|
|
250
|
+
console.log('Error Body:', body);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Automatic Parameter Mapping
|
|
256
|
+
|
|
257
|
+
The `HttpClientRequest` can automatically map a `parameters` object to the URL path, query string, or body based on the URL pattern and HTTP method.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// URL: /users/:userId
|
|
261
|
+
// Method: GET
|
|
262
|
+
// Result: GET /users/123?details=true
|
|
263
|
+
await httpClient.getJson('/users/:userId', {
|
|
264
|
+
parameters: {
|
|
265
|
+
userId: 123, // Mapped to URL path because :userId exists
|
|
266
|
+
details: true, // Mapped to Query because it's GET and not in URL path
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// URL: /users
|
|
271
|
+
// Method: POST
|
|
272
|
+
// Result: POST /users with JSON body { name: 'Bob' }
|
|
273
|
+
await httpClient.postJson('/users', {
|
|
274
|
+
parameters: {
|
|
275
|
+
name: 'Bob', // Mapped to Body because it's POST and no URL params match
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 📚 API
|
|
281
|
+
|
|
282
|
+
### Configuration Functions
|
|
283
|
+
|
|
284
|
+
| Function | Description |
|
|
285
|
+
| :------------------------------------------ | :------------------------------------------------------------------- |
|
|
286
|
+
| `configureHttpClient(config)` | Configures the global `HttpClient` options, adapter, and middleware. |
|
|
287
|
+
| `configureUndiciHttpClientAdapter(options)` | Configures and registers the `UndiciHttpClientAdapter` (Node.js). |
|
|
288
|
+
| `configureNodeHttpServer(config)` | Registers the `NodeHttpServer` as the default `HttpServer`. |
|
|
289
|
+
|
|
290
|
+
### Client
|
|
291
|
+
|
|
292
|
+
| Class | Description |
|
|
293
|
+
| :------------------------ | :-------------------------------------------------------------------------------- |
|
|
294
|
+
| `HttpClient` | Main service for making HTTP requests. |
|
|
295
|
+
| `HttpClientRequest` | Represents an outgoing request. Handles parameter mapping and body normalization. |
|
|
296
|
+
| `HttpClientResponse` | Represents an incoming response. Provides access to status, headers, and body. |
|
|
297
|
+
| `HttpClientAdapter` | Abstract base class for HTTP adapters. |
|
|
298
|
+
| `UndiciHttpClientAdapter` | Adapter implementation using `undici` (Node.js). |
|
|
299
|
+
|
|
300
|
+
### Server
|
|
301
|
+
|
|
302
|
+
| Class | Description |
|
|
303
|
+
| :------------------- | :---------------------------------------------------------------- |
|
|
304
|
+
| `HttpServer` | Abstract base class for HTTP servers. Implements `AsyncIterable`. |
|
|
305
|
+
| `NodeHttpServer` | Server implementation using `node:http`. |
|
|
306
|
+
| `HttpServerRequest` | Represents an incoming request on the server. |
|
|
307
|
+
| `HttpServerResponse` | Represents an outgoing response from the server. |
|
|
308
|
+
|
|
309
|
+
### Shared / Utilities
|
|
310
|
+
|
|
311
|
+
| Class | Description |
|
|
312
|
+
| :------------- | :----------------------------------------------------------------------- |
|
|
313
|
+
| `HttpHeaders` | Typed wrapper around HTTP headers. |
|
|
314
|
+
| `HttpBody` | Helper to read request/response bodies as Text, JSON, Buffer, or Stream. |
|
|
315
|
+
| `HttpQuery` | Helper for managing URL query parameters. |
|
|
316
|
+
| `HttpForm` | Helper for `application/x-www-form-urlencoded` data. |
|
|
317
|
+
| `HttpError` | Error thrown when requests fail or return non-success status codes. |
|
|
318
|
+
| `CookieParser` | Utility to parse `Cookie` headers. |
|
|
@@ -53,7 +53,7 @@ let UndiciHttpClientAdapter = class UndiciHttpClientAdapter extends HttpClientAd
|
|
|
53
53
|
try {
|
|
54
54
|
const response = await request(httpClientRequest.url, {
|
|
55
55
|
method: httpClientRequest.method,
|
|
56
|
-
signal: httpClientRequest.abortSignal
|
|
56
|
+
signal: httpClientRequest.cancellationSignal.abortSignal,
|
|
57
57
|
headers: httpClientRequest.headers.asNormalizedObject(),
|
|
58
58
|
body,
|
|
59
59
|
headersTimeout: httpClientRequest.timeout,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CancellationSignal } from '../../cancellation/index.js';
|
|
1
|
+
import { type CancellationSignal, type CancellationSource } from '../../cancellation/index.js';
|
|
2
2
|
import type { OneOrMany, Record, TypedOmit, UndefinableJson, UndefinableJsonObject } from '../../types/index.js';
|
|
3
3
|
import { HttpForm, type HttpFormObject } from '../http-form.js';
|
|
4
4
|
import { HttpHeaders, type HttpHeadersObject } from '../http-headers.js';
|
|
@@ -23,7 +23,7 @@ export type HttpRequestAuthorization = {
|
|
|
23
23
|
};
|
|
24
24
|
export type HttpFormDataObjectValue = string | number | boolean | Uint8Array<ArrayBuffer> | Blob;
|
|
25
25
|
export type HttpFormDataObject = Record<string, OneOrMany<HttpFormDataObjectValue>>;
|
|
26
|
-
export type HttpClientRequestOptions = Partial<TypedOmit<HttpClientRequest, 'url' | 'method' | '
|
|
26
|
+
export type HttpClientRequestOptions = Partial<TypedOmit<HttpClientRequest, 'url' | 'method' | 'cancellationSignal' | 'abort' | 'headers' | 'query' | 'body'>> & {
|
|
27
27
|
urlParameter?: HttpUrlParametersObject | HttpUrlParameters;
|
|
28
28
|
headers?: HttpHeadersObject | HttpHeaders;
|
|
29
29
|
query?: HttpQueryObject | HttpQuery;
|
|
@@ -33,7 +33,7 @@ export type HttpClientRequestOptions = Partial<TypedOmit<HttpClientRequest, 'url
|
|
|
33
33
|
form?: HttpFormObject | HttpForm;
|
|
34
34
|
formData?: HttpFormDataObject | FormData;
|
|
35
35
|
};
|
|
36
|
-
|
|
36
|
+
cancellationSignal?: CancellationSource;
|
|
37
37
|
priority?: RequestPriority;
|
|
38
38
|
};
|
|
39
39
|
export type HttpRequestCredentials = 'omit' | 'same-origin' | 'include';
|
|
@@ -110,9 +110,10 @@ export declare class HttpClientRequest implements Disposable {
|
|
|
110
110
|
*/
|
|
111
111
|
context: Record;
|
|
112
112
|
/**
|
|
113
|
-
* Can be used to cancel the request.
|
|
113
|
+
* Can be used to cancel the request.
|
|
114
|
+
* Must throw HttpError.
|
|
114
115
|
*/
|
|
115
|
-
get
|
|
116
|
+
get cancellationSignal(): CancellationSignal;
|
|
116
117
|
constructor(url: string, method?: HttpMethod, options?: HttpClientRequestOptions);
|
|
117
118
|
constructor(requestObject: HttpClientRequestObject);
|
|
118
119
|
[Symbol.dispose](): void;
|
|
@@ -7,7 +7,7 @@ import { HttpHeaders } from '../http-headers.js';
|
|
|
7
7
|
import { HttpQuery } from '../http-query.js';
|
|
8
8
|
import { HttpUrlParameters } from '../http-url-parameters.js';
|
|
9
9
|
export class HttpClientRequest {
|
|
10
|
-
#
|
|
10
|
+
#cancellationToken;
|
|
11
11
|
url;
|
|
12
12
|
method;
|
|
13
13
|
headers;
|
|
@@ -75,10 +75,11 @@ export class HttpClientRequest {
|
|
|
75
75
|
*/
|
|
76
76
|
context;
|
|
77
77
|
/**
|
|
78
|
-
* Can be used to cancel the request.
|
|
78
|
+
* Can be used to cancel the request.
|
|
79
|
+
* Must throw HttpError.
|
|
79
80
|
*/
|
|
80
|
-
get
|
|
81
|
-
return this.#
|
|
81
|
+
get cancellationSignal() {
|
|
82
|
+
return this.#cancellationToken.signal;
|
|
82
83
|
}
|
|
83
84
|
constructor(urlOrObject, method, options = {}) {
|
|
84
85
|
if (isString(urlOrObject)) {
|
|
@@ -106,16 +107,14 @@ export class HttpClientRequest {
|
|
|
106
107
|
this.timeout = requestOptions.timeout ?? 30000;
|
|
107
108
|
this.throwOnNon200 = requestOptions.throwOnNon200 ?? true;
|
|
108
109
|
this.context = requestOptions.context ?? {};
|
|
109
|
-
this.#
|
|
110
|
+
this.#cancellationToken = new CancellationToken(requestOptions.cancellationSignal);
|
|
110
111
|
}
|
|
111
112
|
[Symbol.dispose]() {
|
|
112
|
-
this.#
|
|
113
|
-
this.#abortToken.complete();
|
|
113
|
+
this.#cancellationToken[Symbol.dispose]();
|
|
114
114
|
}
|
|
115
115
|
/** Abort the request */
|
|
116
116
|
abort() {
|
|
117
|
-
this.#
|
|
118
|
-
this.#abortToken.complete();
|
|
117
|
+
this.#cancellationToken.set();
|
|
119
118
|
}
|
|
120
119
|
clone() {
|
|
121
120
|
const request = new HttpClientRequest(this);
|
|
@@ -10,7 +10,6 @@ import * as Http from 'node:http';
|
|
|
10
10
|
import { Writable } from 'node:stream';
|
|
11
11
|
import { bindNodeCallback, share } from 'rxjs';
|
|
12
12
|
import { match, P } from 'ts-pattern';
|
|
13
|
-
import { CancellationToken } from '../../../cancellation/index.js';
|
|
14
13
|
import { HttpHeaders } from '../../../http/http-headers.js';
|
|
15
14
|
import { HttpQuery } from '../../../http/http-query.js';
|
|
16
15
|
import { afterResolve, inject, Singleton } from '../../../injector/index.js';
|
|
@@ -88,7 +87,7 @@ let NodeHttpServer = NodeHttpServer_1 = class NodeHttpServer extends HttpServer
|
|
|
88
87
|
}
|
|
89
88
|
if (connections > 0) {
|
|
90
89
|
this.#logger.info(`Waiting for ${connections} connections to end`);
|
|
91
|
-
await cancelableTimeout(250,
|
|
90
|
+
await cancelableTimeout(250, close$);
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
93
|
this.#requestIterable.end();
|