@tstdl/base 0.93.139 → 0.93.141

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 (218) 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.d.ts +1 -1
  28. package/application/application.js +3 -3
  29. package/application/providers.d.ts +20 -2
  30. package/application/providers.js +34 -7
  31. package/audit/README.md +267 -0
  32. package/audit/module.d.ts +5 -0
  33. package/audit/module.js +9 -1
  34. package/authentication/README.md +288 -0
  35. package/authentication/client/authentication.service.d.ts +12 -11
  36. package/authentication/client/authentication.service.js +21 -21
  37. package/authentication/client/http-client.middleware.js +2 -2
  38. package/authentication/server/module.d.ts +5 -0
  39. package/authentication/server/module.js +9 -1
  40. package/authentication/tests/authentication.api-controller.test.js +1 -1
  41. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  42. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  43. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  44. package/authentication/tests/authentication.client-service.test.js +1 -1
  45. package/browser/README.md +401 -0
  46. package/cancellation/README.md +156 -0
  47. package/cancellation/tests/coverage.test.d.ts +1 -0
  48. package/cancellation/tests/coverage.test.js +49 -0
  49. package/cancellation/tests/leak.test.js +24 -29
  50. package/cancellation/tests/token.test.d.ts +1 -0
  51. package/cancellation/tests/token.test.js +136 -0
  52. package/cancellation/token.d.ts +53 -177
  53. package/cancellation/token.js +132 -208
  54. package/circuit-breaker/postgres/module.d.ts +1 -0
  55. package/circuit-breaker/postgres/module.js +5 -1
  56. package/context/README.md +174 -0
  57. package/cookie/README.md +161 -0
  58. package/css/README.md +157 -0
  59. package/data-structures/README.md +320 -0
  60. package/decorators/README.md +140 -0
  61. package/distributed-loop/README.md +231 -0
  62. package/distributed-loop/distributed-loop.js +1 -1
  63. package/document-management/README.md +403 -0
  64. package/document-management/server/configure.js +5 -1
  65. package/document-management/server/module.d.ts +1 -1
  66. package/document-management/server/module.js +1 -1
  67. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  68. package/document-management/server/services/document-management.service.js +9 -7
  69. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  70. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  71. package/document-management/tests/document-management-core.test.js +2 -7
  72. package/document-management/tests/document-management.api.test.js +6 -7
  73. package/document-management/tests/document-statistics.service.test.js +11 -12
  74. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  75. package/document-management/tests/document.service.test.js +3 -3
  76. package/document-management/tests/enum-helpers.test.js +2 -3
  77. package/dom/README.md +213 -0
  78. package/enumerable/README.md +259 -0
  79. package/enumeration/README.md +121 -0
  80. package/errors/README.md +267 -0
  81. package/examples/document-management/main.d.ts +1 -0
  82. package/examples/document-management/main.js +14 -11
  83. package/file/README.md +191 -0
  84. package/formats/README.md +210 -0
  85. package/function/README.md +144 -0
  86. package/http/README.md +318 -0
  87. package/http/client/adapters/undici.adapter.js +1 -1
  88. package/http/client/http-client-request.d.ts +6 -5
  89. package/http/client/http-client-request.js +8 -9
  90. package/http/server/node/node-http-server.js +1 -2
  91. package/image-service/README.md +137 -0
  92. package/injector/README.md +491 -0
  93. package/intl/README.md +113 -0
  94. package/json-path/README.md +182 -0
  95. package/jsx/README.md +154 -0
  96. package/key-value-store/README.md +191 -0
  97. package/key-value-store/postgres/module.d.ts +1 -0
  98. package/key-value-store/postgres/module.js +5 -1
  99. package/lock/README.md +249 -0
  100. package/lock/postgres/module.d.ts +1 -0
  101. package/lock/postgres/module.js +5 -1
  102. package/lock/web/web-lock.js +119 -47
  103. package/logger/README.md +287 -0
  104. package/mail/README.md +256 -0
  105. package/mail/module.d.ts +5 -1
  106. package/mail/module.js +11 -6
  107. package/memory/README.md +144 -0
  108. package/message-bus/README.md +244 -0
  109. package/message-bus/message-bus-base.js +1 -1
  110. package/module/README.md +182 -0
  111. package/module/module.d.ts +1 -1
  112. package/module/module.js +77 -17
  113. package/module/modules/web-server.module.js +3 -4
  114. package/notification/server/module.d.ts +1 -0
  115. package/notification/server/module.js +5 -1
  116. package/notification/tests/notification-flow.test.js +2 -2
  117. package/notification/tests/notification-type.service.test.js +24 -15
  118. package/object-storage/README.md +300 -0
  119. package/openid-connect/README.md +274 -0
  120. package/orm/README.md +423 -0
  121. package/orm/decorators.d.ts +5 -1
  122. package/orm/decorators.js +1 -1
  123. package/orm/server/drizzle/schema-converter.js +17 -30
  124. package/orm/server/encryption.d.ts +0 -1
  125. package/orm/server/encryption.js +1 -4
  126. package/orm/server/index.d.ts +1 -6
  127. package/orm/server/index.js +1 -6
  128. package/orm/server/migration.d.ts +19 -0
  129. package/orm/server/migration.js +72 -0
  130. package/orm/server/repository.d.ts +1 -1
  131. package/orm/server/transaction.d.ts +5 -10
  132. package/orm/server/transaction.js +22 -26
  133. package/orm/server/transactional.js +3 -3
  134. package/orm/tests/database-migration.test.d.ts +1 -0
  135. package/orm/tests/database-migration.test.js +82 -0
  136. package/orm/tests/encryption.test.js +3 -4
  137. package/orm/utils.d.ts +17 -2
  138. package/orm/utils.js +49 -1
  139. package/package.json +9 -6
  140. package/password/README.md +164 -0
  141. package/pdf/README.md +246 -0
  142. package/polyfills.js +1 -0
  143. package/pool/README.md +198 -0
  144. package/process/README.md +237 -0
  145. package/promise/README.md +252 -0
  146. package/promise/cancelable-promise.js +1 -1
  147. package/random/README.md +193 -0
  148. package/rate-limit/postgres/module.d.ts +1 -0
  149. package/rate-limit/postgres/module.js +5 -1
  150. package/reflection/README.md +305 -0
  151. package/reflection/decorator-data.js +11 -12
  152. package/rpc/README.md +386 -0
  153. package/rxjs-utils/README.md +262 -0
  154. package/schema/README.md +342 -0
  155. package/serializer/README.md +342 -0
  156. package/signals/implementation/README.md +134 -0
  157. package/sse/README.md +278 -0
  158. package/task-queue/README.md +293 -0
  159. package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
  160. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
  161. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  162. package/task-queue/postgres/module.d.ts +1 -0
  163. package/task-queue/postgres/module.js +5 -1
  164. package/task-queue/postgres/schemas.d.ts +9 -6
  165. package/task-queue/postgres/schemas.js +4 -3
  166. package/task-queue/postgres/task-queue.d.ts +4 -13
  167. package/task-queue/postgres/task-queue.js +462 -355
  168. package/task-queue/postgres/task.model.d.ts +12 -5
  169. package/task-queue/postgres/task.model.js +51 -25
  170. package/task-queue/task-context.d.ts +2 -2
  171. package/task-queue/task-context.js +8 -8
  172. package/task-queue/task-queue.d.ts +53 -19
  173. package/task-queue/task-queue.js +121 -55
  174. package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
  175. package/task-queue/tests/cascading-cancellations.test.js +38 -0
  176. package/task-queue/tests/complex.test.js +45 -229
  177. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  178. package/task-queue/tests/coverage-branch.test.js +407 -0
  179. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  180. package/task-queue/tests/coverage-enhancement.test.js +144 -0
  181. package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
  182. package/task-queue/tests/dag-dependencies.test.js +41 -0
  183. package/task-queue/tests/dependencies.test.js +28 -26
  184. package/task-queue/tests/extensive-dependencies.test.js +64 -139
  185. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  186. package/task-queue/tests/fan-out-spawning.test.js +53 -0
  187. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  188. package/task-queue/tests/idempotent-replacement.test.js +61 -0
  189. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  190. package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
  191. package/task-queue/tests/queue.test.js +128 -8
  192. package/task-queue/tests/worker.test.js +39 -16
  193. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  194. package/task-queue/tests/zombie-parent.test.js +45 -0
  195. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  196. package/task-queue/tests/zombie-recovery.test.js +51 -0
  197. package/templates/README.md +287 -0
  198. package/test5.js +5 -5
  199. package/testing/README.md +157 -0
  200. package/testing/integration-setup.d.ts +4 -4
  201. package/testing/integration-setup.js +54 -29
  202. package/text/README.md +346 -0
  203. package/text/localization.service.js +2 -2
  204. package/threading/README.md +238 -0
  205. package/types/README.md +311 -0
  206. package/utils/README.md +322 -0
  207. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  208. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  209. package/utils/async-iterable-helpers/take-until.js +4 -4
  210. package/utils/backoff.js +89 -30
  211. package/utils/file-reader.js +1 -2
  212. package/utils/retry-with-backoff.js +1 -1
  213. package/utils/timer.d.ts +1 -1
  214. package/utils/timer.js +5 -7
  215. package/utils/timing.d.ts +1 -1
  216. package/utils/timing.js +2 -4
  217. package/utils/z-base32.d.ts +1 -0
  218. package/utils/z-base32.js +1 -0
