diff --git a/.changeset/dry-cycles-shake.md b/.changeset/dry-cycles-shake.md new file mode 100644 index 00000000000..76b43bcee6a --- /dev/null +++ b/.changeset/dry-cycles-shake.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Add type-level utils to asserting layer types diff --git a/packages/effect/dtslint/Layer.tst.ts b/packages/effect/dtslint/Layer.tst.ts index 40168a49908..909927bcae5 100644 --- a/packages/effect/dtslint/Layer.tst.ts +++ b/packages/effect/dtslint/Layer.tst.ts @@ -1,4 +1,4 @@ -import { Layer, Schedule } from "effect" +import { Context, Layer, Schedule } from "effect" import { describe, expect, it } from "tstyche" interface In1 {} @@ -19,6 +19,8 @@ interface Out3 {} declare const layer3: Layer.Layer +class TestService1 extends Context.Tag("TestService1")() {} + describe("Layer", () => { it("merge", () => { expect(Layer.merge).type.not.toBeCallableWith() @@ -40,4 +42,28 @@ describe("Layer", () => { expect(Layer.retry(layer1, Schedule.recurs(1))).type.toBe>() expect(layer1.pipe(Layer.retry(Schedule.recurs(1)))).type.toBe>() }) + + it("ensureSuccessType", () => { + expect(layer1.pipe(Layer.ensureSuccessType())).type.toBe>() + }) + + it("ensureErrorType", () => { + const withoutError = Layer.succeed(TestService1, {}) + expect(withoutError.pipe(Layer.ensureErrorType())).type.toBe>() + + const withError = layer1 + expect(withError.pipe(Layer.ensureErrorType())).type.toBe>() + }) + + it("ensureRequirementsType", () => { + const withoutRequirements = Layer.succeed(TestService1, {}) + expect(withoutRequirements.pipe(Layer.ensureRequirementsType())).type.toBe< + Layer.Layer + >() + + const withRequirement = layer1 + expect(withRequirement.pipe(Layer.ensureRequirementsType())).type.toBe< + Layer.Layer + >() + }) }) diff --git a/packages/effect/src/Layer.ts b/packages/effect/src/Layer.ts index 82aa92f6fa6..35627c5a235 100644 --- a/packages/effect/src/Layer.ts +++ b/packages/effect/src/Layer.ts @@ -1226,3 +1226,55 @@ export const updateService = dual< layer, map(context(), (c) => Context.add(c, tag, f(Context.unsafeGet(c, tag)))) )) + +// ----------------------------------------------------------------------------- +// Type constraints +// ----------------------------------------------------------------------------- + +/** + * A no-op type constraint that enforces the success channel of a Layer conforms to + * the specified success type `ROut`. + * + * @example + * import { Layer } from "effect" + * + * // Ensure that the layer produces the expected services. + * const program = Layer.succeed(MyService, new MyServiceImpl()).pipe(Layer.ensureSuccessType()) + * + * @since 3.20.0 + * @category Type constraints + */ +export const ensureSuccessType = + () => (layer: Layer): Layer => layer + +/** + * A no-op type constraint that enforces the error channel of a Layer conforms to + * the specified error type `E`. + * + * @example + * import { Layer } from "effect" + * + * // Ensure that the layer does not expose any unhandled errors. + * const program = Layer.succeed(MyService, new MyServiceImpl()).pipe(Layer.ensureErrorType()) + * + * @since 3.20.0 + * @category Type constraints + */ +export const ensureErrorType = () => (layer: Layer): Layer => + layer + +/** + * A no-op type constraint that enforces the requirements channel of a Layer conforms to + * the specified requirements type `RIn`. + * + * @example + * import { Layer } from "effect" + * + * // Ensure that the layer does not have any requirements. + * const program = Layer.succeed(MyService, new MyServiceImpl()).pipe(Layer.ensureRequirementsType()) + * + * @since 3.20.0 + * @category Type constraints + */ +export const ensureRequirementsType = + () => (layer: Layer): Layer => layer