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
26 changes: 26 additions & 0 deletions .changeset/smooth-badgers-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"effect": minor
---

Add parameter support for Effect.Service

This allows you to pass parameters to the `effect` & `scoped` Effect.Service
constructors, which will also be reflected in the `.Default` layer.

```ts
import type { Layer } from "effect"
import { Effect } from "effect"

class NumberService extends Effect.Service<NumberService>()("NumberService", {
// You can now pass a function to the `effect` and `scoped` constructors
effect: Effect.fn(function* (input: number) {
return {
get: Effect.succeed(`The number is: ${input}`)
} as const
})
}) {}

// Pass the arguments to the `Default` layer
const CoolNumberServiceLayer: Layer.Layer<NumberService> =
NumberService.Default(6942)
```
90 changes: 78 additions & 12 deletions packages/effect/src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13523,14 +13523,18 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
const Key extends string,
const Make extends
| {
readonly scoped: Effect<Service.AllowedType<Key, Make>, any, any>
readonly scoped:
| Effect<Service.AllowedType<Key, Make>, any, any>
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
readonly accessors?: boolean
/** @deprecated */
readonly ಠ_ಠ: never
}
| {
readonly effect: Effect<Service.AllowedType<Key, Make>, any, any>
readonly effect:
| Effect<Service.AllowedType<Key, Make>, any, any>
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
readonly accessors?: boolean
/** @deprecated */
Expand All @@ -13557,7 +13561,9 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
<
const Key extends string,
const Make extends NoExcessProperties<{
readonly scoped: Effect<Service.AllowedType<Key, Make>, any, any>
readonly scoped:
| Effect<Service.AllowedType<Key, Make>, any, any>
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
readonly accessors?: boolean
}, Make>
Expand All @@ -13568,7 +13574,9 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
<
const Key extends string,
const Make extends NoExcessProperties<{
readonly effect: Effect<Service.AllowedType<Key, Make>, any, any>
readonly effect:
| Effect<Service.AllowedType<Key, Make>, any, any>
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
readonly accessors?: boolean
}, Make>
Expand Down Expand Up @@ -13651,15 +13659,28 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
const hasDeps = "dependencies" in maker && maker.dependencies.length > 0
const layerName = hasDeps ? "DefaultWithoutDependencies" : "Default"
let layerCache: Layer.Layer.Any | undefined
let isFunction = false
if ("effect" in maker) {
isFunction = typeof maker.effect === "function"
Object.defineProperty(TagClass, layerName, {
get(this: any) {
if (isFunction) {
return function(this: typeof TagClass) {
return layer.fromEffect(TagClass, map(maker.effect.apply(null, arguments), (_) => new this(_)))
}.bind(this)
}
return layerCache ??= layer.fromEffect(TagClass, map(maker.effect, (_) => new this(_)))
}
})
} else if ("scoped" in maker) {
isFunction = typeof maker.scoped === "function"
Object.defineProperty(TagClass, layerName, {
get(this: any) {
if (isFunction) {
return function(this: typeof TagClass) {
return layer.scoped(TagClass, map(maker.scoped.apply(null, arguments), (_) => new this(_)))
}.bind(this)
}
return layerCache ??= layer.scoped(TagClass, map(maker.scoped, (_) => new this(_)))
}
})
Expand All @@ -13681,6 +13702,14 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
let layerWithDepsCache: Layer.Layer.Any | undefined
Object.defineProperty(TagClass, "Default", {
get(this: any) {
if (isFunction) {
return function(this: typeof TagClass) {
return layer.provide(
this.DefaultWithoutDependencies.apply(null, arguments),
maker.dependencies
)
}
}
return layerWithDepsCache ??= layer.provide(
this.DefaultWithoutDependencies,
maker.dependencies
Expand Down Expand Up @@ -13753,23 +13782,36 @@ export declare namespace Service {
& { key: Key }
& (MakeAccessors<Make> extends true ? Tag.Proxy<Self, MakeService<Make>> : {})
& (MakeDeps<Make> extends never ? {
readonly Default: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
readonly Default: HasArguments<Make> extends true ?
(...args: MakeArguments<Make>) => Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
} :
{
readonly DefaultWithoutDependencies: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
readonly Default: Layer.Layer<
Self,
MakeError<Make> | MakeDepsE<Make>,
| Exclude<MakeContext<Make>, MakeDepsOut<Make>>
| MakeDepsIn<Make>
>
readonly DefaultWithoutDependencies: HasArguments<Make> extends true
? (...args: MakeArguments<Make>) => Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>

readonly Default: HasArguments<Make> extends true ? (...args: MakeArguments<Make>) => Layer.Layer<
Self,
MakeError<Make> | MakeDepsE<Make>,
| Exclude<MakeContext<Make>, MakeDepsOut<Make>>
| MakeDepsIn<Make>
> :
Layer.Layer<
Self,
MakeError<Make> | MakeDepsE<Make>,
| Exclude<MakeContext<Make>, MakeDepsOut<Make>>
| MakeDepsIn<Make>
>
})

/**
* @since 3.9.0
*/
export type MakeService<Make> = Make extends { readonly effect: Effect<infer _A, infer _E, infer _R> } ? _A
: Make extends { readonly scoped: Effect<infer _A, infer _E, infer _R> } ? _A
: Make extends { readonly effect: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _A
: Make extends { readonly scoped: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _A
: Make extends { readonly sync: LazyArg<infer A> } ? A
: Make extends { readonly succeed: infer A } ? A
: never
Expand All @@ -13779,13 +13821,18 @@ export declare namespace Service {
*/
export type MakeError<Make> = Make extends { readonly effect: Effect<infer _A, infer _E, infer _R> } ? _E
: Make extends { readonly scoped: Effect<infer _A, infer _E, infer _R> } ? _E
: Make extends { readonly effect: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _E
: Make extends { readonly scoped: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _E
: never

/**
* @since 3.9.0
*/
export type MakeContext<Make> = Make extends { readonly effect: Effect<infer _A, infer _E, infer _R> } ? _R
: Make extends { readonly scoped: Effect<infer _A, infer _E, infer _R> } ? Exclude<_R, Scope.Scope>
: Make extends { readonly effect: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _R
: Make extends { readonly scoped: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ?
Exclude<_R, Scope.Scope>
: never

/**
Expand Down Expand Up @@ -13815,6 +13862,25 @@ export declare namespace Service {
*/
export type MakeAccessors<Make> = Make extends { readonly accessors: true } ? true
: false

/**
* @since 3.16.0
*/
export type MakeArguments<Make> = Make extends
{ readonly effect: (...args: infer Args) => Effect<infer _A, infer _E, infer _R> } ? Args
: Make extends { readonly scoped: (...args: infer Args) => Effect<infer _A, infer _E, infer _R> } ? Args
: never

/**
* @since 3.16.0
*/
export type HasArguments<Make> = Make extends {
readonly scoped: (...args: ReadonlyArray<any>) => Effect<infer _A, infer _E, infer _R>
} ? true :
Make extends {
readonly effect: (...args: ReadonlyArray<any>) => Effect<infer _A, infer _E, infer _R>
} ? true :
false
}

/**
Expand Down
33 changes: 33 additions & 0 deletions packages/effect/test/Effect/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,37 @@ describe("Effect.Service", () => {
const accessorSuccess = yield* Service.bar().pipe(Effect.provide(Service.Default))
strictEqual(accessorSuccess, "bar")
}))

it.effect("scoped with arguments", () =>
Effect.gen(function*() {
class Service extends Effect.Service<Service>()("Service", {
accessors: true,
scoped: Effect.fnUntraced(function*(x: number) {
return { x }
})
}) {}

const x = yield* Service.x.pipe(Effect.provide(Service.Default(42)))
strictEqual(x, 42)
const x2 = yield* Service.x.pipe(Effect.provide(Service.Default(43)))
strictEqual(x2, 43)
}))

it.effect("scoped with arguments & deps", () =>
Effect.gen(function*() {
class Service extends Effect.Service<Service>()("Service", {
accessors: true,
dependencies: [Prefix.Default],
scoped: Effect.fnUntraced(function*(x: number) {
return { x }
})
}) {}

const x = yield* Service.x.pipe(Effect.provide(Service.Default(42)))
strictEqual(x, 42)
const x2 = yield* Service.x.pipe(Effect.provide(Service.DefaultWithoutDependencies(42)))
strictEqual(x2, 42)
const x3 = yield* Service.x.pipe(Effect.provide(Service.Default(43)))
strictEqual(x3, 43)
}))
})