Skip to content

Commit 7de2631

Browse files
TylorStim-smart
authored andcommitted
feat: Function Parameters for Effect.Service (#4911)
Co-authored-by: Tim <[email protected]>
1 parent e63dd01 commit 7de2631

File tree

3 files changed

+137
-12
lines changed

3 files changed

+137
-12
lines changed

.changeset/smooth-badgers-enter.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
"effect": minor
3+
---
4+
5+
Add parameter support for Effect.Service
6+
7+
This allows you to pass parameters to the `effect` & `scoped` Effect.Service
8+
constructors, which will also be reflected in the `.Default` layer.
9+
10+
```ts
11+
import type { Layer } from "effect"
12+
import { Effect } from "effect"
13+
14+
class NumberService extends Effect.Service<NumberService>()("NumberService", {
15+
// You can now pass a function to the `effect` and `scoped` constructors
16+
effect: Effect.fn(function* (input: number) {
17+
return {
18+
get: Effect.succeed(`The number is: ${input}`)
19+
} as const
20+
})
21+
}) {}
22+
23+
// Pass the arguments to the `Default` layer
24+
const CoolNumberServiceLayer: Layer.Layer<NumberService> =
25+
NumberService.Default(6942)
26+
```

packages/effect/src/Effect.ts

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13523,14 +13523,18 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
1352313523
const Key extends string,
1352413524
const Make extends
1352513525
| {
13526-
readonly scoped: Effect<Service.AllowedType<Key, Make>, any, any>
13526+
readonly scoped:
13527+
| Effect<Service.AllowedType<Key, Make>, any, any>
13528+
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
1352713529
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
1352813530
readonly accessors?: boolean
1352913531
/** @deprecated */
1353013532
readonly ಠ_ಠ: never
1353113533
}
1353213534
| {
13533-
readonly effect: Effect<Service.AllowedType<Key, Make>, any, any>
13535+
readonly effect:
13536+
| Effect<Service.AllowedType<Key, Make>, any, any>
13537+
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
1353413538
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
1353513539
readonly accessors?: boolean
1353613540
/** @deprecated */
@@ -13557,7 +13561,9 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
1355713561
<
1355813562
const Key extends string,
1355913563
const Make extends NoExcessProperties<{
13560-
readonly scoped: Effect<Service.AllowedType<Key, Make>, any, any>
13564+
readonly scoped:
13565+
| Effect<Service.AllowedType<Key, Make>, any, any>
13566+
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
1356113567
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
1356213568
readonly accessors?: boolean
1356313569
}, Make>
@@ -13568,7 +13574,9 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
1356813574
<
1356913575
const Key extends string,
1357013576
const Make extends NoExcessProperties<{
13571-
readonly effect: Effect<Service.AllowedType<Key, Make>, any, any>
13577+
readonly effect:
13578+
| Effect<Service.AllowedType<Key, Make>, any, any>
13579+
| ((...args: any) => Effect<Service.AllowedType<Key, Make>, any, any>)
1357213580
readonly dependencies?: ReadonlyArray<Layer.Layer.Any>
1357313581
readonly accessors?: boolean
1357413582
}, Make>
@@ -13651,15 +13659,28 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
1365113659
const hasDeps = "dependencies" in maker && maker.dependencies.length > 0
1365213660
const layerName = hasDeps ? "DefaultWithoutDependencies" : "Default"
1365313661
let layerCache: Layer.Layer.Any | undefined
13662+
let isFunction = false
1365413663
if ("effect" in maker) {
13664+
isFunction = typeof maker.effect === "function"
1365513665
Object.defineProperty(TagClass, layerName, {
1365613666
get(this: any) {
13667+
if (isFunction) {
13668+
return function(this: typeof TagClass) {
13669+
return layer.fromEffect(TagClass, map(maker.effect.apply(null, arguments), (_) => new this(_)))
13670+
}.bind(this)
13671+
}
1365713672
return layerCache ??= layer.fromEffect(TagClass, map(maker.effect, (_) => new this(_)))
1365813673
}
1365913674
})
1366013675
} else if ("scoped" in maker) {
13676+
isFunction = typeof maker.scoped === "function"
1366113677
Object.defineProperty(TagClass, layerName, {
1366213678
get(this: any) {
13679+
if (isFunction) {
13680+
return function(this: typeof TagClass) {
13681+
return layer.scoped(TagClass, map(maker.scoped.apply(null, arguments), (_) => new this(_)))
13682+
}.bind(this)
13683+
}
1366313684
return layerCache ??= layer.scoped(TagClass, map(maker.scoped, (_) => new this(_)))
1366413685
}
1366513686
})
@@ -13681,6 +13702,14 @@ export const Service: <Self = never>() => [Self] extends [never] ? MissingSelfGe
1368113702
let layerWithDepsCache: Layer.Layer.Any | undefined
1368213703
Object.defineProperty(TagClass, "Default", {
1368313704
get(this: any) {
13705+
if (isFunction) {
13706+
return function(this: typeof TagClass) {
13707+
return layer.provide(
13708+
this.DefaultWithoutDependencies.apply(null, arguments),
13709+
maker.dependencies
13710+
)
13711+
}
13712+
}
1368413713
return layerWithDepsCache ??= layer.provide(
1368513714
this.DefaultWithoutDependencies,
1368613715
maker.dependencies
@@ -13753,23 +13782,36 @@ export declare namespace Service {
1375313782
& { key: Key }
1375413783
& (MakeAccessors<Make> extends true ? Tag.Proxy<Self, MakeService<Make>> : {})
1375513784
& (MakeDeps<Make> extends never ? {
13756-
readonly Default: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
13785+
readonly Default: HasArguments<Make> extends true ?
13786+
(...args: MakeArguments<Make>) => Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
13787+
: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
1375713788
} :
1375813789
{
13759-
readonly DefaultWithoutDependencies: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
13760-
readonly Default: Layer.Layer<
13761-
Self,
13762-
MakeError<Make> | MakeDepsE<Make>,
13763-
| Exclude<MakeContext<Make>, MakeDepsOut<Make>>
13764-
| MakeDepsIn<Make>
13765-
>
13790+
readonly DefaultWithoutDependencies: HasArguments<Make> extends true
13791+
? (...args: MakeArguments<Make>) => Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
13792+
: Layer.Layer<Self, MakeError<Make>, MakeContext<Make>>
13793+
13794+
readonly Default: HasArguments<Make> extends true ? (...args: MakeArguments<Make>) => Layer.Layer<
13795+
Self,
13796+
MakeError<Make> | MakeDepsE<Make>,
13797+
| Exclude<MakeContext<Make>, MakeDepsOut<Make>>
13798+
| MakeDepsIn<Make>
13799+
> :
13800+
Layer.Layer<
13801+
Self,
13802+
MakeError<Make> | MakeDepsE<Make>,
13803+
| Exclude<MakeContext<Make>, MakeDepsOut<Make>>
13804+
| MakeDepsIn<Make>
13805+
>
1376613806
})
1376713807

1376813808
/**
1376913809
* @since 3.9.0
1377013810
*/
1377113811
export type MakeService<Make> = Make extends { readonly effect: Effect<infer _A, infer _E, infer _R> } ? _A
1377213812
: Make extends { readonly scoped: Effect<infer _A, infer _E, infer _R> } ? _A
13813+
: Make extends { readonly effect: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _A
13814+
: Make extends { readonly scoped: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _A
1377313815
: Make extends { readonly sync: LazyArg<infer A> } ? A
1377413816
: Make extends { readonly succeed: infer A } ? A
1377513817
: never
@@ -13779,13 +13821,18 @@ export declare namespace Service {
1377913821
*/
1378013822
export type MakeError<Make> = Make extends { readonly effect: Effect<infer _A, infer _E, infer _R> } ? _E
1378113823
: Make extends { readonly scoped: Effect<infer _A, infer _E, infer _R> } ? _E
13824+
: Make extends { readonly effect: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _E
13825+
: Make extends { readonly scoped: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _E
1378213826
: never
1378313827

1378413828
/**
1378513829
* @since 3.9.0
1378613830
*/
1378713831
export type MakeContext<Make> = Make extends { readonly effect: Effect<infer _A, infer _E, infer _R> } ? _R
1378813832
: Make extends { readonly scoped: Effect<infer _A, infer _E, infer _R> } ? Exclude<_R, Scope.Scope>
13833+
: Make extends { readonly effect: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ? _R
13834+
: Make extends { readonly scoped: (...args: infer _Args) => Effect<infer _A, infer _E, infer _R> } ?
13835+
Exclude<_R, Scope.Scope>
1378913836
: never
1379013837

1379113838
/**
@@ -13815,6 +13862,25 @@ export declare namespace Service {
1381513862
*/
1381613863
export type MakeAccessors<Make> = Make extends { readonly accessors: true } ? true
1381713864
: false
13865+
13866+
/**
13867+
* @since 3.16.0
13868+
*/
13869+
export type MakeArguments<Make> = Make extends
13870+
{ readonly effect: (...args: infer Args) => Effect<infer _A, infer _E, infer _R> } ? Args
13871+
: Make extends { readonly scoped: (...args: infer Args) => Effect<infer _A, infer _E, infer _R> } ? Args
13872+
: never
13873+
13874+
/**
13875+
* @since 3.16.0
13876+
*/
13877+
export type HasArguments<Make> = Make extends {
13878+
readonly scoped: (...args: ReadonlyArray<any>) => Effect<infer _A, infer _E, infer _R>
13879+
} ? true :
13880+
Make extends {
13881+
readonly effect: (...args: ReadonlyArray<any>) => Effect<infer _A, infer _E, infer _R>
13882+
} ? true :
13883+
false
1381813884
}
1381913885

1382013886
/**

packages/effect/test/Effect/service.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,37 @@ describe("Effect.Service", () => {
196196
const accessorSuccess = yield* Service.bar().pipe(Effect.provide(Service.Default))
197197
strictEqual(accessorSuccess, "bar")
198198
}))
199+
200+
it.effect("scoped with arguments", () =>
201+
Effect.gen(function*() {
202+
class Service extends Effect.Service<Service>()("Service", {
203+
accessors: true,
204+
scoped: Effect.fnUntraced(function*(x: number) {
205+
return { x }
206+
})
207+
}) {}
208+
209+
const x = yield* Service.x.pipe(Effect.provide(Service.Default(42)))
210+
strictEqual(x, 42)
211+
const x2 = yield* Service.x.pipe(Effect.provide(Service.Default(43)))
212+
strictEqual(x2, 43)
213+
}))
214+
215+
it.effect("scoped with arguments & deps", () =>
216+
Effect.gen(function*() {
217+
class Service extends Effect.Service<Service>()("Service", {
218+
accessors: true,
219+
dependencies: [Prefix.Default],
220+
scoped: Effect.fnUntraced(function*(x: number) {
221+
return { x }
222+
})
223+
}) {}
224+
225+
const x = yield* Service.x.pipe(Effect.provide(Service.Default(42)))
226+
strictEqual(x, 42)
227+
const x2 = yield* Service.x.pipe(Effect.provide(Service.DefaultWithoutDependencies(42)))
228+
strictEqual(x2, 42)
229+
const x3 = yield* Service.x.pipe(Effect.provide(Service.Default(43)))
230+
strictEqual(x3, 43)
231+
}))
199232
})

0 commit comments

Comments
 (0)