diff --git a/.changeset/rare-eagles-jog.md b/.changeset/rare-eagles-jog.md new file mode 100644 index 0000000000..94844ba785 --- /dev/null +++ b/.changeset/rare-eagles-jog.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Add Context.mergeAll to combine multiple Contexts into one. diff --git a/packages/effect/src/Context.ts b/packages/effect/src/Context.ts index 2373b44abf..d684b3e8dd 100644 --- a/packages/effect/src/Context.ts +++ b/packages/effect/src/Context.ts @@ -428,6 +428,36 @@ export const merge: { (self: Context, that: Context): Context } = internal.merge +/** + * Merges any number of `Context`s, returning a new `Context` containing the services of all. + * + * @param ctxs - The `Context`s to merge. + * + * @example + * ```ts + * import { Context } from "effect" + * + * const Port = Context.GenericTag<{ PORT: number }>("Port") + * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") + * const Host = Context.GenericTag<{ HOST: string }>("Host") + * + * const firstContext = Context.make(Port, { PORT: 8080 }) + * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 }) + * const thirdContext = Context.make(Host, { HOST: "localhost" }) + * + * const Services = Context.mergeAll(firstContext, secondContext, thirdContext) + * + * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) + * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) + * assert.deepStrictEqual(Context.get(Services, Host), { HOST: "localhost" }) + * ``` + * + * @since 3.12.0 + */ +export const mergeAll: >( + ...ctxs: [...{ [K in keyof T]: Context }] +) => Context = internal.mergeAll + /** * Returns a new `Context` that contains only the specified services. * diff --git a/packages/effect/src/internal/context.ts b/packages/effect/src/internal/context.ts index 26f10d2326..20e0badd04 100644 --- a/packages/effect/src/internal/context.ts +++ b/packages/effect/src/internal/context.ts @@ -295,6 +295,19 @@ export const merge = dual< return makeContext(map) }) +/** @internal */ +export const mergeAll = >( + ...ctxs: [...{ [K in keyof T]: C.Context }] +): C.Context => { + const map = new Map() + for (const ctx of ctxs) { + for (const [tag, s] of ctx.unsafeMap) { + map.set(tag, s) + } + } + return makeContext(map) +} + /** @internal */ export const pick = >>(...tags: S) => diff --git a/packages/effect/test/Context.test.ts b/packages/effect/test/Context.test.ts index 9475881cd3..4641921879 100644 --- a/packages/effect/test/Context.test.ts +++ b/packages/effect/test/Context.test.ts @@ -256,6 +256,39 @@ describe("Context", () => { expect(result.pipe(Context.get(A))).toEqual({ a: 0 }) }) + it("mergeAll", () => { + const env = Context.mergeAll( + Context.make(A, { a: 0 }), + Context.make(B, { b: 1 }), + Context.make(C, { c: 2 }) + ) + + const pruned = pipe( + env, + Context.pick(A, B) + ) + + expect(pipe( + pruned, + Context.get(A) + )).toEqual({ a: 0 }) + + expect(pipe( + pruned, + Context.getOption(B) + )).toEqual(O.some({ b: 1 })) + + expect(pipe( + pruned, + Context.getOption(C) + )).toEqual(O.none()) + + expect(pipe( + env, + Context.getOption(C) + )).toEqual(O.some({ c: 2 })) + }) + it("isContext", () => { expect(Context.isContext(Context.empty())).toEqual(true) expect(Context.isContext(null)).toEqual(false)