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
25 changes: 25 additions & 0 deletions .changeset/effect-service-to-class-with-layer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
"@effect/language-service": minor
---

Add refactor to convert `Effect.Service` to `Context.Tag` with a static `Layer` property.

Supports all combinator kinds (`effect`, `scoped`, `sync`, `succeed`) and `dependencies`. The refactor replaces the `Effect.Service` class declaration with a `Context.Tag` class that has a `static layer` property using the corresponding `Layer` combinator.

Before:
```ts
export class MyService extends Effect.Service<MyService>()("MyService", {
effect: Effect.gen(function*() {
return { value: "hello" }
})
}){}
```

After:
```ts
export class MyService extends Context.Tag("MyService")<MyService, { value: string }>() {
static layer = Layer.effect(this, Effect.gen(function*() {
return { value: "hello" }
}));
}
```
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
- Toggle between pipe styles `X.pipe(Y)` and `pipe(X, Y)`
- Layer Magic: Automatically compose and build layers based on service dependencies
- Structural Type to Schema: Convert TypeScript interfaces and type aliases to Effect Schema classes, with automatic detection and reuse of existing schemas
- Convert `Effect.Service` to `Context.Tag` with a static `Layer` property (supports `effect`, `scoped`, `sync`, `succeed` combinators and `dependencies`)
### Codegens
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Result of running refactor effectServiceToClassWithLayer at position 100:20
import { Effect, Context } from "effect"
import * as Layer from "effect/Layer"

// this can be converted to a Context.Tag with a static layer property
export class MyService extends Effect.Service<MyService>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyService", {
effect: Effect.gen(function*() {
return {
value: "MyService"
}
})
}){}

// example result
export class MyServiceAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceAsContextTag")<MyServiceAsContextTag, {
value: string
}>(){
static layer = Layer.effect(this, Effect.gen(function*(){
return {
value: "MyService"
}
}))
}

export class MyServiceWithArgs extends Effect.Service<MyServiceWithArgs>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgs", {
effect: (arg: string) => Effect.gen(function*(){
return {
value: arg
}
})
}){}

// example result
export class MyServiceWithArgsAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsAsContextTag")<MyServiceWithArgsAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*(){
return {
value: arg
}
}))
}

// there is also a scoped variant, which should behave the same as the effect variant, but uses the scoped combinator.
export class MyServiceWithArgsScoped extends Effect.Service<MyServiceWithArgsScoped>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScoped", {
scoped: (arg: string) => Effect.gen(function*(){
return {
value: arg
}
})
}){}

export class MyServiceWithArgsScopedAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAsContextTag")<MyServiceWithArgsScopedAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.scoped(this, Effect.gen(function*(){
return {
value: arg
}
}))
}

// the sync variant returns the structure directly, without using an intermediate effect
export class MyServiceSync extends Effect.Service<MyServiceSync>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSync", {
sync: () => {
return {
value: "MyService"
}
}
}){}

// example result
export class MyServiceSyncAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSyncAsContextTag")<MyServiceSyncAsContextTag, {
value: string
}>(){
static layer = Layer.sync(this, () => {
return {
value: "MyService"
}
})
}

// the succeed variant returns the structure directly, without using an intermediate effect
export class MyServiceSucceed extends Effect.Service<MyServiceSucceed>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSucceed", {
succeed: {
value: "MyService"
}
}){}

// example result
export class MyServiceSucceedAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSucceedAsContextTag")<MyServiceSucceedAsContextTag, {
value: "MyService"
}>(){
static layer = Layer.succeed(this, {
value: "MyService"
})
}

// all variants support dependencies as well
export class MyServiceWithArgsScopedAndDependencies extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAndDependencies")<MyServiceWithArgsScopedAndDependencies, { value: string }>() {
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*() {
return {
value: arg
}
})).pipe(Layer.provide(Layer.mergeAll(MyServiceWithArgsScoped.Default("hello"))));
}

// example result
export class MyServiceWithArgsScopedAndDependenciesAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAndDependenciesAsContextTag")<MyServiceWithArgsScopedAndDependenciesAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*(){
return {
value: arg
}
})).pipe(
Layer.provide(Layer.mergeAll(MyServiceWithArgsScoped.Default("hello")))
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Result of running refactor effectServiceToClassWithLayer at position 25:20
import { Effect, Context } from "effect"
import * as Layer from "effect/Layer"

// this can be converted to a Context.Tag with a static layer property
export class MyService extends Effect.Service<MyService>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyService", {
effect: Effect.gen(function*() {
return {
value: "MyService"
}
})
}){}

// example result
export class MyServiceAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceAsContextTag")<MyServiceAsContextTag, {
value: string
}>(){
static layer = Layer.effect(this, Effect.gen(function*(){
return {
value: "MyService"
}
}))
}

export class MyServiceWithArgs extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgs")<MyServiceWithArgs, { value: string }>() {
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*() {
return {
value: arg
}
}));
}

// example result
export class MyServiceWithArgsAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsAsContextTag")<MyServiceWithArgsAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*(){
return {
value: arg
}
}))
}

