Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 2 additions & 158 deletions README.md

Large diffs are not rendered by default.

316 changes: 111 additions & 205 deletions bun.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
description: Always use Effect.Service pattern for service definitions. Never use Context.Tag or Context.GenericTag.
globs: "**/*.ts"
alwaysApply: true

# Always Use Effect.Service for Service Definitions
**Rule:** Always use the `Effect.Service` pattern for defining services. Never use `Context.Tag` or `Context.GenericTag`.

### Why This Rule Exists

The `Effect.Service` pattern is the modern, recommended way to define services in Effect-TS. It provides:
- Automatic service provision - services are available when you yield them
- Better type safety and inference
- Cleaner dependency management
- Consistent service definition patterns across the codebase
- Simplified testing - no need to manually provide services

### Anti-Pattern (Forbidden)

```typescript
// ❌ FORBIDDEN: Using Context.Tag for service definition
import { Context } from "effect";

export class MyService extends Context.Tag("MyService")<
MyService,
{
readonly doSomething: () => Effect.Effect<void>;
}
>() {}

// ❌ FORBIDDEN: Using Context.GenericTag for service definition
export const MyService = Context.GenericTag<MyServiceInterface>("MyService");

// ❌ FORBIDDEN: Manual layer creation with Context.Tag
const MyServiceLive = Layer.succeed(MyService, {
doSomething: () => Effect.succeed(undefined),
});
```

### Correct Pattern (Required)

#### 1. Synchronous Service (No Dependencies)

```typescript
// ✅ CORRECT: Use Effect.Service with sync for simple services
import { Effect } from "effect";

interface MyServiceInterface {
readonly doSomething: () => Effect.Effect<void>;
}

const makeMyService = (): MyServiceInterface => ({
doSomething: () => Effect.succeed(undefined),
});

export class MyService extends Effect.Service<MyService>()("MyService", {
sync: () => makeMyService(),
}) {}

// Usage: Just yield the service - Effect handles provision automatically
const program = Effect.gen(function* () {
const service = yield* MyService;
// Use the service
});
```

#### 2. Service with Dependencies

```typescript
// ✅ CORRECT: Use Effect.Service with effect and dependencies
export class DatabaseService extends Effect.Service<DatabaseService>()(
"DatabaseService",
{
effect: Effect.gen(function* () {
const config = yield* ConfigService; // Effect.Service automatically infers ConfigService as a dependency
return createDatabase(config);
}),
// ✅ CORRECT: No dependencies array needed - Effect.Service infers dependencies automatically
}
) {}

// Usage: Just yield the service - Effect handles provision automatically
```

#### 3. Service with Scoped Resources

```typescript
// ✅ CORRECT: Use Effect.Service with scoped for resource management
export class DatabaseService extends Effect.Service<DatabaseService>()(
"DatabaseService",
{
scoped: Effect.gen(function* () {
const config = yield* ConfigService; // Effect.Service automatically infers ConfigService as a dependency
return yield* Effect.acquireRelease(
() => createConnection(config),
(connection) => connection.close()
);
}),
// ✅ CORRECT: No dependencies array needed - Effect.Service infers dependencies automatically
}
) {}
```

#### 4. Service with Accessors

```typescript
// ✅ CORRECT: Enable accessors for convenient static access
export class LoggerService extends Effect.Service<LoggerService>()(
"LoggerService",
{
accessors: true,
sync: () => ({
log: (message: string) => Effect.sync(() => console.log(message)),
}),
}
) {}

// Usage: LoggerService.log("message") - static accessor available
```

### Migration from Context.Tag

When migrating existing services:

1. **Replace Context.Tag/GenericTag with Effect.Service class**
2. **Move implementation to `sync`, `effect`, or `scoped` property**
3. **Remove manual Layer creation** - Effect.Service handles it automatically
4. **Remove all `.Default` usage** - Never explicitly use `.Default`
5. **Just yield services** - Services are automatically available when you yield them

**Example Migration:**

