red64-cli 0.5.0 → 0.6.0

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 (62) hide show
  1. package/README.md +64 -58
  2. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  3. package/dist/components/screens/StartScreen.js +2 -2
  4. package/dist/components/screens/StartScreen.js.map +1 -1
  5. package/dist/services/AgentInvoker.js +4 -4
  6. package/dist/services/AgentInvoker.js.map +1 -1
  7. package/dist/services/ClaudeHealthCheck.d.ts +5 -0
  8. package/dist/services/ClaudeHealthCheck.d.ts.map +1 -1
  9. package/dist/services/ClaudeHealthCheck.js +43 -5
  10. package/dist/services/ClaudeHealthCheck.js.map +1 -1
  11. package/dist/services/index.d.ts +1 -1
  12. package/dist/services/index.d.ts.map +1 -1
  13. package/dist/services/index.js +1 -1
  14. package/dist/services/index.js.map +1 -1
  15. package/framework/stacks/c/code-quality.md +326 -0
  16. package/framework/stacks/c/coding-style.md +347 -0
  17. package/framework/stacks/c/conventions.md +513 -0
  18. package/framework/stacks/c/error-handling.md +350 -0
  19. package/framework/stacks/c/feedback.md +158 -0
  20. package/framework/stacks/c/memory-safety.md +408 -0
  21. package/framework/stacks/c/tech.md +122 -0
  22. package/framework/stacks/c/testing.md +472 -0
  23. package/framework/stacks/cpp/code-quality.md +282 -0
  24. package/framework/stacks/cpp/coding-style.md +363 -0
  25. package/framework/stacks/cpp/conventions.md +420 -0
  26. package/framework/stacks/cpp/error-handling.md +264 -0
  27. package/framework/stacks/cpp/feedback.md +104 -0
  28. package/framework/stacks/cpp/memory-safety.md +351 -0
  29. package/framework/stacks/cpp/tech.md +160 -0
  30. package/framework/stacks/cpp/testing.md +323 -0
  31. package/framework/stacks/java/code-quality.md +357 -0
  32. package/framework/stacks/java/coding-style.md +400 -0
  33. package/framework/stacks/java/conventions.md +437 -0
  34. package/framework/stacks/java/error-handling.md +408 -0
  35. package/framework/stacks/java/feedback.md +180 -0
  36. package/framework/stacks/java/tech.md +126 -0
  37. package/framework/stacks/java/testing.md +485 -0
  38. package/framework/stacks/javascript/async-patterns.md +216 -0
  39. package/framework/stacks/javascript/code-quality.md +182 -0
  40. package/framework/stacks/javascript/coding-style.md +293 -0
  41. package/framework/stacks/javascript/conventions.md +268 -0
  42. package/framework/stacks/javascript/error-handling.md +216 -0
  43. package/framework/stacks/javascript/feedback.md +80 -0
  44. package/framework/stacks/javascript/tech.md +114 -0
  45. package/framework/stacks/javascript/testing.md +209 -0
  46. package/framework/stacks/loco/code-quality.md +156 -0
  47. package/framework/stacks/loco/coding-style.md +247 -0
  48. package/framework/stacks/loco/error-handling.md +225 -0
  49. package/framework/stacks/loco/feedback.md +35 -0
  50. package/framework/stacks/loco/loco.md +342 -0
  51. package/framework/stacks/loco/structure.md +193 -0
  52. package/framework/stacks/loco/tech.md +129 -0
  53. package/framework/stacks/loco/testing.md +211 -0
  54. package/framework/stacks/rust/code-quality.md +370 -0
  55. package/framework/stacks/rust/coding-style.md +475 -0
  56. package/framework/stacks/rust/conventions.md +430 -0
  57. package/framework/stacks/rust/error-handling.md +399 -0
  58. package/framework/stacks/rust/feedback.md +152 -0
  59. package/framework/stacks/rust/memory-safety.md +398 -0
  60. package/framework/stacks/rust/tech.md +121 -0
  61. package/framework/stacks/rust/testing.md +528 -0
  62. package/package.json +14 -2
