Skip to content

Commit a35eba6

Browse files
PaulJPhilpclaude
andcommitted
chore(release): v0.12.1 — QA validator pipeline, fix 15 pattern content issues
Add QA validation pipeline that validates frontmatter, structure, and TypeScript code blocks for all 309 published patterns. Fix 15 patterns with broken code blocks (yield* outside generators, pseudocode fencing, duplicate declarations, broken frontmatter). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6fdf665 commit a35eba6

28 files changed

Lines changed: 825 additions & 170 deletions

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# Changelog
22

3+
## [0.12.1] - 2026-02-10
4+
5+
### 🔧 QA Pipeline
6+
- **QA Validator**: New `validator.ts` module validates frontmatter, structure, and TypeScript code blocks for all published `.mdx` patterns
7+
- **`qa validate` command**: New subcommand runs validation with configurable concurrency and writes per-pattern `-qa.json` result files
8+
- **`qa process` updated**: Now runs validation as Step 1 before status/report/repair
9+
10+
### 🐛 Content Fixes
11+
- **15 pattern files fixed**: Corrected TypeScript code blocks across concurrency, schema, error-management, streams, scheduling, and tooling patterns
12+
- Fixed broken frontmatter in `concurrency-fork-basics`
13+
- Changed pseudocode blocks from `typescript` to `text` fencing where appropriate
14+
- Wrapped `yield*` calls in proper `Effect.gen` generators inside `catchAll`, `catchTag`, `orElse` callbacks
15+
- Scoped duplicate declarations in linting/type-error example blocks
16+
- Fixed `await` outside async and `yield*` outside generator contexts
17+
18+
### 📦 Dependencies
19+
- **`@effect-patterns/ep-admin`** 0.2.1: Added `gray-matter` for frontmatter parsing
20+
- **`@effect-patterns/toolkit`** 0.4.1: Added `releaseVersion` column to `effectPatterns` schema
21+
22+
### 🛠️ Improvements
23+
- **`QAConfig` type**: Added `publishedPatternsDir` field, made all properties `readonly`
24+
- **Tagged errors**: Added `QAValidationError` and `TypeCheckError` to QA error types
25+
- **Pattern sync script**: New `scripts/sync-patterns-from-mdx.ts` utility
26+
327
## [0.12.0] - 2026-02-10
428

529
### 🚀 First npm Publish

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

