@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
@@ -7,7 +7,6 @@ import { TaskQueue } from '../../task-queue/index.js';
7
7
  import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
8
8
  import { DocumentPropertyDataType } from '../models/document-property.model.js';
9
9
  import { configureDocumentManagement } from '../server/configure.js';
10
- import { migrateDocumentManagementSchema } from '../server/module.js';
11
10
  import { DocumentCategoryTypeService } from '../server/services/document-category-type.service.js';
12
11
  import { DocumentManagementAiService } from '../server/services/document-management-ai.service.js';
13
12
  import { DocumentManagementService } from '../server/services/document-management.service.js';
@@ -24,7 +23,7 @@ describe('Document Management Extended Suite', () => {
24
23
  const otherTenantId = crypto.randomUUID();
25
24
  beforeAll(async () => {
26
25
  ({ injector, database } = await setupIntegrationTest({
27
- modules: { taskQueue: false, messageBus: true },
26
+ modules: { taskQueue: false, messageBus: true, documentManagement: true },
28
27
  orm: { schema },
29
28
  }));
30
29
  injector.register(GenkitModuleOptions, { useValue: {} });
@@ -52,8 +51,8 @@ describe('Document Management Extended Suite', () => {
52
51
  fileObjectStorageModule: 'documents',
53
52
  fileUploadObjectStorageModule: 'document-uploads',
54
53
  filePreviewObjectStorageModule: 'document-previews',
54
+ injector,
55
55
  });
56
- await runInInjectionContext(injector, migrateDocumentManagementSchema);
57
56
  documentManagementService = await injector.resolveAsync(DocumentManagementService);
58
57
  categoryTypeService = await injector.resolveAsync(DocumentCategoryTypeService);
59
58
  propertyService = await injector.resolveAsync(DocumentPropertyService);
package/dom/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # DOM Utilities
2
+
3
+ A collection of browser-specific utilities for file interactions and reactive DOM observation. This module bridges standard DOM APIs with RxJS Observables and Signals to provide a modern, declarative developer experience for handling file downloads, uploads, and element observation.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [✨ Features](#-features)
8
+ 2. [Core Concepts](#core-concepts)
9
+ 3. [🚀 Basic Usage](#-basic-usage)
10
+ - [File Download](#file-download)
11
+ - [File Selection](#file-selection)
12
+ - [Reactive Resize Observation](#reactive-resize-observation)
13
+ 4. [🔧 Advanced Topics](#-advanced-topics)
14
+ - [Using Signals for Observation](#using-signals-for-observation)
15
+ - [Media Query Observation](#media-query-observation)
16
+ - [Touch Event Streams](#touch-event-streams)
17
+ - [Mutation & Performance Observation](#mutation--performance-observation)
18
+ 5. [📚 API](#-api)
19
+
20
+ ## ✨ Features
21
+
22
+ - **Programmatic File Download**: Trigger browser downloads from `Blob` or `File` objects without manual DOM manipulation.
23
+ - **File Selection Dialog**: Open the native file picker programmatically and await the result.
24
+ - **Reactive Observers**: RxJS Observable and Signal wrappers for standard browser observers:
25
+ - `IntersectionObserver` (Visibility)
26
+ - `ResizeObserver` (Dimensions)
27
+ - `MutationObserver` (DOM Tree Changes)
28
+ - `PerformanceObserver` (Metrics)
29
+ - `matchMedia` (Media Queries)
30
+ - **Touch Event Stream**: Normalized observable stream for touch interactions.
31
+
32
+ ## Core Concepts
33
+
34
+ ### File Handling
35
+
36
+ Standard HTML file inputs and download links often require imperative DOM manipulation (e.g., creating a hidden `<a>` tag, clicking it, and removing it). This module abstracts these patterns into simple function calls (`downloadFile`, `openFileSelectDialog`).
37
+
38
+ ### Reactive Observation
39
+
40
+ Modern browser APIs like `ResizeObserver` and `IntersectionObserver` rely on callbacks. This module converts these APIs into **RxJS Observables** (suffixed with `$`) and **Signals** (no suffix). This allows you to compose DOM events with other asynchronous streams or use them directly in signal-based UI frameworks.
41
+
42
+ The implementation handles resource management automatically. For example, `observeIntersection$` uses a caching mechanism to share underlying `IntersectionObserver` instances across multiple subscriptions with identical configurations.
43
+
44
+ ## 🚀 Basic Usage
45
+
46
+ ### File Download
47
+
48
+ Trigger a download for a generated Blob.
49
+
50
+ ```ts
51
+ import { downloadFile } from '@tstdl/base/dom';
52
+
53
+ const content = new Blob(['Hello, World!'], { type: 'text/plain' });
54
+
55
+ // Triggers a download of "hello.txt" in the browser
56
+ downloadFile(content, 'hello.txt');
57
+ ```
58
+
59
+ ### File Selection
60
+
61
+ Open a file dialog and await the user's selection.
62
+
63
+ ```ts
64
+ import { openFileSelectDialog } from '@tstdl/base/dom';
65
+
66
+ async function handleUpload() {
67
+ // Opens the native file picker
68
+ const files = await openFileSelectDialog({
69
+ accept: ['.jpg', '.png', 'image/jpeg'],
70
+ multiple: true,
71
+ });
72
+
73
+ if (files) {
74
+ console.log(`User selected ${files.length} files.`);
75
+ files.forEach((file) => console.log(file.name));
76
+ } else {
77
+ console.log('User cancelled the dialog.');
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Reactive Resize Observation
83
+
84
+ Observe changes to an element's size using RxJS.
85
+
86
+ ```ts
87
+ import { observeResize$ } from '@tstdl/base/dom';
88
+
89
+ const element = document.querySelector('#my-component')!;
90
+
91
+ const subscription = observeResize$(element).subscribe((entry) => {
92
+ const { width, height } = entry.contentRect;
93
+ console.log(`New size: ${width}x${height}`);
94
+ });
95
+
96
+ // Cleanup when done
97
+ // subscription.unsubscribe();
98
+ ```
99
+
100
+ ## 🔧 Advanced Topics
101
+
102
+ ### Using Signals for Observation
103
+
104
+ For state-driven applications, you can use the Signal variants of the observers. These provide the current state synchronously.
105
+
106
+ ```ts
107
+ import { observeIntersection } from '@tstdl/base/dom';
108
+ import { effect } from '@tstdl/base/signals'; // Assuming a signal effect implementation
109
+
110
+ const element = document.querySelector('#lazy-image')!;
111
+
112
+ // Returns a Signal<IntersectionObserverEntry | undefined>
113
+ const intersectionSignal = observeIntersection(element, { threshold: 0.5 });
114
+
115
+ effect(() => {
116
+ const entry = intersectionSignal();
117
+ if (entry?.isIntersecting) {
118
+ console.log('Element is at least 50% visible!');
119
+ // Load image logic here...
120
+ }
121
+ });
122
+ ```
123
+
124
+ ### Media Query Observation
125
+
126
+ Reactively track media query matches (e.g., dark mode or viewport width).
127
+
128
+ ```ts
129
+ import { observeMediaQuery$ } from '@tstdl/base/dom';
130
+
131
+ // RxJS Observable
132
+ observeMediaQuery$('(prefers-color-scheme: dark)').subscribe((isDark) => {
133
+ console.log('Dark mode is:', isDark ? 'Enabled' : 'Disabled');
134
+ });
135
+
136
+ // Or as a Signal
137
+ import { observeMediaQuery } from '@tstdl/base/dom';
138
+ const isMobile = observeMediaQuery('(max-width: 768px)');
139
+ ```
140
+
141
+ ### Touch Event Streams
142
+
143
+ Normalize touch events (`start`, `move`, `end`, `cancel`) into a single observable stream. This is useful for building custom gestures.
144
+
145
+ ```ts
146
+ import { observeTouch$ } from '@tstdl/base/dom';
147
+
148
+ const canvas = document.querySelector('canvas')!;
149
+
150
+ observeTouch$(canvas).subscribe((event) => {
151
+ if (event.start) {
152
+ console.log('Touch started at', event.start.touches[0].clientX);
153
+ }
154
+ if (event.move) {
155
+ // Handle drag
156
+ }
157
+ if (event.end) {
158
+ console.log('Touch ended');
159
+ }
160
+ });
161
+ ```
162
+
163
+ ### Mutation & Performance Observation
164
+
165
+ Observe DOM tree changes or performance metrics.
166
+
167
+ ```ts
168
+ import { observeMutation$, observePerformance$ } from '@tstdl/base/dom';
169
+
170
+ // Mutation Observer
171
+ const list = document.querySelector('ul')!;
172
+ observeMutation$(list, { childList: true }).subscribe((records) => {
173
+ console.log('List changed:', records);
174
+ });
175
+
176
+ // Performance Observer
177
+ observePerformance$({ entryTypes: ['resource'] }).subscribe((list) => {
178
+ const entries = list.getEntries();
179
+ entries.forEach((entry) => console.log(`Resource loaded: ${entry.name}`));
180
+ });
181
+ ```
182
+
183
+ ## 📚 API
184
+
185
+ ### File Utilities
186
+
187
+ | Function | Description |
188
+ | :-------------------------------------------------------- | :----------------------------------------------------------------------------------- |
189
+ | `downloadFile(content: Blob \| File, fileName?: string)` | Triggers a browser download for the provided content. |
190
+ | `openFileSelectDialog(options?: FileSelectDialogOptions)` | Opens a file selection dialog and returns a Promise resolving to `File[]` or `null`. |
191
+
192
+ ### Reactive Observers (RxJS & Signals)
193
+
194
+ | Function | Return Type | Description |
195
+ | :---------------------------------------- | :----------------------------------------------- | :-------------------------------------------------------- |
196
+ | `observeIntersection$(element, options?)` | `Observable<IntersectionObserverEntry>` | Observes element intersection changes. |
197
+ | `observeIntersection(element, options?)` | `Signal<IntersectionObserverEntry \| undefined>` | Signal version of intersection observer. |
198
+ | `observeResize$(element, options?)` | `Observable<ResizeObserverEntry>` | Observes element resize events. |
199
+ | `observeResize(element, options?)` | `Signal<ResizeObserverEntry \| undefined>` | Signal version of resize observer. |
200
+ | `observeMediaQuery$(query)` | `Observable<boolean>` | Observes media query match status. |
201
+ | `observeMediaQuery(query)` | `Signal<boolean>` | Signal version of media query observer. |
202
+ | `observeMutation$(nodes, options?)` | `Observable<MutationRecord[]>` | Observes DOM mutations. |
203
+ | `observePerformance$(inits?, options?)` | `Observable<PerformanceObserverEntryListLike>` | Observes performance entries. |
204
+ | `observeTouch$(element, options?)` | `Observable<TouchEvents>` | Observes touch events (`start`, `move`, `end`, `cancel`). |
205
+
206
+ ### Types
207
+
208
+ | Type | Description |
209
+ | :-------------------------- | :----------------------------------------------------------------------- |
210
+ | `FileSelectDialogOptions` | Options for file selection (`accept`, `multiple`, `capture`). |
211
+ | `TouchEvents` | Object containing optional `start`, `move`, `end`, `cancel` TouchEvents. |
212
+ | `ObserveMutationOptions` | Extends `MutationObserverInit` with optional triggers. |
213
+ | `ObservePerformanceOptions` | Options for performance observation triggers. |
@@ -0,0 +1,259 @@
1
+ # Enumerable
2
+
3
+ A powerful, fluent, LINQ-inspired library for querying, transforming, and manipulating synchronous and asynchronous iterables in TypeScript. It unifies array processing, generators, and streams under a single, lazy-evaluation API.
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
+ - [Asynchronous Streams](#asynchronous-streams)
12
+ - [Parallel Processing](#parallel-processing)
13
+ - [RxJS Observables](#rxjs-observables)
14
+ - [Multiplexing Streams](#multiplexing-streams)
15
+ - [Event Loop Interruption](#event-loop-interruption)
16
+ - [📚 API](#-api)
17
+
18
+ ## ✨ Features
19
+
20
+ - **Fluent Interface**: Chain methods like `.filter()`, `.map()`, and `.reduce()` for readable and expressive data pipelines.
21
+ - **Lazy Evaluation**: Operations are deferred until results are materialized, optimizing performance and memory usage.
22
+ - **Unified API**: Consistent methods for both synchronous (`Enumerable`) and asynchronous (`AsyncEnumerable`) data sources.
23
+ - **Parallel Execution**: Built-in support for concurrent processing in asynchronous streams (`parallelMap`, `parallelFilter`, `parallelTap`) to speed up I/O-bound tasks.
24
+ - **Rich Toolset**: Includes advanced operations like `group`, `distinct`, `batch`, `pairwise`, `throttle`, and `retry`.
25
+ - **Interoperability**: Seamlessly convert between Arrays, Sets, Generators, Promises, and RxJS Observables.
26
+ - **Event Loop Friendly**: Built-in methods to yield control back to the event loop during heavy processing (`interruptEvery`, `interruptPerSecond`).
27
+
28
+ ## Core Concepts
29
+
30
+ ### `Enumerable<T>`
31
+
32
+ Wraps standard synchronous iterables (Arrays, Sets, Maps, Generators). It executes operations synchronously and lazily. No iteration happens until a terminal method (like `toArray`, `first`, or `reduce`) is called.
33
+
34
+ ### `AsyncEnumerable<T>`
35
+
36
+ Wraps asynchronous iterables (`AsyncIterable`, `Promise<Iterable>`, `Observable`). It allows you to process streams of data over time. It supports **concurrency** control, allowing you to process multiple items in parallel while maintaining a simple fluent chain.
37
+
38
+ ## 🚀 Basic Usage
39
+
40
+ ### Synchronous Data Processing
41
+
42
+ ```typescript
43
+ import { Enumerable } from '@tstdl/base/enumerable';
44
+
45
+ const users = [
46
+ { id: 1, name: 'Alice', age: 30, active: true },
47
+ { id: 2, name: 'Bob', age: 25, active: false },
48
+ { id: 3, name: 'Charlie', age: 35, active: true },
49
+ ];
50
+
51
+ const names = Enumerable.from(users)
52
+ .filter((u) => u.active)
53
+ .sort((a, b) => a.age - b.age)
54
+ .map((u) => u.name)
55
+ .toArray();
56
+
57
+ console.log(names); // ['Alice', 'Charlie']
58
+ ```
59
+
60
+ ### Grouping Data
61
+
62
+ Easily group data into Maps or Arrays.
63
+
64
+ ```typescript
65
+ import { Enumerable } from '@tstdl/base/enumerable';
66
+
67
+ const numbers = [1, 2, 3, 4, 5, 6];
68
+
69
+ // Group by even/odd
70
+ const grouped = Enumerable.from(numbers).groupToMap((n) => (n % 2 === 0 ? 'even' : 'odd'));
71
+
72
+ console.log(grouped.get('even')); // [2, 4, 6]
73
+ console.log(grouped.get('odd')); // [1, 3, 5]
74
+ ```
75
+
76
+ ## 🔧 Advanced Topics
77
+
78
+ ### Asynchronous Streams
79
+
80
+ `AsyncEnumerable` works natively with async generators and APIs.
81
+
82
+ ```typescript
83
+ import { AsyncEnumerable } from '@tstdl/base/enumerable';
84
+
85
+ async function* fetchPages() {
86
+ yield [1, 2, 3];
87
+ await new Promise((r) => setTimeout(r, 100));
88
+ yield [4, 5, 6];
89
+ }
90
+
91
+ const total = await AsyncEnumerable.from(fetchPages())
92
+ .mapMany((page) => page) // Flatten arrays
93
+ .filter((n) => n % 2 === 0)
94
+ .reduce((sum, n) => sum + n, 0);
95
+
96
+ console.log(total); // 12 (2 + 4 + 6)
97
+ ```
98
+
99
+ ### Parallel Processing
100
+
101
+ Process async tasks concurrently to improve throughput. This is ideal for batch API calls or file processing.
102
+
103
+ ```typescript
104
+ import { AsyncEnumerable } from '@tstdl/base/enumerable';
105
+
106
+ const ids = [1, 2, 3, 4, 5];
107
+
108
+ const results = await AsyncEnumerable.from(ids)
109
+ .parallelMap(3, true, async (id) => {
110
+ // Simulate network request with concurrency of 3
111
+ // 'true' ensures output order matches input order
112
+ const data = await fetch(`https://api.example.com/items/${id}`);
113
+ return data.json();
114
+ })
115
+ .toArray();
116
+ ```
117
+
118
+ ### RxJS Observables
119
+
120
+ Convert RxJS Observables directly into `AsyncEnumerable` to use LINQ-style operators on streams.
121
+
122
+ ```typescript
123
+ import { AsyncEnumerable } from '@tstdl/base/enumerable';
124
+ import { interval } from 'rxjs';
125
+ import { take } from 'rxjs/operators';
126
+
127
+ const observable = interval(100).pipe(take(5));
128
+
129
+ await AsyncEnumerable.fromObservable(observable)
130
+ .map((n) => n * 2)
131
+ .forEach((n) => console.log(n));
132
+ // Output: 0, 2, 4, 6, 8
133
+ ```
134
+
135
+ ### Multiplexing Streams
136
+
137
+ Split a single async source into multiple independent consumers. This is useful when you need to process the same stream in different ways simultaneously.
138
+
139
+ ```typescript
140
+ import { AsyncEnumerable } from '@tstdl/base/enumerable';
141
+
142
+ const source = AsyncEnumerable.fromRange(1, 5);
143
+
144
+ // Create 2 independent branches from the source
145
+ const [branchA, branchB] = source.multiplex(2);
146
+
147
+ const taskA = branchA.filter((n) => n % 2 === 0).toArray();
148
+
149
+ const taskB = branchB.map((n) => n * 10).toArray();
150
+
151
+ const [evens, multiplied] = await Promise.all([taskA, taskB]);
152
+
153
+ console.log(evens); // [2, 4]
154
+ console.log(multiplied); // [10, 20, 30, 40, 50]
155
+ ```
156
+
157
+ ### Event Loop Interruption
158
+
159
+ When processing large datasets asynchronously, you may want to prevent blocking the event loop for too long. `AsyncEnumerable` provides methods to automatically yield control back to the event loop.
160
+
161
+ ```typescript
162
+ import { AsyncEnumerable } from '@tstdl/base/enumerable';
163
+
164
+ const largeDataset = AsyncEnumerable.fromRange(1, 1_000_000);
165
+
166
+ await largeDataset
167
+ .interruptEvery(1000) // Yield control every 1000 items
168
+ .forEach((item) => {
169
+ // Heavy processing
170
+ });
171
+
172
+ await largeDataset
173
+ .interruptPerSecond(60) // Ensure the event loop is yielded at least 60 times per second
174
+ .forEach((item) => {
175
+ // Heavy processing
176
+ });
177
+ ```
178
+
179
+ ## 📚 API
180
+
181
+ ### Static Creation Methods
182
+
183
+ | Class | Method | Description |
184
+ | :---------------- | :---------------------- | :----------------------------------------------------------------------------- |
185
+ | `Enumerable` | `from(source)` | Creates an `Enumerable` from an `Iterable`. |
186
+ | `Enumerable` | `fromDeferred(factory)` | Creates an `Enumerable` that calls the factory function when iteration starts. |
187
+ | `Enumerable` | `fromRange(from, to)` | Creates an `Enumerable` of numbers in a range (inclusive). |
188
+ | `AsyncEnumerable` | `from(source)` | Creates an `AsyncEnumerable` from an `AnyIterable` (sync or async). |
189
+ | `AsyncEnumerable` | `fromDeferred(factory)` | Creates an `AsyncEnumerable` from a factory returning an iterable or promise. |
190
+ | `AsyncEnumerable` | `fromObservable(obs)` | Creates an `AsyncEnumerable` from an RxJS `Observable`. |
191
+ | `AsyncEnumerable` | `fromRange(from, to)` | Creates an `AsyncEnumerable` of numbers in a range. |
192
+
193
+ ### Common Methods (Sync & Async)
194
+
195
+ These methods exist on both `Enumerable` and `AsyncEnumerable`. Async versions return `Promise` for terminal operations and `AsyncEnumerable` for transformations.
196
+
197
+ | Category | Method | Description |
198
+ | :----------------- | :------------------------ | :--------------------------------------------------------------------------------------- |
199
+ | **Filtering** | `filter(predicate)` | Filters elements based on a predicate. |
200
+ | | `filterNullOrUndefined()` | Removes `null` and `undefined` values. |
201
+ | | `distinct(selector?)` | Returns distinct elements (optionally by key). |
202
+ | | `take(count)` | Takes the first `N` elements. |
203
+ | | `skip(count)` | Skips the first `N` elements. |
204
+ | | `takeWhile(yieldLast, p)` | Takes elements while the predicate is true. |
205
+ | | `takeUntil(signal)` | Takes elements until a `CancellationSignal` is triggered. |
206
+ | | `while(predicate)` | Iterates while the predicate is true. |
207
+ | **Transformation** | `map(mapper)` | Projects each element into a new form. |
208
+ | | `mapMany(mapper)` | Projects each element to an iterable and flattens the result. |
209
+ | | `batch(size)` | Groups elements into arrays of a specific size. |
210
+ | | `pairwise()` | Emits the previous and current element as a tuple `[prev, curr]`. |
211
+ | | `materialize()` | Converts the sequence into a sequence of `Notification` objects (next, error, complete). |
212
+ | | `metadata()` | Wraps elements with metadata (index, isFirst, isLast). |
213
+ | **Set Operations** | `concat(...iterables)` | Concatenates multiple iterables. |
214
+ | | `difference(other, sel?)` | Returns elements present in source but not in other. |
215
+ | | `differenceMany(its, s?)` | Returns elements present in source but not in any of the others. |
216
+ | | `defaultIfEmpty(default)` | Returns a default value if the sequence is empty. |
217
+ | **Aggregation** | `reduce(reducer, init?)` | Aggregates the sequence into a single value. |
218
+ | | `toArray()` | Materializes the sequence into an array. |
219
+ | | `toSet()` | Materializes the sequence into a Set. |
220
+ | | `group(selector)` | Groups elements into `[Key, T[]]` pairs. |
221
+ | | `groupSingle(selector)` | Groups elements into `[Key, T]` pairs (last wins). |
222
+ | | `groupToMap(selector)` | Groups elements into a `Map<Key, T[]>`. |
223
+ | | `groupToSingleMap(sel)` | Groups elements into a `Map<Key, T>` (last wins). |
224
+ | **Inspection** | `all(predicate)` | Checks if all elements satisfy the predicate. |
225
+ | | `any(predicate)` | Checks if any element satisfies the predicate. |
226
+ | | `includes(value)` | Checks if the sequence contains a specific value. |
227
+ | | `first(predicate?)` | Returns the first element (throws if empty). |
228
+ | | `firstOrDefault(def, p?)` | Returns the first element or a default value. |
229
+ | | `last(predicate?)` | Returns the last element. |
230
+ | | `lastOrDefault(def, p?)` | Returns the last element or a default value. |
231
+ | | `single(predicate?)` | Returns the only element (throws if not exactly one). |
232
+ | | `singleOrDefault(def, p)` | Returns the single element or a default value. |
233
+ | **Sorting** | `sort(comparator?)` | Sorts the sequence lazily. |
234
+ | | `sortToArray(comparator?)`| Sorts the sequence and returns an array immediately. |
235
+ | **Side Effects** | `tap(action)` | Executes an action for each element without modifying the stream. |
236
+ | | `forEach(action)` | Iterates over the sequence (terminal operation). |
237
+ | | `drain()` | Consumes the entire sequence and discards results. |
238
+ | **Utility** | `assert(predicate)` | Verifies that each element satisfies the predicate, throws otherwise. |
239
+ | | `cast<TNew>()` | Casts the sequence to a new type (unsafe). |
240
+ | | `forceCast<TNew>()` | Force casts the sequence to a new type (unsafe). |
241
+ | | `toAsync()` | Converts the sequence to an `AsyncEnumerable`. |
242
+ | | `toIterator()` | Returns the underlying iterator. |
243
+
244
+ ### Async-Only Methods (`AsyncEnumerable`)
245
+
246
+ | Method | Description |
247
+ | :-------------------------------------------------- | :--------------------------------------------------------------------------------------- |
248
+ | `parallelMap(concurrency, keepOrder, mapper)` | Maps elements concurrently. |
249
+ | `parallelFilter(concurrency, keepOrder, predicate)` | Filters elements concurrently. |
250
+ | `parallelTap(concurrency, keepOrder, action)` | Taps elements concurrently. |
251
+ | `parallelForEach(concurrency, action)` | Iterates elements concurrently. |
252
+ | `parallelGroup(concurrency, selector)` | Groups elements concurrently (returns `Promise<Map>`). |
253
+ | `interruptEvery(value)` | Yields control to the event loop every `N` elements. |
254
+ | `interruptPerSecond(value)` | Yields control to the event loop `N` times per second. |
255
+ | `throttle(delayOrFunction)` | Limits the rate of emission. |
256
+ | `retry(throwOnFalse, predicate)` | Retries the source sequence on error based on a predicate. |
257
+ | `multiplex(count, buffer?)` | Splits the stream into multiple independent streams. |
258
+ | `buffer(size)` | Buffers elements and emits them as soon as they are available (flow control). |
259
+ | `toSync()` | Resolves the entire async sequence into a synchronous `Enumerable` (returns `Promise`). |
@@ -0,0 +1,121 @@
1
+ # @tstdl/base/enumeration
2
+
3
+ A lightweight utility for creating and registering named, type-safe enumerations in TypeScript using plain objects. It provides runtime name introspection capabilities often missing from native TypeScript enums, making it ideal for framework development, serialization, and logging.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [🚀 Basic Usage](#-basic-usage)
10
+ - [Defining a String Enum](#defining-a-string-enum)
11
+ - [Defining a Numeric Enum](#defining-a-numeric-enum)
12
+ - [🔧 Advanced Topics](#-advanced-topics)
13
+ - [Retrieving an Enum's Name](#retrieving-an-enums-name)
14
+ - [Working with Enum Values (Utilities)](#working-with-enum-values-utilities)
15
+ - [💡 Best Practices](#-best-practices)
16
+ - [📚 API](#-api)
17
+
18
+ ## ✨ Features
19
+
20
+ - **Named Enums:** Associate a string name with an enum-like object, retrievable at runtime.
21
+ - **Type-Safe:** Leverages TypeScript's `const` generics to create precise, type-safe union types from plain objects.
22
+ - **Mixed Types:** Supports both string and numeric values within the same enumeration structure.
23
+ - **Lightweight:** A minimal implementation with zero external dependencies.
24
+ - **Memory-Efficient:** Uses a `WeakMap` to store enum names, allowing for automatic garbage collection if the enum object is no longer referenced.
25
+
26
+ ## Core Concepts
27
+
28
+ Native TypeScript `enum`s have limitations, especially for framework development. Their declared name is lost at compile time (transpiled away), and numeric enums have complex runtime behavior (reverse mappings). This makes it difficult for systems like ORMs, serializers, or validators to identify an enum type by a consistent name at runtime.
29
+
30
+ This module provides a simple and robust alternative. The `defineEnum` function allows you to use a standard JavaScript object as an enum. It registers the object with a unique string name in a global, memory-safe registry (`WeakMap`). This name can be retrieved at runtime using `getEnumName`, enabling powerful metaprogramming scenarios where you need to reference the enum's "type" by name.
31
+
32
+ ## 🚀 Basic Usage
33
+
34
+ ### Defining a String Enum
35
+
36
+ Use the `defineEnum` function to create your enumeration object and register its name. The `EnumType` utility creates a corresponding TypeScript type for the enum values.
37
+
38
+ ```typescript
39
+ import { defineEnum, type EnumType } from '@tstdl/base/enumeration';
40
+
41
+ export const OrderStatus = defineEnum('OrderStatus', {
42
+ Pending: 'pending',
43
+ Processing: 'processing',
44
+ Shipped: 'shipped',
45
+ Delivered: 'delivered',
46
+ });
47
+
48
+ export type OrderStatus = EnumType<typeof OrderStatus>;
49
+
50
+ const status: OrderStatus = OrderStatus.Shipped; // Type is "pending" | "processing" | "shipped" | "delivered"
51
+ ```
52
+
53
+ ### Defining a Numeric Enum
54
+
55
+ The utility works equally well with numeric values.
56
+
57
+ ```typescript
58
+ import { defineEnum, type EnumType } from '@tstdl/base/enumeration';
59
+
60
+ export const Priority = defineEnum('Priority', {
61
+ Low: 0,
62
+ Medium: 1,
63
+ High: 2,
64
+ Urgent: 3,
65
+ });
66
+
67
+ export type Priority = EnumType<typeof Priority>;
68
+
69
+ const taskPriority: Priority = Priority.High; // Type is 0 | 1 | 2 | 3
70
+ ```
71
+
72
+ ## 🔧 Advanced Topics
73
+
74
+ ### Retrieving an Enum's Name
75
+
76
+ You can retrieve the registered name of an enum object. This is particularly useful for logging, error messages, or when building libraries that need to serialize the type name of an enum.
77
+
78
+ ```typescript
79
+ import { defineEnum, getEnumName, tryGetEnumName } from '@tstdl/base/enumeration';
80
+
81
+ const UserRole = defineEnum('UserRole', {
82
+ Admin: 'admin',
83
+ Editor: 'editor',
84
+ });
85
+
86
+ const name = getEnumName(UserRole); // "UserRole"
87
+ const safeName = tryGetEnumName({}); // undefined
88
+ ```
89
+
90
+ ### Working with Enum Values (Utilities)
91
+
92
+ For practical operations like listing all values or finding a key by value, use the utility functions provided in `#/utils/enum.js`.
93
+
94
+ ```typescript
95
+ import { enumValues, enumKeys, enumValueName } from '@tstdl/base/utils/enum';
96
+ import { OrderStatus } from './order-status.js';
97
+
98
+ const allStatuses = enumValues(OrderStatus); // ["pending", "processing", ...]
99
+ const allKeys = enumKeys(OrderStatus); // ["Pending", "Processing", ...]
100
+ const name = enumValueName(OrderStatus, 'pending'); // "Pending"
101
+ ```
102
+
103
+ ## 💡 Best Practices
104
+
105
+ 1. **Always use `defineEnum`:** Ensure every enum-like object in the codebase is wrapped in `defineEnum` to maintain consistency and support runtime introspection.
106
+ 2. **Export both Value and Type:** Export the constant object and a type alias with the same name for a better developer experience.
107
+ ```typescript
108
+ export const MyEnum = defineEnum('MyEnum', { ... });
109
+ export type MyEnum = EnumType<typeof MyEnum>;
110
+ ```
111
+ 3. **Unique Names:** Ensure the string name passed to `defineEnum` is unique across the application, typically matching the variable name.
112
+ 4. **Avoid Native Enums:** Prefer this pattern over native TypeScript `enum` to avoid unexpected runtime behavior and missing metadata.
113
+
114
+ ## 📚 API
115
+
116
+ | Member | Signature | Description |
117
+ | ------------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
118
+ | `defineEnum()` | `function defineEnum<const T extends EnumerationObject>(name: string, enumObject: T): T` | Registers an `enumObject` with a given `name` and returns it. The `<const T>` generic ensures that the returned type is inferred as a set of literal types, providing strong type safety. |
119
+ | `getEnumName()` | `function getEnumName(enumeration: EnumerationObject): string` | Retrieves the registered name for an enum object. Throws an `Error` if the enum is not registered. |
120
+ | `tryGetEnumName()` | `function tryGetEnumName(enumeration: EnumerationObject): string \| undefined` | Safely retrieves the registered name for an enum object. Returns `undefined` if not registered. |
121
+ | `EnumType` | `type EnumType<T extends EnumerationObject> = T[keyof T]` | A utility type that creates a union type from an enum-like object's values. |