@coralai/sps-cli 0.41.2 → 0.43.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 (168) hide show
  1. package/README.md +34 -3
  2. package/dist/commands/cardAdd.d.ts +1 -1
  3. package/dist/commands/cardAdd.d.ts.map +1 -1
  4. package/dist/commands/cardAdd.js +16 -6
  5. package/dist/commands/cardAdd.js.map +1 -1
  6. package/dist/commands/cardDashboard.js +1 -1
  7. package/dist/commands/cardDashboard.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +9 -0
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +3 -314
  11. package/dist/commands/doctor.js.map +1 -1
  12. package/dist/commands/hookCommand.d.ts.map +1 -1
  13. package/dist/commands/hookCommand.js +6 -7
  14. package/dist/commands/hookCommand.js.map +1 -1
  15. package/dist/commands/pmCommand.js +1 -1
  16. package/dist/commands/pmCommand.js.map +1 -1
  17. package/dist/commands/projectInit.d.ts.map +1 -1
  18. package/dist/commands/projectInit.js +60 -37
  19. package/dist/commands/projectInit.js.map +1 -1
  20. package/dist/commands/setup.d.ts.map +1 -1
  21. package/dist/commands/setup.js +3 -30
  22. package/dist/commands/setup.js.map +1 -1
  23. package/dist/commands/skillCommand.d.ts +2 -0
  24. package/dist/commands/skillCommand.d.ts.map +1 -0
  25. package/dist/commands/skillCommand.js +235 -0
  26. package/dist/commands/skillCommand.js.map +1 -0
  27. package/dist/commands/tick.js +1 -1
  28. package/dist/commands/tick.js.map +1 -1
  29. package/dist/core/checklist.d.ts +22 -0
  30. package/dist/core/checklist.d.ts.map +1 -0
  31. package/dist/core/checklist.js +38 -0
  32. package/dist/core/checklist.js.map +1 -0
  33. package/dist/core/checklist.test.d.ts +2 -0
  34. package/dist/core/checklist.test.d.ts.map +1 -0
  35. package/dist/core/checklist.test.js +74 -0
  36. package/dist/core/checklist.test.js.map +1 -0
  37. package/dist/core/config.d.ts +1 -1
  38. package/dist/core/config.d.ts.map +1 -1
  39. package/dist/core/config.js +1 -1
  40. package/dist/core/config.js.map +1 -1
  41. package/dist/core/config.test.js +7 -4
  42. package/dist/core/config.test.js.map +1 -1
  43. package/dist/core/context.d.ts +1 -1
  44. package/dist/core/context.d.ts.map +1 -1
  45. package/dist/core/skillStore.d.ts +46 -0
  46. package/dist/core/skillStore.d.ts.map +1 -0
  47. package/dist/core/skillStore.js +197 -0
  48. package/dist/core/skillStore.js.map +1 -0
  49. package/dist/core/skillStore.test.d.ts +2 -0
  50. package/dist/core/skillStore.test.d.ts.map +1 -0
  51. package/dist/core/skillStore.test.js +190 -0
  52. package/dist/core/skillStore.test.js.map +1 -0
  53. package/dist/engines/EventHandler.test.js +3 -3
  54. package/dist/engines/EventHandler.test.js.map +1 -1
  55. package/dist/engines/MonitorEngine.js +2 -2
  56. package/dist/engines/MonitorEngine.js.map +1 -1
  57. package/dist/engines/SchedulerEngine.js +1 -1
  58. package/dist/engines/SchedulerEngine.js.map +1 -1
  59. package/dist/engines/StageEngine.js +3 -3
  60. package/dist/engines/StageEngine.js.map +1 -1
  61. package/dist/engines/engine-pipeline-adapter.test.js +2 -2
  62. package/dist/engines/engine-pipeline-adapter.test.js.map +1 -1
  63. package/dist/interfaces/TaskBackend.d.ts +3 -1
  64. package/dist/interfaces/TaskBackend.d.ts.map +1 -1
  65. package/dist/main.js +19 -17
  66. package/dist/main.js.map +1 -1
  67. package/dist/models/types.d.ts +16 -1
  68. package/dist/models/types.d.ts.map +1 -1
  69. package/dist/providers/MarkdownTaskBackend.d.ts +2 -1
  70. package/dist/providers/MarkdownTaskBackend.d.ts.map +1 -1
  71. package/dist/providers/MarkdownTaskBackend.js +28 -5
  72. package/dist/providers/MarkdownTaskBackend.js.map +1 -1
  73. package/dist/providers/registry.d.ts.map +1 -1
  74. package/dist/providers/registry.js +5 -7
  75. package/dist/providers/registry.js.map +1 -1
  76. package/package.json +1 -1
  77. package/project-template/.claude/hooks/start.sh +44 -0
  78. package/project-template/.claude/settings.json +1 -1
  79. package/skills/architecture-decision-records/SKILL.md +207 -0
  80. package/skills/backend/SKILL.md +62 -0
  81. package/skills/backend/references/api-design.md +168 -0
  82. package/skills/backend/references/caching.md +181 -0
  83. package/skills/backend/references/data-access.md +173 -0
  84. package/skills/backend/references/layering.md +181 -0
  85. package/skills/backend/references/observability.md +190 -0
  86. package/skills/backend/references/resilience.md +201 -0
  87. package/skills/backend/references/security.md +186 -0
  88. package/skills/backend-architect/SKILL.md +119 -0
  89. package/skills/code-reviewer/SKILL.md +143 -0
  90. package/skills/coding-standards/SKILL.md +60 -0
  91. package/skills/coding-standards/references/clean-code.md +258 -0
  92. package/skills/coding-standards/references/code-review.md +192 -0
  93. package/skills/coding-standards/references/commits-and-prs.md +226 -0
  94. package/skills/coding-standards/references/error-strategy.md +193 -0
  95. package/skills/coding-standards/references/naming.md +185 -0
  96. package/skills/coding-standards/references/tdd.md +171 -0
  97. package/skills/database/SKILL.md +53 -0
  98. package/skills/database/references/indexing.md +190 -0
  99. package/skills/database/references/migrations.md +199 -0
  100. package/skills/database/references/nosql.md +185 -0
  101. package/skills/database/references/queries.md +295 -0
  102. package/skills/database/references/scaling.md +203 -0
  103. package/skills/database/references/schema.md +191 -0
  104. package/skills/database-optimizer/SKILL.md +168 -0
  105. package/skills/debugging-workflow/SKILL.md +244 -0
  106. package/skills/devops/SKILL.md +55 -0
  107. package/skills/devops/references/ci-cd.md +204 -0
  108. package/skills/devops/references/containers.md +272 -0
  109. package/skills/devops/references/deploy.md +201 -0
  110. package/skills/devops/references/iac.md +252 -0
  111. package/skills/devops/references/observability.md +228 -0
  112. package/skills/devops/references/secrets.md +178 -0
  113. package/skills/devops-automator/SKILL.md +164 -0
  114. package/skills/frontend/SKILL.md +52 -0
  115. package/skills/frontend/references/accessibility.md +222 -0
  116. package/skills/frontend/references/components.md +206 -0
  117. package/skills/frontend/references/performance.md +219 -0
  118. package/skills/frontend/references/routing.md +209 -0
  119. package/skills/frontend/references/state.md +190 -0
  120. package/skills/frontend/references/testing.md +216 -0
  121. package/skills/frontend-developer/SKILL.md +115 -0
  122. package/skills/git-workflow/SKILL.md +355 -0
  123. package/skills/golang/SKILL.md +49 -0
  124. package/skills/golang/references/concurrency.md +284 -0
  125. package/skills/golang/references/errors.md +241 -0
  126. package/skills/golang/references/idioms.md +285 -0
  127. package/skills/golang/references/testing.md +238 -0
  128. package/skills/java/SKILL.md +50 -0
  129. package/skills/java/references/concurrency.md +194 -0
  130. package/skills/java/references/idioms.md +283 -0
  131. package/skills/java/references/testing.md +228 -0
  132. package/skills/kotlin/SKILL.md +47 -0
  133. package/skills/kotlin/references/coroutines.md +240 -0
  134. package/skills/kotlin/references/idioms.md +268 -0
  135. package/skills/kotlin/references/testing.md +219 -0
  136. package/skills/mobile/SKILL.md +50 -0
  137. package/skills/mobile/references/architecture.md +204 -0
  138. package/skills/mobile/references/navigation.md +158 -0
  139. package/skills/mobile/references/performance.md +152 -0
  140. package/skills/mobile/references/platform.md +166 -0
  141. package/skills/mobile/references/state-and-data.md +174 -0
  142. package/skills/python/SKILL.md +51 -0
  143. package/skills/python/THIRD_PARTY.md +14 -0
  144. package/skills/python/references/async.md +218 -0
  145. package/skills/python/references/error-handling.md +254 -0
  146. package/skills/python/references/idioms.md +279 -0
  147. package/skills/python/references/packaging.md +233 -0
  148. package/skills/python/references/testing.md +269 -0
  149. package/skills/python/references/typing.md +292 -0
  150. package/skills/qa-tester/SKILL.md +186 -0
  151. package/skills/rust/SKILL.md +50 -0
  152. package/skills/rust/references/async.md +224 -0
  153. package/skills/rust/references/errors.md +240 -0
  154. package/skills/rust/references/ownership.md +263 -0
  155. package/skills/rust/references/testing.md +274 -0
  156. package/skills/rust/references/traits.md +250 -0
  157. package/skills/security-engineer/SKILL.md +157 -0
  158. package/skills/swift/SKILL.md +48 -0
  159. package/skills/swift/references/concurrency.md +280 -0
  160. package/skills/swift/references/idioms.md +334 -0
  161. package/skills/swift/references/testing.md +229 -0
  162. package/skills/typescript/SKILL.md +51 -0
  163. package/skills/typescript/references/async.md +241 -0
  164. package/skills/typescript/references/errors.md +208 -0
  165. package/skills/typescript/references/idioms.md +246 -0
  166. package/skills/typescript/references/testing.md +225 -0
  167. package/skills/typescript/references/tooling.md +208 -0
  168. package/skills/typescript/references/types.md +259 -0