```typescript
// Before (❌ FORBIDDEN)
export const StateStore = Context.GenericTag<StateStoreService>("StateStore");
const makeStateStore = (): StateStoreService => { /* ... */ };
export const StateStoreLive = Layer.succeed(StateStore, makeStateStore());

// Usage (❌ FORBIDDEN)
Effect.provide(program, StateStoreLive);

// After (✅ CORRECT)
const makeStateStore = (): StateStoreService => { /* ... */ };
export class StateStore extends Effect.Service<StateStore>()("StateStore", {
sync: () => makeStateStore(),
}) {}

// Usage (✅ CORRECT)
// Just yield the service - Effect handles provision automatically
const program = Effect.gen(function* () {
const store = yield* StateStore;
// Use the service
});
Effect.runPromise(program); // Service is automatically provided
```

### Exception: Test Mocks

Test mocks may use `Context.Tag` for isolated test scenarios, but production service definitions must always use `Effect.Service`:

```typescript
// ✅ ACCEPTABLE: Test-only mock using Context.Tag
const MockDisplayService = Context.Tag<{ isMocked: true }>();
```

**Explanation:**
The `Effect.Service` pattern is the standard way to define services in Effect-TS. It provides better type safety, automatic layer generation, and consistent patterns across the codebase. Using `Context.Tag` or `Context.GenericTag` directly is deprecated in favor of the `Effect.Service` pattern.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
description: Never use 'as any' type assertions - they bypass TypeScript's type safety.
globs: "**/*.ts"
alwaysApply: true

# Never Use 'as any' Type Assertions
**Rule:** Never use `as any` type assertions. They bypass TypeScript's type safety and are an anti-pattern.

### Why This Is Forbidden

Using `as any` disables TypeScript's type checking, which defeats the purpose of using a type-safe language. It can lead to:
- Runtime errors that could have been caught at compile time
- Loss of IDE autocomplete and type hints
- Hidden bugs that surface in production
- Reduced code maintainability

### Anti-Pattern (Forbidden)

```typescript
// ❌ FORBIDDEN: Using 'as any' to bypass type checking
const result = someFunction() as any;
const value = (someOption as any).value;
const effect = someEffect.gen(function* () {
// ...
}) as any;
```

### Correct Approaches

#### 1. Fix Type Mismatches Properly

If you encounter type errors, fix the underlying issue:

```typescript
// ✅ CORRECT: Fix the type definition or use proper type narrowing
const result: ExpectedType = someFunction();
const value = Option.isSome(someOption) ? someOption.value : defaultValue;
```

#### 2. Use Type Guards

```typescript
// ✅ CORRECT: Use type guards for runtime type checking
function isExpectedType(value: unknown): value is ExpectedType {
return typeof value === "object" && value !== null && "property" in value;
}

if (isExpectedType(value)) {
// TypeScript knows value is ExpectedType here
console.log(value.property);
}
```

#### 3. Use Proper Type Assertions

If you must assert a type, use a more specific assertion:

```typescript
// ✅ CORRECT: Use specific type assertions when you're certain
const result = someValue as SpecificType;
// Or use type predicates
const result = someValue as unknown as SpecificType;
```

#### 4. Fix Dependency Version Conflicts

If type errors are caused by multiple versions of the same library:

```typescript
// ✅ CORRECT: Resolve dependency conflicts at the package level
// Update package.json to use consistent versions
// Use workspace hoisting or resolution strategies
```

#### 5. Use Effect's Type System Properly

For Effect-specific type issues:

```typescript
// ✅ CORRECT: Use Effect's type system features
const effect = Effect.gen(function* () {
const value = yield* someEffect;
return value;
});

// ✅ CORRECT: Use Effect.map, Effect.flatMap, etc. for type transformations
const transformed = effect.pipe(
Effect.map((value) => value.property)
);
```

**Explanation:**
Type safety is one of TypeScript's core benefits. Using `as any` throws away this protection and should never be used. Always fix the underlying type issue instead of bypassing it.
Loading
Loading