@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/pdf/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# @tstdl/base/pdf
|
|
2
|
+
|
|
3
|
+
A comprehensive module for generating high-fidelity PDFs from HTML, URLs, and templates using a headless browser, along with utilities for merging, inspecting, and converting PDF files.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [Prerequisites](#prerequisites)
|
|
11
|
+
- [Rendering HTML to PDF](#rendering-html-to-pdf)
|
|
12
|
+
- [Rendering a URL to PDF](#rendering-a-url-to-pdf)
|
|
13
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
14
|
+
- [Template-Based Generation](#template-based-generation)
|
|
15
|
+
- [Streaming Support](#streaming-support)
|
|
16
|
+
- [Merging PDFs](#merging-pdfs)
|
|
17
|
+
- [PDF Utilities (Page Count & Image Conversion)](#pdf-utilities-page-count--image-conversion)
|
|
18
|
+
- [Configuration & Options](#configuration--options)
|
|
19
|
+
- [📚 API](#-api)
|
|
20
|
+
|
|
21
|
+
## ✨ Features
|
|
22
|
+
|
|
23
|
+
- **Headless Browser Rendering**: Utilizes a managed browser instance to render modern HTML, CSS, and JavaScript into PDFs with pixel-perfect accuracy.
|
|
24
|
+
- **Multiple Sources**: Generate PDFs from raw HTML strings, live URLs, or structured templates.
|
|
25
|
+
- **Template Integration**: Seamlessly integrates with `@tstdl/base/templates` for dynamic, data-driven document generation.
|
|
26
|
+
- **Stream-First Architecture**: All rendering methods offer streaming counterparts for efficient memory usage with large documents.
|
|
27
|
+
- **Manipulation Utilities**: Includes tools to merge PDFs, count pages, and rasterize PDF pages into images (PNG, JPEG, etc.).
|
|
28
|
+
- **Customizable**: Extensive options for page formats, margins, locales, and network idle behaviors.
|
|
29
|
+
|
|
30
|
+
## Core Concepts
|
|
31
|
+
|
|
32
|
+
### PdfService
|
|
33
|
+
|
|
34
|
+
The `PdfService` is the main entry point for generation. It orchestrates a `BrowserController` to manage headless browser contexts and pages. When you request a PDF render, the service handles the lifecycle of opening a page, setting content, waiting for the network to settle, printing to PDF, and cleaning up resources.
|
|
35
|
+
|
|
36
|
+
### External Tools
|
|
37
|
+
|
|
38
|
+
While the core rendering logic uses a browser, the manipulation utilities (`mergePdfs`, `getPdfPageCount`, `pdfToImage`) rely on efficient external command-line tools (`qpdf`, `poppler-utils`) to perform operations without the overhead of a browser engine.
|
|
39
|
+
|
|
40
|
+
## 🚀 Basic Usage
|
|
41
|
+
|
|
42
|
+
### Prerequisites
|
|
43
|
+
|
|
44
|
+
To use the **utility functions** (`mergePdfs`, `getPdfPageCount`, `pdfToImage`), you must have the following tools installed on your system. The core `PdfService` rendering does **not** require these, but it does require a valid browser environment (managed by `@tstdl/base/browser`).
|
|
45
|
+
|
|
46
|
+
- **`qpdf`**: Required for `getPdfPageCount`.
|
|
47
|
+
- **`poppler-utils`**: Required for `mergePdfs` (uses `pdfunite`) and `pdfToImage` (uses `pdftocairo`).
|
|
48
|
+
|
|
49
|
+
**Debian/Ubuntu:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
sudo apt-get update && sudo apt-get install -y qpdf poppler-utils
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Rendering HTML to PDF
|
|
56
|
+
|
|
57
|
+
Inject the `PdfService` and use `renderHtml` to convert an HTML string directly into a PDF `Uint8Array`.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { PdfService } from '@tstdl/base/pdf';
|
|
61
|
+
import { inject } from '@tstdl/base/injector';
|
|
62
|
+
import { writeFile } from 'node:fs/promises';
|
|
63
|
+
|
|
64
|
+
const pdfService = inject(PdfService);
|
|
65
|
+
|
|
66
|
+
const html = `
|
|
67
|
+
<html>
|
|
68
|
+
<body>
|
|
69
|
+
<h1>Hello World</h1>
|
|
70
|
+
<p>This is a PDF generated from HTML.</p>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const pdfBytes = await pdfService.renderHtml(html, {
|
|
76
|
+
format: 'A4',
|
|
77
|
+
margin: { top: '2cm', right: '2cm', bottom: '2cm', left: '2cm' },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await writeFile('output.pdf', pdfBytes);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Rendering a URL to PDF
|
|
84
|
+
|
|
85
|
+
Capture a live webpage as a PDF.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { PdfService } from '@tstdl/base/pdf';
|
|
89
|
+
import { inject } from '@tstdl/base/injector';
|
|
90
|
+
import { writeFile } from 'node:fs/promises';
|
|
91
|
+
|
|
92
|
+
const pdfService = inject(PdfService);
|
|
93
|
+
|
|
94
|
+
// Renders the target URL
|
|
95
|
+
const pdfBytes = await pdfService.renderUrl('https://example.com', {
|
|
96
|
+
format: 'Letter',
|
|
97
|
+
renderBackground: true,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await writeFile('website.pdf', pdfBytes);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 🔧 Advanced Topics
|
|
104
|
+
|
|
105
|
+
### Template-Based Generation
|
|
106
|
+
|
|
107
|
+
For complex reports, use `pdfTemplate` to define the structure and `@tstdl/base/templates` to handle data injection.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { PdfService, pdfTemplate } from '@tstdl/base/pdf';
|
|
111
|
+
import { stringTemplateField } from '@tstdl/base/templates';
|
|
112
|
+
import { inject } from '@tstdl/base/injector';
|
|
113
|
+
import { writeFile } from 'node:fs/promises';
|
|
114
|
+
|
|
115
|
+
const pdfService = inject(PdfService);
|
|
116
|
+
|
|
117
|
+
// Define the template structure
|
|
118
|
+
const invoiceTemplate = pdfTemplate(
|
|
119
|
+
'invoice',
|
|
120
|
+
{
|
|
121
|
+
header: stringTemplateField({
|
|
122
|
+
template: '<div style="font-size: 10px; text-align: center;">Invoice #{{ invoiceId }}</div>',
|
|
123
|
+
}),
|
|
124
|
+
body: stringTemplateField({
|
|
125
|
+
template: '<h1>Total Due: {{ amount }} {{ currency }}</h1><p>Customer: {{ customer }}</p>',
|
|
126
|
+
}),
|
|
127
|
+
footer: stringTemplateField({
|
|
128
|
+
template: '<div style="font-size: 10px;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
displayHeaderFooter: true,
|
|
133
|
+
margin: { top: '2cm', bottom: '2cm' },
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Render with context data
|
|
138
|
+
const context = {
|
|
139
|
+
invoiceId: 'INV-2024-001',
|
|
140
|
+
amount: 150.0,
|
|
141
|
+
currency: 'USD',
|
|
142
|
+
customer: 'Acme Corp',
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const pdfBytes = await pdfService.renderTemplate(invoiceTemplate, context);
|
|
146
|
+
await writeFile('invoice.pdf', pdfBytes);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Streaming Support
|
|
150
|
+
|
|
151
|
+
For large documents or high-throughput scenarios, use the `*Stream` methods to handle data as it is generated, reducing memory footprint.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { PdfService } from '@tstdl/base/pdf';
|
|
155
|
+
import { inject } from '@tstdl/base/injector';
|
|
156
|
+
import { createWriteStream } from 'node:fs';
|
|
157
|
+
import { Writable } from 'node:stream';
|
|
158
|
+
|
|
159
|
+
const pdfService = inject(PdfService);
|
|
160
|
+
|
|
161
|
+
// Get a ReadableStream<Uint8Array>
|
|
162
|
+
const pdfStream = pdfService.renderHtmlStream('<h1>Large Document</h1>...');
|
|
163
|
+
|
|
164
|
+
// Pipe to a file (Node.js stream adapter required for direct piping in some envs,
|
|
165
|
+
// or simply iterate the web stream)
|
|
166
|
+
const fileStream = createWriteStream('streamed-output.pdf');
|
|
167
|
+
const writer = Writable.toWeb(fileStream);
|
|
168
|
+
|
|
169
|
+
await pdfStream.pipeTo(writer);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Merging PDFs
|
|
173
|
+
|
|
174
|
+
Combine multiple PDF sources (file paths, buffers, or streams) into a single document.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { mergePdfs } from '@tstdl/base/pdf';
|
|
178
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
179
|
+
|
|
180
|
+
const coverPage = await readFile('cover.pdf');
|
|
181
|
+
const contentPath = '/tmp/content.pdf';
|
|
182
|
+
|
|
183
|
+
// Merge a buffer and a file path
|
|
184
|
+
const mergedBytes = await mergePdfs([coverPage, contentPath]);
|
|
185
|
+
|
|
186
|
+
await writeFile('full-report.pdf', mergedBytes);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### PDF Utilities (Page Count & Image Conversion)
|
|
190
|
+
|
|
191
|
+
Inspect and convert existing PDFs.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { getPdfPageCount, pdfToImage } from '@tstdl/base/pdf';
|
|
195
|
+
import { writeFile } from 'node:fs/promises';
|
|
196
|
+
|
|
197
|
+
// 1. Get Page Count
|
|
198
|
+
const pages = await getPdfPageCount('report.pdf');
|
|
199
|
+
console.log(`Document has ${pages} pages.`);
|
|
200
|
+
|
|
201
|
+
// 2. Convert Page 1 to a JPEG image
|
|
202
|
+
const imageBytes = await pdfToImage('report.pdf', 1, 1024, 'jpeg');
|
|
203
|
+
await writeFile('page-1-preview.jpg', imageBytes);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Configuration & Options
|
|
207
|
+
|
|
208
|
+
The `PdfServiceRenderOptions` interface allows fine-grained control over the output. You can also configure the `PdfService` globally by providing `PdfServiceArgument` during injection (e.g., setting a default `locale` or custom `browserArguments`).
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const options = {
|
|
212
|
+
format: 'A4',
|
|
213
|
+
landscape: true,
|
|
214
|
+
scale: 0.8,
|
|
215
|
+
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' },
|
|
216
|
+
renderBackground: true,
|
|
217
|
+
waitForNetworkIdle: true, // Wait for network requests to finish
|
|
218
|
+
delay: 100, // Additional delay in ms before printing
|
|
219
|
+
locale: 'de-DE', // Set browser locale for date/currency formatting
|
|
220
|
+
log: true, // Enable logging for the browser page (Trace level by default)
|
|
221
|
+
browserContext: existingContext, // Optional: reuse an existing browser context
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
await pdfService.renderHtml(html, options);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## 📚 API
|
|
228
|
+
|
|
229
|
+
| Export | Type | Description |
|
|
230
|
+
| :-------------------------------- | :--------- | :---------------------------------------------------------------------- |
|
|
231
|
+
| **`PdfService`** | `class` | Singleton service for generating PDFs via headless browser. |
|
|
232
|
+
| `PdfService.renderHtml` | `method` | Renders HTML string to `Promise<Uint8Array>`. |
|
|
233
|
+
| `PdfService.renderHtmlStream` | `method` | Renders HTML string to `ReadableStream<Uint8Array>`. |
|
|
234
|
+
| `PdfService.renderUrl` | `method` | Renders a URL to `Promise<Uint8Array>`. |
|
|
235
|
+
| `PdfService.renderUrlStream` | `method` | Renders a URL to `ReadableStream<Uint8Array>`. |
|
|
236
|
+
| `PdfService.renderTemplate` | `method` | Renders a `PdfTemplate` to `Promise<Uint8Array>`. |
|
|
237
|
+
| `PdfService.renderTemplateStream` | `method` | Renders a `PdfTemplate` to `ReadableStream<Uint8Array>`. |
|
|
238
|
+
| **`pdfTemplate`** | `function` | Factory to create a `PdfTemplate` definition. |
|
|
239
|
+
| **`getPdfPageCount`** | `function` | Returns the number of pages in a PDF. Requires `qpdf`. |
|
|
240
|
+
| **`mergePdfs`** | `function` | Merges multiple PDFs into one `Uint8Array`. Requires `pdfunite`. |
|
|
241
|
+
| **`mergePdfsStream`** | `function` | Merges multiple PDFs into a `ReadableStream`. Requires `pdfunite`. |
|
|
242
|
+
| **`pdfToImage`** | `function` | Converts a specific PDF page to an image buffer. Requires `pdftocairo`. |
|
|
243
|
+
| `PdfServiceRenderOptions` | `class` | Configuration object for rendering (extends browser PDF options). |
|
|
244
|
+
| `PdfTemplate` | `class` | Class representing a PDF template structure. |
|
|
245
|
+
| `PdfServiceOptions` | `type` | Global configuration options for `PdfService`. |
|
|
246
|
+
| `PdfServiceArgument` | `type` | Full argument type for `PdfService` injection. |
|
package/polyfills.js
CHANGED
package/pool/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Pool
|
|
2
|
+
|
|
3
|
+
A robust, asynchronous object pooling implementation for managing reusable resources. It handles instantiation, lifecycle management, and concurrency limits to optimize performance and resource usage.
|
|
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
|
+
- [Manual Resource Management](#manual-resource-management)
|
|
12
|
+
- [Error Handling Strategies](#error-handling-strategies)
|
|
13
|
+
- [Pool Lifecycle](#pool-lifecycle)
|
|
14
|
+
- [Resource Verification & Ownership](#resource-verification--ownership)
|
|
15
|
+
- [📚 API](#-api)
|
|
16
|
+
|
|
17
|
+
## ✨ Features
|
|
18
|
+
|
|
19
|
+
- **Async Lifecycle**: Supports asynchronous factory and disposer functions for complex resource initialization.
|
|
20
|
+
- **Concurrency Control**: Automatically limits the number of active instances. Defaults to half of CPU cores (or 4 if unknown).
|
|
21
|
+
- **Queueing**: Waits for available instances when the pool is exhausted.
|
|
22
|
+
- **Scoped Usage**: Provides a `use()` method that automatically handles acquisition and release (RAII-style).
|
|
23
|
+
- **Error Handling**: Configurable options to dispose of instances that encounter errors instead of returning them to the pool.
|
|
24
|
+
- **Modern Standards**: Implements `AsyncDisposable` for integration with the `using` keyword.
|
|
25
|
+
- **Resource Ownership**: Track which resources belong to the pool to prevent accidental double-releases or cross-pool contamination.
|
|
26
|
+
|
|
27
|
+
## Core Concepts
|
|
28
|
+
|
|
29
|
+
Object pooling is a design pattern used to improve performance and efficiency when the cost of initializing a class instance is high, or when the number of instances in use at any one time needs to be limited (e.g., database connections, worker threads).
|
|
30
|
+
|
|
31
|
+
The `Pool` class manages a collection of objects:
|
|
32
|
+
|
|
33
|
+
1. **Factory**: Creates new instances when the pool is not full and no free instances are available.
|
|
34
|
+
2. **Disposer**: Cleans up instances when they are removed from the pool or when the pool is destroyed.
|
|
35
|
+
3. **Acquisition**: Clients `get` an instance, removing it from the available set.
|
|
36
|
+
4. **Release**: Clients `release` an instance, returning it to the available set for reuse.
|
|
37
|
+
|
|
38
|
+
## 🚀 Basic Usage
|
|
39
|
+
|
|
40
|
+
The most common and recommended way to use the pool is via the `use()` method. This ensures that resources are always released back to the pool, even if an error occurs.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { Pool } from '@tstdl/base/pool';
|
|
44
|
+
import { ConsoleLogger } from '@tstdl/base/logger'; // Assuming a logger implementation
|
|
45
|
+
|
|
46
|
+
// 1. Define the resource you want to pool
|
|
47
|
+
class Worker {
|
|
48
|
+
readonly id = Math.random().toString(36).slice(2);
|
|
49
|
+
|
|
50
|
+
async process(data: string): Promise<string> {
|
|
51
|
+
return `Worker ${this.id} processed ${data}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Create the pool
|
|
56
|
+
const logger = new ConsoleLogger();
|
|
57
|
+
|
|
58
|
+
const workerPool = new Pool<Worker>(
|
|
59
|
+
// Factory: Creates a new instance
|
|
60
|
+
() => new Worker(),
|
|
61
|
+
// Disposer: Cleans up the instance (optional logic here)
|
|
62
|
+
(worker) => console.log(`Disposing worker ${worker.id}`),
|
|
63
|
+
logger,
|
|
64
|
+
{ size: 2 }, // Limit to 2 concurrent workers
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
// 3. Use the pool
|
|
69
|
+
// The 'use' method acquires a worker, runs the callback, and releases the worker.
|
|
70
|
+
const result = await workerPool.use(async (worker) => {
|
|
71
|
+
console.log(`Acquired worker ${worker.id}`);
|
|
72
|
+
return await worker.process('Hello World');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(result);
|
|
76
|
+
|
|
77
|
+
// Clean up the pool when done
|
|
78
|
+
await workerPool.dispose();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 🔧 Advanced Topics
|
|
85
|
+
|
|
86
|
+
### Manual Resource Management
|
|
87
|
+
|
|
88
|
+
If you need fine-grained control over when a resource is acquired and released, you can use `get()` and `release()` directly. **Note:** You must ensure `release()` is called, usually within a `finally` block.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { Pool } from '@tstdl/base/pool';
|
|
92
|
+
import { ConsoleLogger } from '@tstdl/base/logger';
|
|
93
|
+
|
|
94
|
+
const pool = new Pool<object>(
|
|
95
|
+
() => ({}),
|
|
96
|
+
() => {},
|
|
97
|
+
new ConsoleLogger(),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
async function manualFlow() {
|
|
101
|
+
// Acquire
|
|
102
|
+
const instance = await pool.get();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
// Do work
|
|
106
|
+
console.log('Using instance manually');
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Handle error
|
|
109
|
+
} finally {
|
|
110
|
+
// Release back to pool
|
|
111
|
+
await pool.release(instance);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Error Handling Strategies
|
|
117
|
+
|
|
118
|
+
By default, if an error occurs inside `use()`, the instance is released back to the pool for reuse. However, for some resources (like network sockets), an error might indicate the instance is corrupt. You can configure the pool to dispose of instances on error.
|
|
119
|
+
|
|
120
|
+
**Global Configuration:**
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const pool = new Pool<object>(
|
|
124
|
+
factory,
|
|
125
|
+
disposer,
|
|
126
|
+
logger,
|
|
127
|
+
{ disposeOnError: true }, // Always dispose if use() throws
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Per-Call Configuration:**
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
await pool.use(
|
|
135
|
+
async (conn) => {
|
|
136
|
+
throw new Error('Connection lost');
|
|
137
|
+
},
|
|
138
|
+
{ disposeOnError: true }, // Override for this specific call
|
|
139
|
+
);
|
|
140
|
+
// The instance used above will be disposed, not released.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Pool Lifecycle
|
|
144
|
+
|
|
145
|
+
The pool implements `AsyncDisposable`. When disposed, it clears all free instances and marks itself as disposed. Any currently used instances will be disposed when they are returned.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
await using pool = new Pool<object>(...);
|
|
149
|
+
|
|
150
|
+
// ... use pool ...
|
|
151
|
+
|
|
152
|
+
// End of scope: pool.dispose() is called automatically.
|
|
153
|
+
// All idle resources are destroyed.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Resource Verification & Ownership
|
|
157
|
+
|
|
158
|
+
The pool provides methods to track and manage instances explicitly. This is useful for debugging or when integrating with external systems that might attempt to return objects to the wrong pool.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const isOwned = workerPool.owns(manualWorker);
|
|
162
|
+
const currentPoolSize = workerPool.length;
|
|
163
|
+
|
|
164
|
+
if (manualWorker.isCorrupt) {
|
|
165
|
+
// Removes from pool and calls the disposer
|
|
166
|
+
await workerPool.disposeInstance(manualWorker);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 📚 API
|
|
171
|
+
|
|
172
|
+
### Classes
|
|
173
|
+
|
|
174
|
+
| Class | Description |
|
|
175
|
+
| :-------- | :--------------------------------------------------------- |
|
|
176
|
+
| `Pool<T>` | The main class for managing a pool of objects of type `T`. |
|
|
177
|
+
|
|
178
|
+
### Types & Interfaces
|
|
179
|
+
|
|
180
|
+
| Type | Description |
|
|
181
|
+
| :------------------------ | :----------------------------------------------------------------------------- |
|
|
182
|
+
| `PoolOptions` | Configuration options for the pool (size, error handling). |
|
|
183
|
+
| `PoolUseOptions` | Options for the `use` method (override error handling). |
|
|
184
|
+
| `PoolInstanceFactory<T>` | Function type for creating new instances: `() => T \| Promise<T>`. |
|
|
185
|
+
| `PoolInstanceDisposer<T>` | Function type for disposing instances: `(instance: T) => any \| Promise<any>`. |
|
|
186
|
+
|
|
187
|
+
### Pool Properties & Methods
|
|
188
|
+
|
|
189
|
+
| Member | Signature | Description |
|
|
190
|
+
| :---------------- | :------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- |
|
|
191
|
+
| `length` | `number` (getter) | The total number of instances (free + used + being created). |
|
|
192
|
+
| `get` | `(): Promise<T>` | Acquires an instance from the pool. Waits if none are available. |
|
|
193
|
+
| `release` | `(instance: T): Promise<void>` | Returns an instance to the pool. |
|
|
194
|
+
| `use` | `<R>(handler: (instance: T) => R \| Promise<R>, options?: PoolUseOptions): Promise<R>` | Acquires an instance, executes the handler, and releases the instance. |
|
|
195
|
+
| `disposeInstance` | `(instance: T): Promise<void>` | Removes a specific instance from the pool and disposes of it. |
|
|
196
|
+
| `owns` | `(instance: T): boolean` | Checks if an instance belongs to this pool (either in-use or idle). |
|
|
197
|
+
| `dispose` | `(): Promise<void>` | Shuts down the pool and disposes of all idle instances. |
|
|
198
|
+
| `[asyncDispose]` | `(): Promise<void>` | Implementation of `AsyncDisposable` for use with the `using` keyword. |
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Process (`@tstdl/base/process`)
|
|
2
|
+
|
|
3
|
+
A modern, promise- and stream-based wrapper for spawning child processes in Node.js, built on the Web Streams API. It simplifies process interaction by treating processes as transform streams and providing ergonomic async/await helpers.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [Spawn and Stay Connected](#spawn-and-stay-connected)
|
|
11
|
+
- [Spawn and Wait](#spawn-and-wait)
|
|
12
|
+
- [Spawn, Wait and Read](#spawn-wait-and-read)
|
|
13
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
14
|
+
- [Writing to Standard Input](#writing-to-standard-input)
|
|
15
|
+
- [Piping Processes](#piping-processes)
|
|
16
|
+
- [Handling Errors and Exit Codes](#handling-errors-and-exit-codes)
|
|
17
|
+
- [Binary Output](#binary-output)
|
|
18
|
+
- [📚 API](#-api)
|
|
19
|
+
|
|
20
|
+
## ✨ Features
|
|
21
|
+
|
|
22
|
+
- **Web Streams Interface**: Exposes the child process as a `TransformStream`, where the writable side is `stdin` and the readable side is `stdout`.
|
|
23
|
+
- **Promise-based API**: Fully async/await compatible for spawning and waiting for termination.
|
|
24
|
+
- **Convenience Helpers**: Built-in methods to read `stdout` and `stderr` as strings or byte arrays, and to write strings, buffers, or streams to `stdin`.
|
|
25
|
+
- **One-liner Helpers**: `spawnWaitCommand` and `spawnWaitReadCommand` for common tasks without manual process management.
|
|
26
|
+
- **Error Management**: Configurable handling of non-zero exit codes via the `wait()` method.
|
|
27
|
+
- **Type Safety**: Fully typed with TypeScript.
|
|
28
|
+
|
|
29
|
+
## Core Concepts
|
|
30
|
+
|
|
31
|
+
The primary entry point is the `spawnCommand` function. Unlike the standard Node.js `spawn`, this function returns a `SpawnCommandResult` object which implements the `TransformStream` interface.
|
|
32
|
+
|
|
33
|
+
- **Writable Side (`stdin`)**: You can write to the process's standard input via the `writable` property or the `write()` helper.
|
|
34
|
+
- **Readable Side (`stdout`)**: You can read from the process's standard output via the `readable` property or `readOutput()` helpers.
|
|
35
|
+
- **`stderr`**: Available as a separate `ReadableStream` or via `readError()` helpers.
|
|
36
|
+
|
|
37
|
+
This design allows you to seamlessly pipe data through processes using standard Web Streams API methods like `pipeThrough` and `pipeTo`.
|
|
38
|
+
|
|
39
|
+
## 🚀 Basic Usage
|
|
40
|
+
|
|
41
|
+
### Spawn and Stay Connected
|
|
42
|
+
|
|
43
|
+
Spawn a command and interact with it while it's running.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { spawnCommand } from '@tstdl/base/process';
|
|
47
|
+
|
|
48
|
+
async function listFiles() {
|
|
49
|
+
// Spawn 'ls -la'
|
|
50
|
+
const command = await spawnCommand('ls', ['-la']);
|
|
51
|
+
|
|
52
|
+
// Helper to read the entire stdout as a UTF-8 string
|
|
53
|
+
const output = await command.readOutput();
|
|
54
|
+
console.log('Output:\n', output);
|
|
55
|
+
|
|
56
|
+
// Wait for the process to exit.
|
|
57
|
+
// This will throw an error if the exit code is not 0.
|
|
58
|
+
const { code } = await command.wait();
|
|
59
|
+
console.log(`Process finished with code ${code}`);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Spawn and Wait
|
|
64
|
+
|
|
65
|
+
If you only care about the exit code and signal, use `spawnWaitCommand`.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { spawnWaitCommand } from '@tstdl/base/process';
|
|
69
|
+
|
|
70
|
+
async function checkFile() {
|
|
71
|
+
const { code } = await spawnWaitCommand('test', ['-f', 'config.json']);
|
|
72
|
+
console.log(code === 0 ? 'File exists' : 'File missing');
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Spawn, Wait and Read
|
|
77
|
+
|
|
78
|
+
Use `spawnWaitReadCommand` to run a command and get its full output in one go.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { spawnWaitReadCommand } from '@tstdl/base/process';
|
|
82
|
+
|
|
83
|
+
async function getUptime() {
|
|
84
|
+
const result = await spawnWaitReadCommand('string', 'uptime');
|
|
85
|
+
console.log('System uptime:', result.output.trim());
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 🔧 Advanced Topics
|
|
90
|
+
|
|
91
|
+
### Writing to Standard Input
|
|
92
|
+
|
|
93
|
+
You can write data to the process using the `write` helper, which accepts strings, `Uint8Array`s, or `ReadableStream`s. It handles encoding and stream closing automatically.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { spawnCommand } from '@tstdl/base/process';
|
|
97
|
+
|
|
98
|
+
async function grepText() {
|
|
99
|
+
const grep = await spawnCommand('grep', ['world']);
|
|
100
|
+
|
|
101
|
+
// Write text to stdin. The promise resolves when writing is complete.
|
|
102
|
+
await grep.write('hello\nworld\nfoo\nbar');
|
|
103
|
+
|
|
104
|
+
// Read the filtered output
|
|
105
|
+
const result = await grep.readOutput();
|
|
106
|
+
console.log('Grep Result:', result.trim()); // Output: "world"
|
|
107
|
+
|
|
108
|
+
await grep.wait();
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Piping Processes
|
|
113
|
+
|
|
114
|
+
Because `SpawnCommandResult` is a `TransformStream`, you can pipe the output of one command directly into the input of another.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { spawnCommand } from '@tstdl/base/process';
|
|
118
|
+
|
|
119
|
+
async function pipeExample() {
|
|
120
|
+
const ls = await spawnCommand('ls', ['-la']);
|
|
121
|
+
const grep = await spawnCommand('grep', ['.ts']);
|
|
122
|
+
|
|
123
|
+
// Pipe ls stdout -> grep stdin
|
|
124
|
+
await ls.readable.pipeTo(grep.writable);
|
|
125
|
+
|
|
126
|
+
// Read the final output from grep
|
|
127
|
+
const output = await grep.readOutput();
|
|
128
|
+
console.log('TypeScript files:\n', output);
|
|
129
|
+
|
|
130
|
+
// Wait for both to finish
|
|
131
|
+
await Promise.all([ls.wait(), grep.wait()]);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Handling Errors and Exit Codes
|
|
136
|
+
|
|
137
|
+
By default, `wait()` throws an error if the process exits with a non-zero code. The error message attempts to capture the content of `stderr`.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { spawnCommand } from '@tstdl/base/process';
|
|
141
|
+
|
|
142
|
+
async function errorHandling() {
|
|
143
|
+
const cat = await spawnCommand('cat', ['non-existent-file.txt']);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
await cat.wait();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('Command failed!');
|
|
149
|
+
console.error((error as Error).message);
|
|
150
|
+
// Output: "cat: non-existent-file.txt: No such file or directory"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Disable automatic throwing
|
|
155
|
+
async function manualErrorHandling() {
|
|
156
|
+
const cat = await spawnCommand('cat', ['non-existent-file.txt']);
|
|
157
|
+
const { code } = await cat.wait({ throwOnNonZeroExitCode: false });
|
|
158
|
+
|
|
159
|
+
if (code !== 0) {
|
|
160
|
+
const errorOutput = await cat.readError();
|
|
161
|
+
console.error(`Exited with ${code}. Error: ${errorOutput}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Binary Output
|
|
167
|
+
|
|
168
|
+
For commands that produce binary data (like images or compressed files), use `readOutputBytes` or `spawnWaitReadCommand` with the `'binary'` format.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { spawnCommand, spawnWaitReadCommand } from '@tstdl/base/process';
|
|
172
|
+
|
|
173
|
+
async function getBinary() {
|
|
174
|
+
const result = await spawnWaitReadCommand('binary', 'gzip', ['-c', 'file.txt']);
|
|
175
|
+
console.log(`Compressed size: ${result.output.length} bytes`);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## 📚 API
|
|
180
|
+
|
|
181
|
+
### Functions
|
|
182
|
+
|
|
183
|
+
| Function | Description |
|
|
184
|
+
| :--------------------- | :--------------------------------------------------------------------------------------- |
|
|
185
|
+
| `spawnCommand` | Spawns a command and returns an interactive `SpawnCommandResult`. |
|
|
186
|
+
| `spawnWaitCommand` | Spawns a command and waits for its termination. Returns `WaitResult`. |
|
|
187
|
+
| `spawnWaitReadCommand` | Spawns a command, waits for termination, and returns `WaitReadResult` with full output. |
|
|
188
|
+
|
|
189
|
+
### `SpawnCommandResult`
|
|
190
|
+
|
|
191
|
+
The object returned by `spawnCommand`. It implements `TransformStream<Uint8Array, Uint8Array>` and provides additional helpers:
|
|
192
|
+
|
|
193
|
+
| Property / Method | Type | Description |
|
|
194
|
+
| :---------------------------- | :------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------- |
|
|
195
|
+
| `process` | `ChildProcess` | The underlying Node.js `ChildProcess` instance. |
|
|
196
|
+
| `stderr` | `ReadableStream<Uint8Array>` | Stream for the process's standard error. |
|
|
197
|
+
| `write(data, options?)` | `Promise<void>` | Writes `string`, `Uint8Array`, or `ReadableStream` to `stdin`. Resolves when writing is done. |
|
|
198
|
+
| `writeInBackground(data, opts)`| `void` | Fire-and-forget version of `write`. Errors are handled by closing streams. |
|
|
199
|
+
| `readOutput()` | `Promise<string>` | Reads all data from `stdout` as a UTF-8 string. |
|
|
200
|
+
| `readOutputBytes()` | `Promise<Uint8Array>` | Reads all data from `stdout` as a byte array. |
|
|
201
|
+
| `readError()` | `Promise<string>` | Reads all data from `stderr` as a UTF-8 string. |
|
|
202
|
+
| `readErrorBytes()` | `Promise<Uint8Array>` | Reads all data from `stderr` as a byte array. |
|
|
203
|
+
| `wait(options?)` | `Promise<WaitResult>` | Waits for the process to exit. Throws if exit code is non-zero unless configured otherwise. |
|
|
204
|
+
| `waitRead(format, options?)` | `Promise<WaitReadResult>` | Waits for the process to exit and returns all output/error data. |
|
|
205
|
+
| `handleNonZeroExitCode()` | `void` | Manually triggers the internal error handling logic (aborting streams) if the process failed. |
|
|
206
|
+
|
|
207
|
+
### Types
|
|
208
|
+
|
|
209
|
+
#### `SpawnOptions`
|
|
210
|
+
|
|
211
|
+
| Property | Type | Description |
|
|
212
|
+
| :----------------- | :----------------------- | :----------------------------------------- |
|
|
213
|
+
| `arguments` | `string[]` | Arguments for the command. |
|
|
214
|
+
| `workingDirectory` | `string` | Current working directory for the process. |
|
|
215
|
+
| `environment` | `Record<string, string>` | Environment variables for the process. |
|
|
216
|
+
|
|
217
|
+
#### `WaitOptions`
|
|
218
|
+
|
|
219
|
+
| Property | Type | Default | Description |
|
|
220
|
+
| :----------------------- | :-------- | :------ | :-------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `throwOnNonZeroExitCode` | `boolean` | `true` | If true, `wait()` rejects with an error containing `stderr` output if the exit code is not 0. |
|
|
222
|
+
|
|
223
|
+
#### `WaitResult`
|
|
224
|
+
|
|
225
|
+
| Property | Type | Description |
|
|
226
|
+
| :------- | :--------------- | :---------------------------------------------------- |
|
|
227
|
+
| `code` | `number \| null` | The exit code if the child exited on its own. |
|
|
228
|
+
| `signal` | `string \| null` | The signal by which the child process was terminated. |
|
|
229
|
+
|
|
230
|
+
#### `WaitReadResult`
|
|
231
|
+
|
|
232
|
+
Extends `WaitResult` with:
|
|
233
|
+
|
|
234
|
+
| Property | Type | Description |
|
|
235
|
+
| :------- | :---------------------- | :----------------------------------------- |
|
|
236
|
+
| `output` | `string \| Uint8Array` | The full content of `stdout`. |
|
|
237
|
+
| `error` | `string \| Uint8Array` | The full content of `stderr`. |
|