@@ -9,7 +9,6 @@ import { DocumentValidationExecution, DocumentValidationExecutionState, Document
9
9
  import { DocumentWorkflow, DocumentWorkflowState, DocumentWorkflowStep } from '../models/document-workflow.model.js';
10
10
  import { DocumentApproval } from '../models/document.model.js';
11
11
  import { configureDocumentManagement } from '../server/configure.js';
12
- import { migrateDocumentManagementSchema } from '../server/module.js';
13
12
  import { DocumentCategoryTypeService } from '../server/services/document-category-type.service.js';
14
13
  import { DocumentCollectionService } from '../server/services/document-collection.service.js';
15
14
  import { DocumentManagementAiService } from '../server/services/document-management-ai.service.js';
@@ -29,7 +28,7 @@ describe('DocumentStatisticsService', () => {
29
28
  const tenantId = crypto.randomUUID();
30
29
  beforeAll(async () => {
31
30
  ({ injector, database } = await setupIntegrationTest({
32
- modules: { taskQueue: true },
31
+ modules: { taskQueue: true, documentManagement: true },
33
32
  orm: { schema },
34
33
  }));
35
34
  const mockObjectStorage = {
@@ -56,8 +55,8 @@ describe('DocumentStatisticsService', () => {
56
55
  fileObjectStorageModule: 'documents',
57
56
  fileUploadObjectStorageModule: 'document-uploads',
58
57
  filePreviewObjectStorageModule: 'document-previews',
58
+ injector,
59
59
  });
60
- await runInInjectionContext(injector, migrateDocumentManagementSchema);
61
60
  documentService = await injector.resolveAsync(DocumentService);
62
61
  statsService = await injector.resolveAsync(DocumentStatisticsService);
63
62
  collectionService = await injector.resolveAsync(DocumentCollectionService);
@@ -288,7 +287,7 @@ describe('DocumentStatisticsService', () => {
288
287
  const stats = await statsService.getStatistics(tenantId, {
289
288
  includeTotalCount: true,
290
289
  collectionIds: [collection1.id],
291
- categoryIds: [category1.id]
290
+ categoryIds: [category1.id],
292
291
  });
293
292
  expect(stats.totalCount).toBe(1); // Only Doc 1 matches both
294
293
  });
@@ -310,7 +309,7 @@ describe('DocumentStatisticsService', () => {
310
309
  includeMimeTypeDistribution: true,
311
310
  includeStorageUsage: true,
312
311
  includeDataQuality: true,
313
- collectionIds: [collection.id]
312
+ collectionIds: [collection.id],
314
313
  });
315
314
  expect(stats.totalCount).toBe(1);
316
315
  expect(stats.typeBreakdown?.[type.id]).toBe(1);
@@ -328,7 +327,7 @@ describe('DocumentStatisticsService', () => {
328
327
  const stats = await statsService.getStatistics(tenantId, {
329
328
  includeTotalCount: true,
330
329
  includeMimeTypeDistribution: true,
331
- collectionIds: [collection.id]
330
+ collectionIds: [collection.id],
332
331
  });
333
332
  expect(stats.totalCount).toBe(1);
334
333
  });
@@ -346,7 +345,7 @@ describe('DocumentStatisticsService', () => {
346
345
  const stats = await statsService.getStatistics(tenantId, {
347
346
  includeMimeTypeDistribution: true,
348
347
  collectionIds: [collection.id],
349
- categoryIds: [category.id]
348
+ categoryIds: [category.id],
350
349
  });
351
350
  expect(Object.keys(stats.mimeTypeDistribution ?? {}).length).toBeGreaterThan(0);
352
351
  });
@@ -366,7 +365,7 @@ describe('DocumentStatisticsService', () => {
366
365
  const stats = await statsService.getStatistics(tenantId, {
367
366
  includeTagUsage: true,
368
367
  collectionIds: [collection.id],
369
- categoryIds: [category.id]
368
+ categoryIds: [category.id],
370
369
  });
371
370
  expect(stats.tagUsage?.[tag.id]).toBe(1);
372
371
  });
@@ -410,7 +409,7 @@ describe('DocumentStatisticsService', () => {
410
409
  });
411
410
  const stats = await statsService.getStatistics(tenantId, {
412
411
  includeValidationFailures: true,
413
- collectionIds: [collection.id]
412
+ collectionIds: [collection.id],
414
413
  });
415
414
  expect(stats.validationFailures).toBe(1);
416
415
  });
@@ -459,7 +458,7 @@ describe('DocumentStatisticsService', () => {
459
458
  const stats = await statsService.getStatistics(tenantId, {
460
459
  includeValidationFailures: true,
461
460
  collectionIds: [collection.id],
462
- categoryIds: [category.id]
461
+ categoryIds: [category.id],
463
462
  });
464
463
  expect(stats.validationFailures).toBe(1);
465
464
  });
@@ -476,7 +475,7 @@ describe('DocumentStatisticsService', () => {
476
475
  }, new Uint8Array([1]));
477
476
  const stats = await statsService.getStatistics(tenantId, {
478
477
  includeTypeBreakdown: true,
479
- collectionIds: [collection.id]
478
+ collectionIds: [collection.id],
480
479
  });
481
480
  expect(stats.typeBreakdown?.[type.id]).toBe(1);
482
481
  });
@@ -488,7 +487,7 @@ describe('DocumentStatisticsService', () => {
488
487
  includeStorageUsage: true,
489
488
  includeTotalPages: true,
490
489
  includeValidationFailures: true,
491
- collectionIds: []
490
+ collectionIds: [],
492
491
  });
493
492
  expect(stats.totalCount).toBe(0);
494
493
  expect(stats.storageUsage).toBe(0);
@@ -32,7 +32,6 @@ describe('AiValidationExecutor Overrides', () => {
32
32
  };
33
33
  beforeAll(async () => {
34
34
  ({ injector } = await setupIntegrationTest({
35
- orm: { schema: 'document_management' },
36
35
  modules: { messageBus: true, signals: true, objectStorage: true },
37
36
  }));
38
37
  runInInjectionContext(injector, () => {
@@ -6,7 +6,7 @@ import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
6
6
  import { DocumentWorkflowState, DocumentWorkflowStep } from '../models/document-workflow.model.js';
7
7
  import { DocumentApproval } from '../models/document.model.js';
8
8
  import { configureDocumentManagement } from '../server/configure.js';
9
- import { DocumentManagementConfiguration, migrateDocumentManagementSchema } from '../server/module.js';
9
+ import { DocumentManagementConfiguration } from '../server/module.js';
10
10
  import { DocumentCollectionService } from '../server/services/document-collection.service.js';
11
11
  import { DocumentManagementAiService } from '../server/services/document-management-ai.service.js';
12
12
  import { DocumentWorkflowService } from '../server/services/document-workflow.service.js';
@@ -22,7 +22,7 @@ describe('DocumentService', () => {
22
22
  const tenantId = crypto.randomUUID();
23
23
  beforeAll(async () => {
24
24
  ({ injector, database } = await setupIntegrationTest({
25
- modules: { taskQueue: true },
25
+ modules: { taskQueue: true, documentManagement: true },
26
26
  orm: { schema },
27
27
  }));
28
28
  const mockObjectStorage = {
@@ -49,8 +49,8 @@ describe('DocumentService', () => {
49
49
  fileObjectStorageModule: 'documents',
50
50
  fileUploadObjectStorageModule: 'document-uploads',
51
51
  filePreviewObjectStorageModule: 'document-previews',
52
+ injector,
52
53
  });
53
- await runInInjectionContext(injector, migrateDocumentManagementSchema);
54
54
  documentService = await injector.resolveAsync(DocumentService);
55
55
  workflowService = await injector.resolveAsync(DocumentWorkflowService);
56
56
  collectionService = await injector.resolveAsync(DocumentCollectionService);
@@ -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`). |