Skip to content

Commit 1a11572

Browse files
committed
feat: add example code files and update docs to reference them
1 parent 45516dc commit 1a11572

179 files changed

Lines changed: 5324 additions & 2181 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"root": true,
3+
"parser": "@typescript-eslint/parser",
4+
"plugins": ["@typescript-eslint/eslint-plugin"],
5+
"extends": [
6+
"eslint:recommended",
7+
"plugin:@typescript-eslint/eslint-plugin/recommended"
8+
],
9+
"parserOptions": {
10+
"ecmaVersion": 2022,
11+
"sourceType": "module"
12+
},
13+
"env": {
14+
"es6": true,
15+
"node": true
16+
},
17+
"rules": {
18+
"@typescript-eslint/no-unused-vars": [
19+
"warn",
20+
{ "argsIgnorePattern": "^_" }
21+
],
22+
"no-constant-condition": "off"
23+
}
24+
}

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,21 @@ How to test Effect code effectively, reliably, and deterministically.
260260

261261
## AI Coding Rules
262262

263-
This project provides a machine-readable set of coding rules for AI-powered IDEs and coding agents.
263+
This project provides several machine-readable sets of coding rules, tailored for different needs and optimized for AI-powered IDEs and coding agents like Cursor or GitHub Copilot.
264264

265-
You can find the latest rules files in the [rules directory](./rules/). These files are designed for integration with tools like Cursor, GitHub Copilot, and other AI coding assistants.
265+
You can find the latest rule files in the [`rules`](./rules/) directory. These are automatically generated from the content in this repository.
266266

267-
- **rules.md**: Human-readable rules summary
268-
- **rules.json**: Structured rules for programmatic consumption
267+
### Available Formats
268+
269+
- **Comprehensive Rules (`rules.md`)**: A human-readable markdown file containing all patterns, guidelines, and rationale.
270+
- **Compact Rules (`rules-compact.md`)**: A condensed version containing only the core rule for each pattern, designed for minimal token usage.
271+
- **Structured JSON (`rules.json`)**: A machine-readable JSON file containing all rule information for programmatic use.
272+
- **By Skill Level**: Detailed rules, including examples, split into separate files for each skill level.
273+
- [`beginner.md`](./rules/beginner.md)
274+
- [`intermediate.md`](./rules/intermediate.md)
275+
- [`advanced.md`](./rules/advanced.md)
276+
- **By Use Case**: Detailed rules, including examples, grouped by practical application areas.
277+
- [View all use cases](./rules/by-use-case/)
269278

270279
---
271280

bun.lock

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

content/access-config-in-context.mdx

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,7 @@ This allows your business logic to declaratively state its dependency on a piece
2323

2424
## Good Example
2525