@@ -0,0 +1,229 @@
1
+ # Swift — Testing
2
+
3
+ XCTest and Swift Testing (Swift 6+). For TDD, see `coding-standards/references/tdd.md`.
4
+
5
+ ## Runner choice
6
+
7
+ - **XCTest** — mature, ships with Xcode, required for UI tests.
8
+ - **Swift Testing** — Swift 6 stdlib, cleaner API, macros for parametrized tests. Use for new unit tests when your toolchain supports it.
9
+
10
+ Both can coexist in the same target.
11
+
12
+ ## XCTest
13
+
14
+ ```swift
15
+ import XCTest
16
+ @testable import MyApp
17
+
18
+ final class UserServiceTests: XCTestCase {
19
+ func test_createUser_returnsPersistedUser() async throws {
20
+ let service = UserService(repo: InMemoryUserRepo())
21
+ let user = try await service.create(email: "a@x.com")
22
+ XCTAssertEqual(user.email, "a@x.com")
23
+ XCTAssertFalse(user.id.isEmpty)
24
+ }
25
+
26
+ func test_createUser_rejectsEmptyEmail() async {
27
+ let service = UserService(repo: InMemoryUserRepo())
28
+ do {
29
+ _ = try await service.create(email: "")
30
+ XCTFail("expected ValidationError")
31
+ } catch is ValidationError {
32
+ // expected
33
+ } catch {
34
+ XCTFail("unexpected error: \(error)")
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Name tests `test_behaviour_expectation`. Structure: **Arrange → Act → Assert**.
41
+
42
+ ## Swift Testing (Swift 6+)
43
+
44
+ ```swift
45
+ import Testing
46
+ @testable import MyApp
47
+
48
+ struct UserServiceTests {
49
+ @Test
50
+ func createUserReturnsPersistedUser() async throws {
51
+ let service = UserService(repo: InMemoryUserRepo())
52
+ let user = try await service.create(email: "a@x.com")
53
+ #expect(user.email == "a@x.com")
54
+ #expect(!user.id.isEmpty)
55
+ }
56
+
57
+ @Test
58
+ func createUserRejectsEmptyEmail() async throws {
59
+ let service = UserService(repo: InMemoryUserRepo())
60
+ await #expect(throws: ValidationError.self) {
61
+ try await service.create(email: "")
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Cleaner: `#expect(...)` macro, `@Test` annotation, real async support.
68
+
69
+ ## Parametrized tests
70
+
71
+ ```swift
72
+ @Test("add cases", arguments: [
73
+ (1, 2, 3),
74
+ (0, 0, 0),
75
+ (-1, 1, 0),
76
+ ])
77
+ func add(a: Int, b: Int, expected: Int) {
78
+ #expect(MyApp.add(a, b) == expected)
79
+ }
80
+ ```
81
+
82
+ XCTest equivalent requires manual loops or `XCTestCase` subclasses.
83
+
84
+ ## Assertions
85
+
86
+ | XCTest | Swift Testing |
87
+ |---|---|
88
+ | `XCTAssertEqual(a, b)` | `#expect(a == b)` |
89
+ | `XCTAssertTrue(x)` | `#expect(x)` |
90
+ | `XCTAssertNil(x)` | `#expect(x == nil)` |
91
+ | `XCTAssertThrowsError { ... }` | `#expect(throws: ...) { ... }` |
92
+ | `XCTFail("msg")` | `Issue.record("msg")` |
93
+
94
+ ## Fakes over mocks
95
+
96
+ Hand-roll fakes; Swift's strong type system makes them cheap.
97
+
98
+ ```swift
99
+ final class InMemoryUserRepo: UserRepositoryProtocol {
100
+ private var users: [String: User] = [:]
101
+ func find(id: String) async -> User? { users[id] }
102
+ func save(_ u: User) async { users[u.id] = u }
103
+ }
104
+ ```
105
+
106
+ For mock libraries, `Cuckoo` and `Mockable` exist; most teams find hand-rolled cheaper in Swift than in JVM.
107
+
108
+ ## Async tests
109
+
110
+ Swift Testing supports `async` natively. XCTest also, from iOS 13+ / macOS 10.15+:
111
+
112
+ ```swift
113
+ func test_loads() async throws {
114
+ let user = try await service.load(id: "u1")
115
+ XCTAssertEqual(user.id, "u1")
116
+ }
117
+ ```
118
+
119
+ For code using completion callbacks (legacy), use `XCTestExpectation`:
120
+
121
+ ```swift
122
+ func test_legacy() {
123
+ let exp = expectation(description: "callback")
124
+ legacy { result in
125
+ XCTAssertNotNil(result)
126
+ exp.fulfill()
127
+ }
128
+ wait(for: [exp], timeout: 5)
129
+ }
130
+ ```
131
+
132
+ New code shouldn't need this — bridge to `async` with `withCheckedContinuation`.
133
+
134
+ ## Timing / clock control
135
+
136
+ Mock time with a `Clock` protocol:
137
+
138
+ ```swift
139
+ protocol Clock { func now() -> Date }
140
+
141
+ final class FixedClock: Clock {
142
+ var time: Date
143
+ init(_ t: Date) { self.time = t }
144
+ func now() -> Date { time }
145
+ }
146
+ ```
147
+
148
+ For `Task.sleep` in tests, use `ContinuousClock` with a manually advanced test clock (Swift Testing / swift-async-algorithms).
149
+
150
+ Swift Testing integrates with the new `Clock` primitives and supports virtual time.
151
+
152
+ ## UI tests — XCUITest
153
+
154
+ ```swift
155
+ final class AppUITests: XCTestCase {
156
+ func test_login_success() {
157
+ let app = XCUIApplication()
158
+ app.launch()
159
+ app.textFields["email"].tap()
160
+ app.textFields["email"].typeText("a@x.com")
161
+ app.buttons["Continue"].tap()
162
+ XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5))
163
+ }
164
+ }
165
+ ```
166
+
167
+ Rules:
168
+ - Use `accessibilityIdentifier`, not labels — labels break when copy changes.
169
+ - Reset app state in `setUp`; UI tests are order-dependent if you don't.
170
+ - Keep to happy paths and critical flows; they're slow and brittle.
171
+
172
+ ## Snapshot tests
173
+
174
+ `swift-snapshot-testing` (`point-free`) for views, JSON, formatted strings.
175
+
176
+ ```swift
177
+ import SnapshotTesting
178
+ import XCTest
179
+
180
+ final class UserViewTests: XCTestCase {
181
+ func test_userView() {
182
+ let vc = UserViewController(user: .sample)
183
+ assertSnapshot(matching: vc, as: .image)
184
+ }
185
+ }
186
+ ```
187
+
188
+ Review diffs deliberately; don't mass-accept.
189
+
190
+ ## Coverage
191
+
192
+ Xcode can emit `.xcresult` with coverage; `xcrun xccov view --report` for CLI. Set a threshold in your `fastlane` / CI.
193
+
194
+ ## Performance tests
195
+
196
+ ```swift
197
+ func test_perf_parsing() {
198
+ measure {
199
+ _ = parse(sampleInput)
200
+ }
201
+ }
202
+ ```
203
+
204
+ Xcode stores baselines; regressions highlight on re-run. Keep perf tests to critical paths; they're slow and machine-dependent.
205
+
206
+ ## Test plans
207
+
208
+ Use Xcode test plans to split fast unit tests, slower integration, UI tests into separate CI stages:
209
+
210
+ ```
211
+ UnitTests.xctestplan # fast, ~seconds
212
+ IntegrationTests.xctestplan # real deps, minutes
213
+ UITests.xctestplan # UI, slow
214
+ ```
215
+
216
+ Run unit on every PR; integration and UI on merge-to-main.
217
+
218
+ ## Anti-patterns
219
+
220
+ | Anti-pattern | Fix |
221
+ |---|---|
222
+ | `Thread.sleep` / `sleep()` in tests to wait for async | `XCTestExpectation` or `async/await` |
223
+ | `try!` inside a test | Use `try` + `XCTAssertNoThrow` or Swift Testing `#expect(throws:)` |
224
+ | Shared state across tests via singletons | Reset in `setUp`; prefer DI |
225
+ | Snapshot test accepted without review | Always diff before committing |
226
+ | UI tests finding by label (`"Log in"`) | Use `accessibilityIdentifier` |
227
+ | Mocking the thing you're testing | Test via real public API |
228
+ | `XCTFail` + guard + `return` scattered inline | Use Swift Testing `#expect(throws:)` or helper methods |
229
+ | Tests depending on device locale / time | Inject locale / clock |
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: typescript
3
+ description: TypeScript language skill — types, idioms, async, tooling. Pair with end skills (`backend`, `frontend`, `mobile`) for architecture and with `coding-standards` for cross-language principles.
4
+ origin: ecc-fork + original (https://github.com/affaan-m/everything-claude-code, MIT)
5
+ ---
6
+
7
+ # TypeScript
8
+
9
+ Type system, idioms, async, tooling. **Language-focused**. Architecture belongs to end skills; general principles (TDD, naming, error strategy) live in `coding-standards`.
10
+
11
+ ## When to load
12
+
13
+ - Project primary language is TypeScript (Node.js / Deno / Bun / browser)
14
+ - Reviewing TS code
15
+ - Designing types, generics, discriminated unions
16
+ - Setting up `tsconfig.json`, build, test tooling
17
+
18
+ ## Core principles
19
+
20
+ 1. **`strict: true`, always.** Non-negotiable. The rest of TS is barely worth using without it.
21
+ 2. **No `any`.** Use `unknown` + narrowing if you don't know the shape. Every `any` in review gets a blocker.
22
+ 3. **Types describe intent.** Prefer `UserId` branded type over bare `string`; use discriminated unions over boolean flags.
23
+ 4. **Inference first.** Let TS infer; add annotations on public signatures and where inference is wrong or opaque.
24
+ 5. **`const` by default, `let` only when reassigning.** Never `var`.
25
+ 6. **Immutability where practical.** `readonly` on fields, `ReadonlyArray<T>` for inputs.
26
+ 7. **`===` only.** `==` has JS quirks; there is no legitimate reason to use it in new code.
27
+ 8. **Explicit error types at boundaries** (HTTP, queue, FFI). Inside, throw freely.
28
+
29
+ ## How to use references
30
+
31
+ | Reference | When to load |
32
+ |---|---|
33
+ | [`references/types.md`](references/types.md) | Generics, unions, discriminated unions, conditional types, utility types, brands |
34
+ | [`references/idioms.md`](references/idioms.md) | Destructuring, optional chaining, nullish coalescing, modules, enums vs. union |
35
+ | [`references/async.md`](references/async.md) | Promises, async/await, error propagation, cancellation (AbortController), concurrency |
36
+ | [`references/errors.md`](references/errors.md) | Error classes, `Result` / `neverthrow`, `try/catch`, re-throw semantics |
37
+ | [`references/testing.md`](references/testing.md) | Vitest / Jest, mocking, fixtures, integration tests |
38
+ | [`references/tooling.md`](references/tooling.md) | `tsconfig`, ESLint, formatter, bundlers, monorepo layout |
39
+
40
+ ## Forbidden patterns (auto-reject)
41
+
42
+ - `any` without a comment explaining why
43
+ - `as X` casts without runtime validation (use a schema validator at boundaries)
44
+ - `==` / `!=`
45
+ - `var`
46
+ - Unhandled promise (`.then()` without `.catch()`, or an `await` without containing `try`)
47
+ - `Function` / `Object` as a type
48
+ - Default-exporting everything in new code (named exports — one public name per import)
49
+ - Enums for string constants (use literal string unions instead — smaller, tree-shakeable)
50
+ - Mutating function parameters
51
+ - `console.log` left in committed code
@@ -0,0 +1,241 @@
1
+ # TypeScript — Async
2
+
3
+ Promises, async/await, error propagation, cancellation, concurrency.
4
+
5
+ ## `async/await` is sugar for promises
6
+
7
+ ```ts
8
+ // These are equivalent
9
+ async function f() { const x = await g(); return x + 1; }
10
+ function f() { return g().then(x => x + 1); }
11
+ ```
12
+
13
+ Prefer `async/await` for linear flows, `.then` chains for pipelines on a single value.
14
+
15
+ ## Every promise must be awaited or handled
16
+
17
+ Unhandled rejections crash Node (in strict mode) or log cryptic warnings.
18
+
19
+ ```ts
20
+ // ❌ fire-and-forget
21
+ sendEmail(user.email); // if it rejects → unhandled
22
+ doThing().then(logSuccess); // no .catch
23
+
24
+ // ✅
25
+ await sendEmail(user.email);
26
+
27
+ // ✅ intentional fire-and-forget: use .catch
28
+ void sendEmail(user.email).catch(err => log.error(err));
29
+ ```
30
+
31
+ TS/ESLint rules: `@typescript-eslint/no-floating-promises` catches these.
32
+
33
+ ## Error propagation
34
+
35
+ Throws inside `async` become rejections. `try/catch` catches them.
36
+
37
+ ```ts
38
+ async function load() {
39
+ try {
40
+ const r = await fetch(url);
41
+ if (!r.ok) throw new HttpError(r.status);
42
+ return await r.json();
43
+ } catch (e) {
44
+ if (e instanceof HttpError && e.status === 404) return null;
45
+ throw e;
46
+ }
47
+ }
48
+ ```
49
+
50
+ `catch (e: unknown)` — always. `e` is not an `Error` in JS; could be anything thrown.
51
+
52
+ ```ts
53
+ try { ... } catch (e) {
54
+ if (e instanceof Error) log.error(e.message);
55
+ else log.error('non-Error thrown', { e });
56
+ }
57
+ ```
58
+
59
+ ## Concurrency
60
+
61
+ ### Parallel, all must succeed
62
+
63
+ ```ts
64
+ const [u, o, p] = await Promise.all([
65
+ getUser(id),
66
+ getOrders(id),
67
+ getPrefs(id),
68
+ ]);
69
+ ```
70
+
71
+ One rejection → whole thing rejects; other in-flight promises continue but their results are discarded.
72
+
73
+ ### Parallel, any may fail
74
+
75
+ ```ts
76
+ const results = await Promise.allSettled(tasks);
77
+ for (const r of results) {
78
+ if (r.status === 'fulfilled') use(r.value);
79
+ else log.warn(r.reason);
80
+ }
81
+ ```
82
+
83
+ ### First to finish wins
84
+
85
+ ```ts
86
+ const winner = await Promise.race([req, timeout(5000)]);
87
+ ```
88
+
89
+ ### First to succeed (any)
90
+
91
+ ```ts
92
+ const first = await Promise.any([primary(), secondary(), tertiary()]);
93
+ // rejects only if all fail
94
+ ```
95
+
96
+ ## Sequential vs. parallel — don't accidentally serialize
97
+
98
+ ```ts
99
+ // ❌ serial (each waits for the prior)
100
+ for (const id of ids) {
101
+ await fetchUser(id);
102
+ }
103
+
104
+ // ✅ parallel
105
+ await Promise.all(ids.map(fetchUser));
106
+ ```
107
+
108
+ Sequential is correct when order matters or when you want backpressure. Parallel is often what you wanted but wrote wrong.
109
+
110
+ ## Bounded parallelism
111
+
112
+ Unlimited parallelism blows up memory and hammers downstreams. Bound it.
113
+
114
+ ```ts
115
+ import pLimit from 'p-limit';
116
+
117
+ const limit = pLimit(10);
118
+ const results = await Promise.all(ids.map(id => limit(() => fetchUser(id))));
119
+ ```
120
+
121
+ Or chunk manually:
122
+
123
+ ```ts
124
+ async function inChunks<T, R>(xs: T[], size: number, fn: (x: T) => Promise<R>) {
125
+ const out: R[] = [];
126
+ for (let i = 0; i < xs.length; i += size) {
127
+ const chunk = xs.slice(i, i + size);
128
+ out.push(...await Promise.all(chunk.map(fn)));
129
+ }
130
+ return out;
131
+ }
132
+ ```
133
+
134
+ ## Cancellation — `AbortController`
135
+
136
+ The standard cancellation primitive for fetch, streams, timers.
137
+
138
+ ```ts
139
+ const ac = new AbortController();
140
+ setTimeout(() => ac.abort(), 5000);
141
+
142
+ try {
143
+ const r = await fetch(url, { signal: ac.signal });
144
+ return await r.json();
145
+ } catch (e) {
146
+ if (e instanceof DOMException && e.name === 'AbortError') {
147
+ // cancelled
148
+ } else {
149
+ throw e;
150
+ }
151
+ }
152
+ ```
153
+
154
+ For your own async functions, accept a `signal` and check / propagate it:
155
+
156
+ ```ts
157
+ async function work({ signal }: { signal?: AbortSignal }) {
158
+ if (signal?.aborted) throw new DOMException('aborted', 'AbortError');
159
+ signal?.addEventListener('abort', () => { /* clean up */ }, { once: true });
160
+ ...
161
+ }
162
+ ```
163
+
164
+ ## Timeouts — `AbortSignal.timeout` (modern)
165
+
166
+ ```ts
167
+ const r = await fetch(url, { signal: AbortSignal.timeout(5000) });
168
+ ```
169
+
170
+ Falls back to `AbortController` + `setTimeout` on older runtimes.
171
+
172
+ ## Retry with backoff
173
+
174
+ ```ts
175
+ async function withRetry<T>(
176
+ fn: () => Promise<T>,
177
+ { attempts = 3, baseMs = 100, maxMs = 10_000 } = {},
178
+ ): Promise<T> {
179
+ for (let i = 0; i < attempts; i++) {
180
+ try { return await fn(); }
181
+ catch (e) {
182
+ if (i === attempts - 1) throw e;
183
+ const delay = Math.min(maxMs, baseMs * 2 ** i) * (0.5 + Math.random());
184
+ await new Promise(r => setTimeout(r, delay));
185
+ }
186
+ }
187
+ throw new Error('unreachable');
188
+ }
189
+ ```
190
+
191
+ Only retry idempotent operations. See `backend/references/resilience.md`.
192
+
193
+ ## Async iterators & streams
194
+
195
+ For data you consume one chunk at a time.
196
+
197
+ ```ts
198
+ async function* readLines(stream: ReadableStream<Uint8Array>) {
199
+ const decoder = new TextDecoder();
200
+ const reader = stream.getReader();
201
+ let buf = '';
202
+ while (true) {
203
+ const { value, done } = await reader.read();
204
+ if (done) break;
205
+ buf += decoder.decode(value, { stream: true });
206
+ const lines = buf.split('\n');
207
+ buf = lines.pop()!;
208
+ for (const line of lines) yield line;
209
+ }
210
+ if (buf) yield buf;
211
+ }
212
+
213
+ for await (const line of readLines(resp.body!)) {
214
+ process(line);
215
+ }
216
+ ```
217
+
218
+ ## Microtasks & the event loop
219
+
220
+ `await` queues continuation as a microtask — runs before macro-tasks (`setTimeout`) even at delay 0.
221
+
222
+ ```ts
223
+ Promise.resolve().then(() => console.log('A')); // microtask
224
+ setTimeout(() => console.log('B'), 0); // macrotask
225
+ // A then B
226
+ ```
227
+
228
+ You rarely need to know this, until you're debugging an out-of-order log.
229
+
230
+ ## Anti-patterns
231
+
232
+ | Anti-pattern | Fix |
233
+ |---|---|
234
+ | `new Promise((res) => { ... res(x) })` wrapping a callback API | Use `util.promisify` (Node) or a library wrapper |
235
+ | Rejecting with a string | Always `new Error(...)` |
236
+ | Nested `.then` chains | Flatten with `await` |
237
+ | `.catch` without re-throw on unknown errors | Narrow or rethrow |
238
+ | `await` inside a `.forEach` | Use `for...of` or `Promise.all` |
239
+ | `async` function returning `Promise<Promise<T>>` | Unnecessary; one `async` is enough |
240
+ | Dangling `setTimeout`/`setInterval` in React effects | Return cleanup or use a library |
241
+ | Fire-and-forget without `.catch` | Silent failures; add `.catch(log)` |