// there is also a scoped variant, which should behave the same as the effect variant, but uses the scoped combinator.
export class MyServiceWithArgsScoped extends Effect.Service<MyServiceWithArgsScoped>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScoped", {
scoped: (arg: string) => Effect.gen(function*(){
return {
value: arg
}
})
}){}

export class MyServiceWithArgsScopedAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAsContextTag")<MyServiceWithArgsScopedAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.scoped(this, Effect.gen(function*(){
return {
value: arg
}
}))
}

// the sync variant returns the structure directly, without using an intermediate effect
export class MyServiceSync extends Effect.Service<MyServiceSync>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSync", {
sync: () => {
return {
value: "MyService"
}
}
}){}

// example result
export class MyServiceSyncAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSyncAsContextTag")<MyServiceSyncAsContextTag, {
value: string
}>(){
static layer = Layer.sync(this, () => {
return {
value: "MyService"
}
})
}

// the succeed variant returns the structure directly, without using an intermediate effect
export class MyServiceSucceed extends Effect.Service<MyServiceSucceed>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSucceed", {
succeed: {
value: "MyService"
}
}){}

// example result
export class MyServiceSucceedAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSucceedAsContextTag")<MyServiceSucceedAsContextTag, {
value: "MyService"
}>(){
static layer = Layer.succeed(this, {
value: "MyService"
})
}

// all variants support dependencies as well
export class MyServiceWithArgsScopedAndDependencies extends Effect.Service<MyServiceWithArgsScopedAndDependencies>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAndDependencies", {
effect: (arg: string) => Effect.gen(function*(){
return {
value: arg
}
}),
dependencies: [MyServiceWithArgsScoped.Default("hello")]
}){}

// example result
export class MyServiceWithArgsScopedAndDependenciesAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAndDependenciesAsContextTag")<MyServiceWithArgsScopedAndDependenciesAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*(){
return {
value: arg
}
})).pipe(
Layer.provide(Layer.mergeAll(MyServiceWithArgsScoped.Default("hello")))
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Result of running refactor effectServiceToClassWithLayer at position 45:20
import { Effect, Context } from "effect"
import * as Layer from "effect/Layer"

// this can be converted to a Context.Tag with a static layer property
export class MyService extends Effect.Service<MyService>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyService", {
effect: Effect.gen(function*() {
return {
value: "MyService"
}
})
}){}

// example result
export class MyServiceAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceAsContextTag")<MyServiceAsContextTag, {
value: string
}>(){
static layer = Layer.effect(this, Effect.gen(function*(){
return {
value: "MyService"
}
}))
}

export class MyServiceWithArgs extends Effect.Service<MyServiceWithArgs>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgs", {
effect: (arg: string) => Effect.gen(function*(){
return {
value: arg
}
})
}){}

// example result
export class MyServiceWithArgsAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsAsContextTag")<MyServiceWithArgsAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*(){
return {
value: arg
}
}))
}

// there is also a scoped variant, which should behave the same as the effect variant, but uses the scoped combinator.
export class MyServiceWithArgsScoped extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScoped")<MyServiceWithArgsScoped, { value: string }>() {
static layer = (arg: string) => Layer.scoped(this, Effect.gen(function*() {
return {
value: arg
}
}));
}

export class MyServiceWithArgsScopedAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAsContextTag")<MyServiceWithArgsScopedAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.scoped(this, Effect.gen(function*(){
return {
value: arg
}
}))
}

// the sync variant returns the structure directly, without using an intermediate effect
export class MyServiceSync extends Effect.Service<MyServiceSync>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSync", {
sync: () => {
return {
value: "MyService"
}
}
}){}

// example result
export class MyServiceSyncAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSyncAsContextTag")<MyServiceSyncAsContextTag, {
value: string
}>(){
static layer = Layer.sync(this, () => {
return {
value: "MyService"
}
})
}

// the succeed variant returns the structure directly, without using an intermediate effect
export class MyServiceSucceed extends Effect.Service<MyServiceSucceed>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSucceed", {
succeed: {
value: "MyService"
}
}){}

// example result
export class MyServiceSucceedAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceSucceedAsContextTag")<MyServiceSucceedAsContextTag, {
value: "MyService"
}>(){
static layer = Layer.succeed(this, {
value: "MyService"
})
}

// all variants support dependencies as well
export class MyServiceWithArgsScopedAndDependencies extends Effect.Service<MyServiceWithArgsScopedAndDependencies>()("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAndDependencies", {
effect: (arg: string) => Effect.gen(function*(){
return {
value: arg
}
}),
dependencies: [MyServiceWithArgsScoped.Default("hello")]
}){}

// example result
export class MyServiceWithArgsScopedAndDependenciesAsContextTag extends Context.Tag("@effect/harness-effect-v3/examples/refactors/effectServiceToClassWithLayer/MyServiceWithArgsScopedAndDependenciesAsContextTag")<MyServiceWithArgsScopedAndDependenciesAsContextTag, {
value: string
}>(){
static layer = (arg: string) => Layer.effect(this, Effect.gen(function*(){
return {
value: arg
}
})).pipe(
Layer.provide(Layer.mergeAll(MyServiceWithArgsScoped.Default("hello")))
)
}
Loading
Loading