26-
```typescript
27-
import { Config, Effect } from "effect";
28-
29-
const ServerConfig = Config.all({
30-
host: Config.string("HOST"),
31-
port: Config.number("PORT"),
32-
});
33-
34-
const program = Effect.gen(function* () {
35-
const config = yield* ServerConfig;
36-
yield* Effect.log(`Starting server on http://${config.host}:${config.port}`);
37-
});
38-
```
26+
<Example path="./src/access-config-in-context.ts" />
3927

4028
**Explanation:**
4129
By yielding the config object, you make your dependency explicit and leverage Effect's context system for testability and modularity.

content/accessing-current-time-with-clock.mdx

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,41 +42,7 @@ This makes any time-dependent logic pure, deterministic, and easy to test with p
4242

4343
This example shows a function that checks if a token is expired. Its logic depends on `Clock`, making it fully testable.
4444

45-
```typescript
46-
import { Effect, Clock, Layer } from "effect";
47-
import { TestClock } from "effect/TestClock";
48-
import { describe, it, expect } from "vitest";
49-
50-
interface Token {
51-
readonly value: string;
52-
readonly expiresAt: number; // UTC milliseconds
53-
}
54-
55-
// This function is pure and testable because it depends on Clock
56-
const isTokenExpired = (token: Token): Effect.Effect<boolean, never, Clock> =>
57-
Clock.currentTimeMillis.pipe(
58-
Effect.map((now) => now > token.expiresAt),
59-
);
60-
61-
// --- Testing the function ---
62-
describe("isTokenExpired", () => {
63-
const token = { value: "abc", expiresAt: 1000 };
64-
65-
it("should return false when the clock is before the expiry time", () =>
66-
Effect.gen(function* () {
67-
yield* TestClock.setTime(500); // Set virtual time
68-
const isExpired = yield* isTokenExpired(token);
69-
expect(isExpired).toBe(false);
70-
}).pipe(Effect.provide(TestClock.layer), Effect.runPromise));
71-
72-
it("should return true when the clock is after the expiry time", () =>
73-
Effect.gen(function* () {
74-
yield* TestClock.setTime(1500); // Set virtual time
75-
const isExpired = yield* isTokenExpired(token);
76-
expect(isExpired).toBe(true);
77-
}).pipe(Effect.provide(TestClock.layer), Effect.runPromise));
78-
});
79-
```
45+
<Example path="./src/accessing-current-time-with-clock.ts" />
8046

8147
---
8248

content/accumulate-multiple-errors-with-either.mdx

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,39 +40,7 @@ However, for tasks like validating a user's input, this is poor user experience.
4040

4141
Using `Schema.decode` with the `allErrors: true` option demonstrates this pattern perfectly. The underlying mechanism uses `Either` to collect all parsing errors into an array instead of stopping at the first one.
4242

43-
````typescript
44-
import { Effect, Schema } from "effect";
45-
46-
const UserSchema = Schema.Struct({
47-
name: Schema.String.pipe(Schema.minLength(3)),
48-
email: Schema.String.pipe(Schema.pattern(/@/)),
49-
});
50-
51-
const invalidInput = {
52-
name: "Al", // Too short
53-
email: "bob-no-at-sign.com", // Invalid pattern
54-
};
55-
56-
// Use { allErrors: true } to enable error accumulation
57-
const decoded = Schema.decode(UserSchema)(invalidInput, { allErrors: true });
58-
59-
const program = Effect.match(decoded, {
60-
onFailure: (error) => {
61-
// The error contains a tree of all validation failures
62-
console.log("Validation failed with multiple errors:");
63-
error.errors.forEach((e, i) => console.log(`${i + 1}. ${e.message}`));
64-
},
65-
onSuccess: (user) => console.log("User is valid:", user),
66-
});
67-
68-
Effect.runSync(program);
69-
/*
70-
Output:
71-
Validation failed with multiple errors:
72-
1. name must be a string at least 3 character(s) long
73-
2. email must be a string matching the pattern /@/
74-
*/
75-
````
43+
`<Example path="./src/accumulate-multiple-errors-with-either.ts" />`
7644

7745
---
7846

content/add-caching-by-wrapping-a-layer.mdx

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -45,63 +45,7 @@ This approach is powerful because:
4545

4646
We have a `WeatherService` that makes slow API calls. We create a `WeatherService.cached` wrapper layer that adds an in-memory cache using a `Ref` and a `Map`.
4747

48-
```typescript
49-
import { Effect, Layer, Ref, Duration } from "effect";
50-
51-
// 1. The original service definition
52-
class WeatherService extends Effect.Tag("WeatherService")<
53-
WeatherService,
54-
{ readonly getForecast: (city: string) => Effect.Effect<string, "ApiError"> }
55-
>() {}
56-
57-
// 2. The "Live" implementation that is slow
58-
const WeatherServiceLive = Layer.succeed(
59-
WeatherService,
60-
WeatherService.of({
61-
getForecast: (city) =>
62-
Effect.succeed(`Sunny in ${city}`).pipe(
63-
Effect.delay("2 seconds"),
64-
Effect.tap(() => Effect.log(`Fetched live forecast for ${city}`)),
65-
),
66-
}),
67-
);
68-
69-
// 3. The Caching Wrapper Layer
70-
const WeatherServiceCached = Layer.effect(
71-
WeatherService,
72-
Effect.gen(function* () {
73-
// It REQUIRES the original WeatherService
74-
const underlyingService = yield* WeatherService;
75-
const cache = yield* Ref.make(new Map<string, string>());
76-
77-
return WeatherService.of({
78-
getForecast: (city) =>
79-
Ref.get(cache).pipe(
80-
Effect.flatMap((map) =>
81-
map.has(city)
82-
? Effect.log(`Cache HIT for ${city}`).pipe(Effect.as(map.get(city)!))
83-
: Effect.log(`Cache MISS for ${city}`).pipe(
84-
Effect.flatMap(() => underlyingService.getForecast(city)),
85-
Effect.tap((forecast) => Ref.update(cache, (map) => map.set(city, forecast))),
86-
),
87-
),
88-
),
89-
});
90-
}),
91-
);
92-
93-
// 4. Compose the final layer. The wrapper is provided with the live implementation.
94-
const AppLayer = Layer.provide(WeatherServiceCached, WeatherServiceLive);
95-
96-
// 5. The application logic
97-
const program = Effect.gen(function* () {
98-
const weather = yield* WeatherService;
99-
yield* weather.getForecast("London"); // First call is slow (MISS)
100-
yield* weather.getForecast("London"); // Second call is instant (HIT)
101-
});
102-
103-
Effect.runPromise(Effect.provide(program, AppLayer));
104-
```
48+
<Example path="./src/add-caching-by-wrapping-a-layer.ts" />
10549

10650
---
10751

content/add-custom-metrics.mdx

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -47,38 +47,7 @@ This allows you to answer questions like:
4747

4848
This example creates a counter to track how many times a user is created and a histogram to track the duration of the database operation.
4949

50-
```typescript
51-
import { Effect, Metric, Duration } from "effect";
52-
53-
// 1. Define your metrics. It's good practice to keep them in one place.
54-
const userRegisteredCounter = Metric.counter("users_registered_total", {
55-
description: "A counter for how many users have been registered.",
56-
});
57-
58-
const dbDurationHistogram = Metric.histogram(
59-
"db_operation_duration_seconds",
60-
Metric.Histogram.Boundaries.exponential({ start: 0.01, factor: 2, count: 10 }),
61-
);
62-
63-
// 2. A simulated database call
64-
const saveUserToDb = Effect.succeed("user saved").pipe(
65-
Effect.delay(Duration.millis(Math.random() * 100)),
66-
);
67-
68-
// 3. Instrument the business logic
69-
const createUser = Effect.gen(function* () {
70-
// Use .pipe() and Metric.trackDuration to time the operation
71-
yield* saveUserToDb.pipe(Metric.trackDuration(dbDurationHistogram));
72-
73-
// Use Metric.increment to update the counter
74-
yield* Metric.increment(userRegisteredCounter);
75-
76-
return { status: "success" };
77-
});
78-
79-
// When run with a metrics backend, these metrics would be exported.
80-
Effect.runPromise(createUser);
81-
```
50+
<Example path="./src/add-custom-metrics.ts" />
8251

8352
---
8453

content/avoid-long-andthen-chains.mdx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,7 @@ debug than deeply nested functional chains.
2525

2626
## Good Example
2727

28-
```typescript
29-
import { Effect } from "effect";
30-
declare const step1: () => Effect.Effect<any>;
31-
declare const step2: (a: any) => Effect.Effect<any>;
32-
33-
Effect.gen(function* () {
34-
const a = yield* step1();
35-
const b = yield* step2(a);
36-
return b;
37-
});
38-
```
28+
<Example path="./src/avoid-long-andthen-chains.ts" />
3929

4030
**Explanation:**
4131
Generators keep sequential logic readable and easy to maintain.

content/beyond-the-date-type.mdx

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,36 +45,7 @@ This makes your time-based logic pure, predictable, and easy to test.
4545

4646
This example shows a function that creates a timestamped event. It depends on the `Clock` service, making it fully testable.
4747

48-
```typescript
49-
import { Effect, Clock, Layer } from "effect";
50-
import { TestClock } from "effect/TestClock";
51-
import { describe, it, expect } from "vitest";
52-
53-
interface Event {
54-
readonly message: string;
55-
readonly timestamp: number; // Store as a primitive number (UTC millis)
56-
}
57-
58-
// This function is pure and testable because it depends on Clock
59-
const createEvent = (message: string): Effect.Effect<Event, never, Clock> =>
60-
Effect.gen(function* () {
61-
const timestamp = yield* Clock.currentTimeMillis;
62-
return { message, timestamp };
63-
});
64-
65-
// --- Testing the function ---
66-
describe("createEvent", () => {
67-
it("should use the time from the TestClock", () =>
68-
Effect.gen(function* () {
69-
// Manually set the virtual time
70-
yield* TestClock.setTime(1672531200000); // Jan 1, 2023 UTC
71-
const event = yield* createEvent("User logged in");
72-
73-
// The timestamp is predictable and testable
74-
expect(event.timestamp).toBe(1672531200000);
75-
}).pipe(Effect.provide(TestClock.layer), Effect.runPromise));
76-
});
77-
```
48+
<Example path="./src/beyond-the-date-type.ts" />
7849

7950
---
8051

0 commit comments

Comments
 (0)