content/published/patterns/concurrency/concurrency-pattern-race-timeout.mdx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,12 @@ const program = Effect.gen(function* () {
142142

143143
const raceWithFallback = primary.pipe(
144144
Effect.timeout("150 millis"),
145-
Effect.catchAll(() => {
146-
yield* Effect.log(`[PRIMARY] Timed out, using fallback`);
147-
148-
return fallback;
149-
})
145+
Effect.catchAll(() =>
146+
Effect.gen(function* () {
147+
yield* Effect.log(`[PRIMARY] Timed out, using fallback`);
148+
return yield* fallback;
149+
})
150+
)
150151
);
151152

152153
const fallbackResult = yield* raceWithFallback;

content/published/patterns/concurrency/getting-started/concurrency-fork-basics.mdx

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
---
2-
lessonOrder: 1
3-
---
4-
1---
52
title: Fork Background Work
63
id: concurrency-fork-basics
74
skillLevel: beginner
@@ -143,19 +140,21 @@ Effect.runPromise(fanOut)
143140
## Common Patterns
144141

145142
```typescript
146-
// Fire and forget (logging, analytics)
147-
yield* Effect.forkDaemon(sendAnalytics(event))
148-
149-
// Background with timeout
150-
const fiber = yield* Effect.fork(longRunningTask)
151-
yield* Effect.sleep("5 seconds")
152-
yield* Fiber.interrupt(fiber) // Cancel if still running
153-
154-
// Parallel fan-out/fan-in
155-
const fibers = yield* Effect.forEach(items, (item) =>
156-
Effect.fork(processItem(item))
157-
)
158-
const results = yield* Fiber.joinAll(fibers)
143+
Effect.gen(function* () {
144+
// Fire and forget (logging, analytics)
145+
yield* Effect.forkDaemon(sendAnalytics(event))
146+
147+
// Background with timeout
148+
const fiber = yield* Effect.fork(longRunningTask)
149+
yield* Effect.sleep("5 seconds")
150+
yield* Fiber.interrupt(fiber) // Cancel if still running
151+
152+
// Parallel fan-out/fan-in
153+
const fibers = yield* Effect.forEach(items, (item) =>
154+
Effect.fork(processItem(item))
155+
)
156+
const results = yield* Fiber.joinAll(fibers)
157+
})
159158
```
160159

161160
## Best Practices

content/published/patterns/concurrency/state-management-pattern-synchronized-ref.mdx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -294,24 +294,27 @@ const program = Effect.gen(function* () {
294294
});
295295

296296
const updateAndNotify = (newValue: number) =>
297-
Ref.modify(observedState, (current) => {
298-
const notification: Notification = {
299-
oldValue: current.value,
300-
newValue,
301-
timestamp: new Date(),
302-
};
297+
Effect.gen(function* () {
298+
const notification = yield* Ref.modify(observedState, (current) => {
299+
const n: Notification = {
300+
oldValue: current.value,
301+
newValue,
302+
timestamp: new Date(),
303+
};
304+
return [
305+
n,
306+
{
307+
value: newValue,
308+
lastChange: n.timestamp,
309+
},
310+
];
311+
});
303312

304313
yield* Effect.log(
305-
`[NOTIFY] ${current.value} → ${newValue} at ${notification.timestamp.toISOString()}`
314+
`[NOTIFY] ${notification.oldValue} → ${newValue} at ${notification.timestamp.toISOString()}`
306315
);
307316

308-
return [
309-
notification,
310-
{
311-
value: newValue,
312-
lastChange: notification.timestamp,
313-
},
314-
];
317+
return notification;
315318
});
316319

317320
// Trigger changes

content/published/patterns/core-concepts/comparing-data-by-value-with-structural-equality.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Then, compare instances using the `Equal.equals(a, b)` function.
3939

4040
In JavaScript, comparing two non-primitive values with `===` checks for _referential equality_. It only returns `true` if they are the exact same instance in memory. This means two objects with identical contents are not considered equal, which is a common source of bugs.
4141

42-
```typescript
42+
```text
4343
{ a: 1 } === { a: 1 } // false!
4444
```
4545

content/published/patterns/error-management/error-handling-pattern-propagation.mdx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -245,19 +245,21 @@ const program = Effect.gen(function* () {
245245

246246
for (let i = 1; i <= 3; i++) {
247247
const result = yield* networkCall.pipe(
248-
Effect.catchTag("NetworkError", (error) => {
249-
lastError = error;
248+
Effect.catchTag("NetworkError", (error) =>
249+
Effect.gen(function* () {
250+
lastError = error;
250251

251-
yield* Effect.log(
252-
`[RETRY] Attempt ${i} failed: ${error.statusCode}`
253-
);
252+
yield* Effect.log(
253+
`[RETRY] Attempt ${i} failed: ${error.statusCode}`
254+
);
254255

255-
if (i < 3) {
256-
yield* Effect.log(`[RETRY] Waiting before retry...`);
257-
}
256+
if (i < 3) {
257+
yield* Effect.log(`[RETRY] Waiting before retry...`);
258+
}
258259

259-
return Effect.fail(error);
260-
})
260+
return yield* Effect.fail(error);
261+
})
262+
)
261263
).pipe(
262264
Effect.tap(() => Effect.log(`[SUCCESS] Connected on attempt ${i}`))
263265
).pipe(

content/published/patterns/error-management/scheduling-pattern-exponential-backoff.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ const exponentialBackoffWithJitter = (config: BackoffConfig) => {
114114
const withJitter = exponential * (0.5 + Math.random() * 0.5); // ±50% jitter
115115
const capped = Math.min(withJitter, config.maxDelayMs);
116116

117-
yield* Effect.log(
117+
console.log(
118118
`[BACKOFF] Attempt ${attempt + 1}: ${Math.round(capped)}ms delay`
119119
);
120120

content/published/patterns/getting-started/getting-started-effect-vs-promise.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,13 @@ const fetchUser = (id: string): Effect.Effect<User, FetchError | ParseError> =>
9494
## Key Insight
9595

9696
With Promises:
97-
```typescript
97+
```text
9898
// You have to read the implementation to know what might fail
9999
async function doSomething(): Promise<Result> { ... }
100100
```
101101

102102
With Effect:
103-
```typescript
103+
```text
104104
// The type tells you everything
105105
function doSomething(): Effect<Result, NetworkError | ParseError, Database> { ... }
106106
// ^success ^errors ^dependencies

content/published/patterns/scheduling/scheduling-pattern-advanced-retry-chains.mdx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,12 @@ const program = Effect.gen(function* () {
300300

301301
const fallbackChain = callEndpoint("primary", true).pipe(
302302
Effect.orElse(() => callEndpoint("secondary", false)),
303-
Effect.orElse(() => {
304-
yield* Effect.log(`[FALLBACK] Using cached data`);
305-
return Effect.succeed(endpoints.cache);
306-
})
303+
Effect.orElse(() =>
304+
Effect.gen(function* () {
305+
yield* Effect.log(`[FALLBACK] Using cached data`);
306+
return endpoints.cache;
307+
})
308+
)
307309
);
308310

309311
const result = yield* fallbackChain;
@@ -470,10 +472,12 @@ const createHealthAwareRetry = (config: {
470472
return healthy;
471473
}).pipe(
472474
Effect.timeout(Duration.millis(config.healthCheckTimeoutMs)),
473-
Effect.catchAll(() => {
474-
yield* Effect.log("[HEALTH] Check timed out, assuming unhealthy");
475-
return Effect.succeed(false);
476-
})
475+
Effect.catchAll(() =>
476+
Effect.gen(function* () {
477+
yield* Effect.log("[HEALTH] Check timed out, assuming unhealthy");
478+
return false;
479+
})
480+
)
477481
);
478482

479483
return performHealthCheck;

0 commit comments

Comments
 (0)