@@ -0,0 +1,485 @@
1
+ # Testing Patterns
2
+
3
+ Comprehensive JUnit 5 testing patterns for Java 21+ with Spring Boot, Mockito, AssertJ, and Testcontainers.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Fast feedback**: Unit tests run in milliseconds with no I/O
10
+ - **Production parity**: Integration tests use real databases via Testcontainers
11
+ - **Readable tests**: Each test tells a story with arrange-act-assert and `@DisplayName`
12
+ - **Test slices**: Use Spring test slices (`@WebMvcTest`, `@DataJpaTest`) to keep tests fast and focused
13
+
14
+ ---
15
+
16
+ ## Test Organization
17
+
18
+ ```
19
+ src/test/java/com/example/
20
+ unit/
21
+ service/
22
+ UserServiceTest.java
23
+ PaymentServiceTest.java
24
+ util/
25
+ SlugGeneratorTest.java
26
+ integration/
27
+ api/
28
+ UserControllerIntegrationTest.java
29
+ repository/
30
+ UserRepositoryIntegrationTest.java
31
+ architecture/
32
+ ArchitectureTest.java
33
+ support/
34
+ TestContainersConfig.java
35
+ TestDataFactory.java
36
+ ```
37
+
38
+ **Pattern**: Mirror `src/main/java` structure. Suffix all test classes with `Test`.
39
+
40
+ ---
41
+
42
+ ## JUnit 5 Fundamentals
43
+
44
+ ### @Test and @DisplayName
45
+
46
+ ```java
47
+ class UserServiceTest {
48
+
49
+ @Test
50
+ @DisplayName("should create user when email is unique")
51
+ void createUser_uniqueEmail_returnsUser() {
52
+ // Arrange
53
+ var request = new CreateUserRequest("alice@example.com", "Alice");
54
+ when(userRepository.findByEmail("alice@example.com")).thenReturn(Optional.empty());
55
+ when(userRepository.save(any(User.class))).thenAnswer(inv -> {
56
+ var user = inv.getArgument(0, User.class);
57
+ user.setId(1L);
58
+ return user;
59
+ });
60
+
61
+ // Act
62
+ var result = userService.createUser(request);
63
+
64
+ // Assert
65
+ assertThat(result.getEmail()).isEqualTo("alice@example.com");
66
+ assertThat(result.getId()).isNotNull();
67
+ verify(userRepository).save(any(User.class));
68
+ }
69
+
70
+ @Test
71
+ @DisplayName("should throw ConflictException when email already exists")
72
+ void createUser_duplicateEmail_throwsConflict() {
73
+ var request = new CreateUserRequest("taken@example.com", "Bob");
74
+ when(userRepository.findByEmail("taken@example.com"))
75
+ .thenReturn(Optional.of(new User("taken@example.com", "Existing")));
76
+
77
+ assertThatThrownBy(() -> userService.createUser(request))
78
+ .isInstanceOf(ConflictException.class)
79
+ .hasMessageContaining("already registered");
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### @Nested for Grouping
85
+
86
+ ```java
87
+ class PostServiceTest {
88
+
89
+ @Nested
90
+ @DisplayName("publish()")
91
+ class Publish {
92
+
93
+ @Test
94
+ @DisplayName("should publish draft post")
95
+ void draft_publishes() { ... }
96
+
97
+ @Test
98
+ @DisplayName("should throw when post is already published")
99
+ void alreadyPublished_throwsConflict() { ... }
100
+
101
+ @Test
102
+ @DisplayName("should throw when user is not the author")
103
+ void wrongAuthor_throwsForbidden() { ... }
104
+ }
105
+
106
+ @Nested
107
+ @DisplayName("delete()")
108
+ class Delete {
109
+
110
+ @Test
111
+ @DisplayName("should soft-delete post")
112
+ void validPost_softDeletes() { ... }
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### @ParameterizedTest
118
+
119
+ ```java
120
+ class EmailValidatorTest {
121
+
122
+ @ParameterizedTest(name = "\"{0}\" should be {1}")
123
+ @CsvSource({
124
+ "user@example.com, true",
125
+ "user@sub.example.com, true",
126
+ "invalid, false",
127
+ "'', false",
128
+ "@example.com, false",
129
+ })
130
+ @DisplayName("should validate email format")
131
+ void validateEmail(String email, boolean expectedValid) {
132
+ assertThat(EmailValidator.isValid(email)).isEqualTo(expectedValid);
133
+ }
134
+
135
+ @ParameterizedTest
136
+ @MethodSource("rolePermissions")
137
+ @DisplayName("should check role permissions")
138
+ void checkPermission(Role role, Permission permission, boolean expected) {
139
+ assertThat(role.hasPermission(permission)).isEqualTo(expected);
140
+ }
141
+
142
+ static Stream<Arguments> rolePermissions() {
143
+ return Stream.of(
144
+ Arguments.of(Role.ADMIN, Permission.DELETE, true),
145
+ Arguments.of(Role.USER, Permission.DELETE, false),
146
+ Arguments.of(Role.USER, Permission.READ, true)
147
+ );
148
+ }
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## AssertJ Fluent Assertions
155
+
156
+ ```java
157
+ // GOOD: AssertJ -- fluent, readable, great error messages
158
+ assertThat(user.getEmail()).isEqualTo("alice@example.com");
159
+ assertThat(users).hasSize(3)
160
+ .extracting(User::getEmail)
161
+ .containsExactly("alice@example.com", "bob@example.com", "carol@example.com");
162
+
163
+ assertThat(result).isNotNull()
164
+ .satisfies(r -> {
165
+ assertThat(r.getStatus()).isEqualTo(Status.ACTIVE);
166
+ assertThat(r.getCreatedAt()).isBeforeOrEqualTo(Instant.now());
167
+ });
168
+
169
+ assertThatThrownBy(() -> service.getUser(999L))
170
+ .isInstanceOf(NotFoundException.class)
171
+ .hasMessageContaining("User not found");
172
+
173
+ assertThat(optionalUser).isPresent()
174
+ .get()
175
+ .extracting(User::getName)
176
+ .isEqualTo("Alice");
177
+
178
+ // BAD: JUnit assertions -- less readable, worse error messages
179
+ assertEquals("alice@example.com", user.getEmail());
180
+ assertTrue(users.size() == 3);
181
+ assertNotNull(result);
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Mockito
187
+
188
+ ### Setup with @Mock and @InjectMocks
189
+
190
+ ```java
191
+ @ExtendWith(MockitoExtension.class)
192
+ class UserServiceTest {
193
+
194
+ @Mock
195
+ private UserRepository userRepository;
196
+
197
+ @Mock
198
+ private PasswordEncoder passwordEncoder;
199
+
200
+ @InjectMocks
201
+ private UserService userService;
202
+
203
+ @Test
204
+ void getUser_existing_returnsUser() {
205
+ // given
206
+ var user = new User("alice@example.com", "Alice");
207
+ when(userRepository.findById(1L)).thenReturn(Optional.of(user));
208
+
209
+ // when
210
+ var result = userService.getUser(1L);
211
+
212
+ // then
213
+ assertThat(result.getEmail()).isEqualTo("alice@example.com");
214
+ verify(userRepository).findById(1L);
215
+ verifyNoMoreInteractions(userRepository);
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### BDD Style (given/when/then)
221
+
222
+ ```java
223
+ import static org.mockito.BDDMockito.*;
224
+
225
+ @Test
226
+ void createUser_hashesPassword() {
227
+ // given
228
+ given(passwordEncoder.encode("rawPassword")).willReturn("hashedPassword");
229
+ given(userRepository.save(any())).willAnswer(inv -> inv.getArgument(0));
230
+
231
+ // when
232
+ var user = userService.createUser(new CreateUserRequest("a@b.com", "Alice", "rawPassword"));
233
+
234
+ // then
235
+ then(passwordEncoder).should().encode("rawPassword");
236
+ assertThat(user.getHashedPassword()).isEqualTo("hashedPassword");
237
+ }
238
+ ```
239
+
240
+ ### Argument Captors
241
+
242
+ ```java
243
+ @Test
244
+ void createUser_savesCorrectEntity() {
245
+ var captor = ArgumentCaptor.forClass(User.class);
246
+ given(userRepository.save(captor.capture())).willAnswer(inv -> inv.getArgument(0));
247
+
248
+ userService.createUser(new CreateUserRequest("a@b.com", "Alice", "pw"));
249
+
250
+ var saved = captor.getValue();
251
+ assertThat(saved.getEmail()).isEqualTo("a@b.com");
252
+ assertThat(saved.getName()).isEqualTo("Alice");
253
+ }
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Testcontainers (Integration Tests)
259
+
260
+ ### Shared Container Configuration
261
+
262
+ ```java
263
+ // src/test/java/com/example/support/TestContainersConfig.java
264
+ @TestConfiguration(proxyBeanMethods = false)
265
+ public class TestContainersConfig {
266
+
267
+ @Bean
268
+ @ServiceConnection
269
+ PostgreSQLContainer<?> postgresContainer() {
270
+ return new PostgreSQLContainer<>("postgres:16-alpine")
271
+ .withDatabaseName("testdb")
272
+ .withUsername("test")
273
+ .withPassword("test");
274
+ }
275
+
276
+ @Bean
277
+ @ServiceConnection
278
+ GenericContainer<?> redisContainer() {
279
+ return new GenericContainer<>("redis:7-alpine")
280
+ .withExposedPorts(6379);
281
+ }
282
+ }
283
+ ```
284
+
285
+ ### Integration Test Base
286
+
287
+ ```java
288
+ @SpringBootTest
289
+ @Import(TestContainersConfig.class)
290
+ @Transactional // rolls back after each test
291
+ abstract class IntegrationTestBase {
292
+ }
293
+ ```
294
+
295
+ ### Repository Integration Test
296
+
297
+ ```java
298
+ class UserRepositoryIntegrationTest extends IntegrationTestBase {
299
+
300
+ @Autowired
301
+ private UserRepository userRepository;
302
+
303
+ @Test
304
+ @DisplayName("should save and find user by email")
305
+ void saveAndFindByEmail() {
306
+ var user = new User("test@example.com", "Test User");
307
+ userRepository.save(user);
308
+
309
+ var found = userRepository.findByEmail("test@example.com");
310
+
311
+ assertThat(found).isPresent()
312
+ .get()
313
+ .satisfies(u -> {
314
+ assertThat(u.getId()).isNotNull();
315
+ assertThat(u.getEmail()).isEqualTo("test@example.com");
316
+ });
317
+ }
318
+ }
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Spring Test Slices
324
+
325
+ ### @WebMvcTest (Controller Layer)
326
+
327
+ ```java
328
+ @WebMvcTest(UserController.class)
329
+ class UserControllerTest {
330
+
331
+ @Autowired
332
+ private MockMvc mockMvc;
333
+
334
+ @MockitoBean
335
+ private UserService userService;
336
+
337
+ @Test
338
+ @DisplayName("GET /api/v1/users/{id} returns user")
339
+ void getUser_existing_returns200() throws Exception {
340
+ var user = new UserResponse(1L, "alice@example.com", "Alice", Instant.now());
341
+ when(userService.getUser(1L)).thenReturn(user);
342
+
343
+ mockMvc.perform(get("/api/v1/users/1"))
344
+ .andExpect(status().isOk())
345
+ .andExpect(jsonPath("$.email").value("alice@example.com"))
346
+ .andExpect(jsonPath("$.name").value("Alice"));
347
+ }
348
+
349
+ @Test
350
+ @DisplayName("GET /api/v1/users/{id} returns 404 when not found")
351
+ void getUser_notFound_returns404() throws Exception {
352
+ when(userService.getUser(999L)).thenThrow(new NotFoundException("User", 999L));
353
+
354
+ mockMvc.perform(get("/api/v1/users/999"))
355
+ .andExpect(status().isNotFound())
356
+ .andExpect(jsonPath("$.title").value("NOT_FOUND"));
357
+ }
358
+
359
+ @Test
360
+ @DisplayName("POST /api/v1/users validates request body")
361
+ void createUser_invalidBody_returns422() throws Exception {
362
+ mockMvc.perform(post("/api/v1/users")
363
+ .contentType(MediaType.APPLICATION_JSON)
364
+ .content("""
365
+ {"email": "", "name": ""}
366
+ """))
367
+ .andExpect(status().isUnprocessableEntity());
368
+ }
369
+ }
370
+ ```
371
+
372
+ ### @DataJpaTest (Repository Layer)
373
+
374
+ ```java
375
+ @DataJpaTest
376
+ @Import(TestContainersConfig.class)
377
+ class UserRepositoryTest {
378
+
379
+ @Autowired
380
+ private UserRepository userRepository;
381
+
382
+ @Autowired
383
+ private TestEntityManager entityManager;
384
+
385
+ @Test
386
+ @DisplayName("should find active users by role")
387
+ void findActiveByRole() {
388
+ entityManager.persist(new User("admin@test.com", "Admin", Role.ADMIN, true));
389
+ entityManager.persist(new User("user@test.com", "User", Role.USER, true));
390
+ entityManager.persist(new User("inactive@test.com", "Inactive", Role.ADMIN, false));
391
+ entityManager.flush();
392
+
393
+ var admins = userRepository.findByRoleAndActiveTrue(Role.ADMIN);
394
+
395
+ assertThat(admins).hasSize(1)
396
+ .extracting(User::getEmail)
397
+ .containsExactly("admin@test.com");
398
+ }
399
+ }
400
+ ```
401
+
402
+ ---
403
+
404
+ ## JaCoCo Coverage
405
+
406
+ ### Gradle Configuration
407
+
408
+ ```kotlin
409
+ // build.gradle.kts
410
+ plugins {
411
+ id("jacoco")
412
+ }
413
+
414
+ jacoco {
415
+ toolVersion = "0.8.12"
416
+ }
417
+
418
+ tasks.jacocoTestReport {
419
+ dependsOn(tasks.test)
420
+ reports {
421
+ xml.required.set(true) // for SonarQube
422
+ html.required.set(true) // for humans
423
+ }
424
+ }
425
+
426
+ tasks.jacocoTestCoverageVerification {
427
+ violationRules {
428
+ rule {
429
+ limit {
430
+ minimum = "0.80".toBigDecimal()
431
+ }
432
+ }
433
+ rule {
434
+ element = "CLASS"
435
+ excludes = listOf(
436
+ "*.dto.*",
437
+ "*.config.*",
438
+ "*.Application"
439
+ )
440
+ limit {
441
+ minimum = "0.80".toBigDecimal()
442
+ }
443
+ }
444
+ }
445
+ }
446
+
447
+ tasks.check {
448
+ dependsOn(tasks.jacocoTestCoverageVerification)
449
+ }
450
+ ```
451
+
452
+ ```bash
453
+ ./gradlew test jacocoTestReport # Generate coverage report
454
+ ./gradlew jacocoTestCoverageVerification # Enforce thresholds
455
+ # Report: build/reports/jacoco/test/html/index.html
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Test Naming Convention
461
+
462
+ | Pattern | Example |
463
+ |---|---|
464
+ | `methodUnderTest_scenario_expectedBehavior` | `createUser_duplicateEmail_throwsConflict` |
465
+ | `@DisplayName` for human-readable output | `"should throw ConflictException when email exists"` |
466
+ | `@Nested` class name = method being tested | `class Publish { ... }` |
467
+
468
+ ---
469
+
470
+ ## Anti-Patterns
471
+
472
+ | Anti-Pattern | Problem | Correct Approach |
473
+ |---|---|---|
474
+ | No `@DisplayName` | Test output is unreadable | Always add descriptive display names |
475
+ | JUnit `assertEquals` over AssertJ | Worse error messages, less fluent | Use `assertThat()` from AssertJ |
476
+ | H2 for integration tests | Behavior differs from PostgreSQL | Testcontainers with real PostgreSQL |
477
+ | `@SpringBootTest` for unit tests | Slow startup, tests entire context | `@ExtendWith(MockitoExtension.class)` |
478
+ | Testing implementation, not behavior | Fragile tests that break on refactor | Test public API and outcomes |
479
+ | No test slices | Full context loaded for every test | `@WebMvcTest`, `@DataJpaTest` |
480
+ | Shared mutable state between tests | Order-dependent, flaky tests | `@Transactional` rollback, fresh fixtures |
481
+ | `@Autowired` fields in unit tests | Requires Spring context | `@Mock` + `@InjectMocks` |
482
+
483
+ ---
484
+
485
+ _Tests document behavior. Each test should read as a specification: given this state, when this action, then this outcome._
@@ -0,0 +1,216 @@
1
+ # Async Patterns
2
+
3
+ Modern asynchronous programming patterns for Node.js applications.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **async/await everywhere**: Promises are the foundation; async/await is the syntax
10
+ - **Concurrent by default**: Run independent operations in parallel, not sequentially
11
+ - **Cancellable operations**: Use AbortController for timeouts and user cancellation
12
+ - **Backpressure-aware**: Use streams for large data; do not load everything into memory
13
+
14
+ ---
15
+
16
+ ## Promise Combinators
17
+
18
+ ### Promise.all -- All Must Succeed
19
+
20
+ ```javascript
21
+ // GOOD: Run independent operations concurrently
22
+ async function getUserDashboard(userId) {
23
+ const [user, posts, notifications] = await Promise.all([
24
+ userService.getUser(userId),
25
+ postService.getUserPosts(userId),
26
+ notificationService.getUnread(userId),
27
+ ]);
28
+ return { user, posts, notifications };
29
+ }
30
+
31
+ // BAD: Sequential when operations are independent (2-3x slower)
32
+ async function getUserDashboard(userId) {
33
+ const user = await userService.getUser(userId);
34
+ const posts = await postService.getUserPosts(userId);
35
+ const notifications = await notificationService.getUnread(userId);
36
+ return { user, posts, notifications };
37
+ }
38
+ ```
39
+
40
+ ### Promise.allSettled -- Collect All Results
41
+
42
+ ```javascript
43
+ // When partial failures are acceptable
44
+ async function sendNotifications(userIds) {
45
+ const results = await Promise.allSettled(
46
+ userIds.map(id => notificationService.send(id)),
47
+ );
48
+ const succeeded = results.filter(r => r.status === 'fulfilled');
49
+ const failed = results.filter(r => r.status === 'rejected');
50
+ logger.info({ sent: succeeded.length, failed: failed.length }, 'notifications_sent');
51
+ }
52
+ ```
53
+
54
+ ### When to Use Each
55
+
56
+ | Combinator | Use Case | Failure Behavior |
57
+ |-----------|----------|-----------------|
58
+ | `Promise.all` | All tasks must succeed | Rejects on first failure |
59
+ | `Promise.allSettled` | Collect all outcomes (partial failure OK) | Never rejects |
60
+ | `Promise.race` | First to settle (timeouts) | Settles with first result |
61
+ | `Promise.any` | First to succeed (fallbacks) | Rejects only if all fail |
62
+
63
+ ---
64
+
65
+ ## AbortController
66
+
67
+ ### AbortSignal.timeout() -- Built-in Timeout
68
+
69
+ ```javascript
70
+ // Node.js 22+: Simpler timeout pattern
71
+ async function fetchUser(userId) {
72
+ const response = await fetch(`/api/users/${userId}`, {
73
+ signal: AbortSignal.timeout(5000),
74
+ });
75
+ return response.json();
76
+ }
77
+ ```
78
+
79
+ ### AbortSignal.any() -- Combining Signals
80
+
81
+ ```javascript
82
+ async function fetchData(url, userSignal) {
83
+ const response = await fetch(url, {
84
+ signal: AbortSignal.any([userSignal, AbortSignal.timeout(10000)]),
85
+ });
86
+ return response.json();
87
+ }
88
+ ```
89
+
90
+ ### Making Custom APIs Abortable
91
+
92
+ ```javascript
93
+ async function processLargeDataset(items, { signal } = {}) {
94
+ const results = [];
95
+ for (const item of items) {
96
+ signal?.throwIfAborted();
97
+ const result = await processItem(item);
98
+ results.push(result);
99
+ }
100
+ return results;
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Stream Patterns
107
+
108
+ ### Readable Streams with async iteration
109
+
110
+ ```javascript
111
+ import { createReadStream } from 'node:fs';
112
+ import { createInterface } from 'node:readline';
113
+
114
+ // GOOD: Process large files line by line (constant memory)
115
+ async function processLogFile(filePath) {
116
+ const fileStream = createReadStream(filePath);
117
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
118
+ let lineCount = 0;
119
+ for await (const line of rl) {
120
+ if (line.includes('ERROR')) lineCount++;
121
+ }
122
+ return lineCount;
123
+ }
124
+
125
+ // BAD: Loading entire file into memory
126
+ async function processLogFile(filePath) {
127
+ const content = await readFile(filePath, 'utf-8'); // May OOM on large files
128
+ return content.split('\n').filter(line => line.includes('ERROR')).length;
129
+ }
130
+ ```
131
+
132
+ ### Transform Streams (pipeline)
133
+
134
+ ```javascript
135
+ import { pipeline } from 'node:stream/promises';
136
+ import { createReadStream, createWriteStream } from 'node:fs';
137
+ import { createGzip } from 'node:zlib';
138
+
139
+ async function compressFile(inputPath, outputPath) {
140
+ await pipeline(
141
+ createReadStream(inputPath),
142
+ createGzip(),
143
+ createWriteStream(outputPath),
144
+ );
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Worker Threads
151
+
152
+ ### CPU-Intensive Tasks
153
+
154
+ ```javascript
155
+ import { Worker } from 'node:worker_threads';
156
+
157
+ function hashPassword(password, salt) {
158
+ return new Promise((resolve, reject) => {
159
+ const worker = new Worker('./src/workers/hash-worker.js', {
160
+ workerData: { password, salt },
161
+ });
162
+ worker.on('message', resolve);
163
+ worker.on('error', reject);
164
+ });
165
+ }
166
+ ```
167
+
168
+ ### When to Use Worker Threads
169
+
170
+ | Task | Use Worker? | Reason |
171
+ |------|------------|--------|
172
+ | JSON parsing (small) | No | Fast enough on main thread |
173
+ | Image processing | Yes | CPU-bound, blocks event loop |
174
+ | Cryptographic hashing | Yes | CPU-intensive |
175
+ | Database queries | No | Already async I/O |
176
+
177
+ ---
178
+
179
+ ## Concurrency Control
180
+
181
+ ```javascript
182
+ // Simple concurrency limiter (or use p-limit package)
183
+ async function mapWithConcurrency(items, fn, concurrency = 5) {
184
+ const results = [];
185
+ const executing = new Set();
186
+ for (const item of items) {
187
+ const promise = fn(item).then(result => {
188
+ executing.delete(promise);
189
+ return result;
190
+ });
191
+ executing.add(promise);
192
+ results.push(promise);
193
+ if (executing.size >= concurrency) {
194
+ await Promise.race(executing);
195
+ }
196
+ }
197
+ return Promise.all(results);
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Anti-Patterns
204
+
205
+ | Anti-Pattern | Problem | Correct Approach |
206
+ |---|---|---|
207
+ | Unhandled promise rejection | Crashes process | Always `catch` or use process handler |
208
+ | `await` inside `forEach` | Does not wait, runs all at once | Use `for...of` or `Promise.all` with `.map()` |
209
+ | `new Promise(async ...)` | Unnecessary wrapper | Return the async call directly |
210
+ | Mixing callbacks and promises | Inconsistent error handling | Convert callbacks with `util.promisify()` |
211
+ | Sequential independent awaits | 2-3x slower than necessary | Use `Promise.all` for independent operations |
212
+ | Unbounded parallelism | Overwhelms external services | Use concurrency limiter (p-limit) |
213
+
214
+ ---
215
+
216
+ _Async code should be simple to read and reason about. Use async/await consistently, run independent work concurrently, and always handle errors._