<const D extends DefaultsBag>(bag: DefaultsInput<D>): <
T extends PipeableTag
>(
tag: T
) => TagWithDefaults<T, D>
<T extends PipeableTag, const D extends DefaultsBag>(
tag: T,
bag: DefaultsInput<D>
): TagWithDefaults<T, D>Pipe multiple Tag-baked defaults onto a Tag — batteries on every local/client handle. Spec stays branded builders; this bag is the extras surface.
Singular fields belong in the contract via default (fully typed on Service).
Piped bag keys widen Service / yield* Tag at construction (via remapTagService)
so bag keys are typed without a use-site cast — same licensed-cast pattern as named
Gate / WorkPool handles. Same key as a Spec path (or a prior bag key) →
DuplicateDefaultKey at pipe time.
Layer/serve may override bag keys at the provide site only — overrides do not travel over the wire; clients always see the Tag-baked bag. Prefer Layer.updateService for post-hoc local patches.
class Jobs extends WorkPool.Tag<Jobs>()("@app/Jobs", jobSpec).pipe(
Hyperlink.defaults({
label: (n: number) => `job=${n}`,
tags: ["admin", "beta"],
}),
) {}
// equivalent factory sugar:
class Jobs2 extends WorkPool.Tag<Jobs2>()("@app/Jobs2", {
payload: JobSchema,
defaults: { label: (n: number) => `job=${n}` },
}) {}
const jobs = yield* Jobs
jobs.label(1) // typed on Service — no WithDefaults castexport const const defaults: {
<D extends DefaultsBag>(
bag: DefaultsInput<D>
): <T extends PipeableTag>(
tag: T
) => TagWithDefaults<T, D>
<T extends PipeableTag, D extends DefaultsBag>(
tag: T,
bag: DefaultsInput<D>
): TagWithDefaults<T, D>
}
Pipe multiple Tag-baked defaults onto a Tag — batteries on every local/client handle.
Spec stays branded builders; this bag is the extras surface.
Singular fields belong in the contract via
default
(fully typed on Service).
Piped bag keys widen Service / yield* Tag at construction (via
remapTagService
)
so bag keys are typed without a use-site cast — same licensed-cast pattern as named
Gate / WorkPool handles. Same key as a Spec path (or a prior bag key) →
DuplicateDefaultKey
at pipe time.
Layer/serve may override bag keys at the provide site only — overrides do not travel
over the wire; clients always see the Tag-baked bag. Prefer
Layer.updateService
for post-hoc local patches.
class Jobs extends WorkPool.Tag<Jobs>()("@app/Jobs", jobSpec).pipe(
Hyperlink.defaults({
label: (n: number) => `job=${n}`,
tags: ["admin", "beta"],
}),
) {}
// equivalent factory sugar:
class Jobs2 extends WorkPool.Tag<Jobs2>()("@app/Jobs2", {
payload: JobSchema,
defaults: { label: (n: number) => `job=${n}` },
}) {}
const jobs = yield* Jobs
jobs.label(1) // typed on Service — no WithDefaults cast
defaults: {
<const function (type parameter) D in <const D extends DefaultsBag>(bag: DefaultsInput<D>): <T extends PipeableTag>(tag: T) => TagWithDefaults<T, D>D extends type DefaultsBag = {
readonly [key: string]: unknown
}
A bag of Tag-baked defaults for
defaults
.
DefaultsBag>(
bag: DefaultsInput<D>bag: type DefaultsInput<
D extends DefaultsBag
> = { readonly [K in keyof D]: SyncDefault<D[K]> }
Input bag for
defaults
/ Tag { defaults } — sync values only (Promise-returning
fns are a type error).
DefaultsInput<function (type parameter) D in <const D extends DefaultsBag>(bag: DefaultsInput<D>): <T extends PipeableTag>(tag: T) => TagWithDefaults<T, D>D>,
): <function (type parameter) T in <T extends PipeableTag>(tag: T): TagWithDefaults<T, D>T extends type PipeableTag = {
readonly [specSym]: FlatSpec
}
PipeableTag>(tag: T extends PipeableTagtag: function (type parameter) T in <T extends PipeableTag>(tag: T): TagWithDefaults<T, D>T) => type TagWithDefaults<
T,
D extends DefaultsBag
> = T & {
readonly Service: ServiceOfTag<T> &
MergedDefaultsBag<T, D>
readonly [defaultsSym]: MergedDefaultsBag<T, D>
} & Effect.Effect<
ServiceOfTag<T> & PriorDefaults<T> & D,
never,
never
>
Tag after
defaults
(pipe or factory { defaults } sugar).
Keeps T (shallow
PipeableTag
pipe — no HyperlinkTag<Self,…> rebuild,
so class X extends Tag<X>().pipe(defaults) does not recurse on X). Widens
what yield* Tag /
Shape
see by intersecting a covariant
Effect<Svc & Bag> and a widened Service property. Soundness:
test/defaults-handle.test-d.ts.
TagWithDefaults<function (type parameter) T in <T extends PipeableTag>(tag: T): TagWithDefaults<T, D>T, function (type parameter) D in <const D extends DefaultsBag>(bag: DefaultsInput<D>): <T extends PipeableTag>(tag: T) => TagWithDefaults<T, D>D>;
<function (type parameter) T in <T extends PipeableTag, const D extends DefaultsBag>(tag: T, bag: DefaultsInput<D>): TagWithDefaults<T, D>T extends type PipeableTag = {
readonly [specSym]: FlatSpec
}
PipeableTag, const function (type parameter) D in <T extends PipeableTag, const D extends DefaultsBag>(tag: T, bag: DefaultsInput<D>): TagWithDefaults<T, D>D extends type DefaultsBag = {
readonly [key: string]: unknown
}
A bag of Tag-baked defaults for
defaults
.
DefaultsBag>(
tag: T extends PipeableTagtag: function (type parameter) T in <T extends PipeableTag, const D extends DefaultsBag>(tag: T, bag: DefaultsInput<D>): TagWithDefaults<T, D>T,
bag: DefaultsInput<D>bag: type DefaultsInput<
D extends DefaultsBag
> = { readonly [K in keyof D]: SyncDefault<D[K]> }
Input bag for
defaults
/ Tag { defaults } — sync values only (Promise-returning
fns are a type error).
DefaultsInput<function (type parameter) D in <T extends PipeableTag, const D extends DefaultsBag>(tag: T, bag: DefaultsInput<D>): TagWithDefaults<T, D>D>,
): type TagWithDefaults<
T,
D extends DefaultsBag
> = T & {
readonly Service: ServiceOfTag<T> &
MergedDefaultsBag<T, D>
readonly [defaultsSym]: MergedDefaultsBag<T, D>
} & Effect.Effect<
ServiceOfTag<T> & PriorDefaults<T> & D,
never,
never
>
Tag after
defaults
(pipe or factory { defaults } sugar).
Keeps T (shallow
PipeableTag
pipe — no HyperlinkTag<Self,…> rebuild,
so class X extends Tag<X>().pipe(defaults) does not recurse on X). Widens
what yield* Tag /
Shape
see by intersecting a covariant
Effect<Svc & Bag> and a widened Service property. Soundness:
test/defaults-handle.test-d.ts.
TagWithDefaults<function (type parameter) T in <T extends PipeableTag, const D extends DefaultsBag>(tag: T, bag: DefaultsInput<D>): TagWithDefaults<T, D>T, function (type parameter) D in <T extends PipeableTag, const D extends DefaultsBag>(tag: T, bag: DefaultsInput<D>): TagWithDefaults<T, D>D>;
} = import FnFn.const dual: <(...args: Array<any>) => any, <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D) => TagWithDefaults<T, D>>(arity: 2, body: <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D) => TagWithDefaults<T, D>) => ((...args: Array<any>) => any) & (<T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D) => TagWithDefaults<T, D>) (+1 overload)Creates a function that can be called in data-first style or data-last
(pipe-friendly) style.
When to use
Use to expose one implementation through both direct and pipe-friendly
call styles.
Details
Pass either the arity of the uncurried function or a predicate that decides
whether the current call is data-first. Arity is the common case. Use a
predicate when optional arguments make arity ambiguous.
Example (Selecting data-first or data-last style by arity)
import { Function, pipe } from "effect"
const sum = Function.dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(2, (self, that) => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5
Example (Defining overloads with call signatures)
import { Function, pipe } from "effect"
const sum: {
(that: number): (self: number) => number
(self: number, that: number): number
} = Function.dual(2, (self: number, that: number): number => self + that)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5
Example (Selecting data-first or data-last style with a predicate)
import { Function, pipe } from "effect"
const sum = Function.dual<
(that: number) => (self: number) => number,
(self: number, that: number) => number
>(
(args) => args.length === 2,
(self, that) => self + that
)
console.log(sum(2, 3)) // 5
console.log(pipe(2, sum(3))) // 5
dual(
2,
<function (type parameter) T in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>T extends type PipeableTag = {
readonly [specSym]: FlatSpec
}
PipeableTag, function (type parameter) D in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>D extends type DefaultsBag = {
readonly [key: string]: unknown
}
A bag of Tag-baked defaults for
defaults
.
DefaultsBag>(
tag: T extends PipeableTagtag: function (type parameter) T in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>T,
bag: D extends DefaultsBagbag: function (type parameter) D in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>D,
): type TagWithDefaults<
T,
D extends DefaultsBag
> = T & {
readonly Service: ServiceOfTag<T> &
MergedDefaultsBag<T, D>
readonly [defaultsSym]: MergedDefaultsBag<T, D>
} & Effect.Effect<
ServiceOfTag<T> & PriorDefaults<T> & D,
never,
never
>
Tag after
defaults
(pipe or factory { defaults } sugar).
Keeps T (shallow
PipeableTag
pipe — no HyperlinkTag<Self,…> rebuild,
so class X extends Tag<X>().pipe(defaults) does not recurse on X). Widens
what yield* Tag /
Shape
see by intersecting a covariant
Effect<Svc & Bag> and a widened Service property. Soundness:
test/defaults-handle.test-d.ts.
TagWithDefaults<function (type parameter) T in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>T, function (type parameter) D in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>D> => {
const const flat: FlatSpecflat = tag: T extends PipeableTagtag[const specSym: typeof specSymWhere the contract spec is stowed on a Tag (hidden from the value surface). Exported so
the public
HyperlinkTag
type is nameable across modules.
specSym];
for (const const key: stringkey of var Object: ObjectConstructorProvides functionality common to all JavaScript objects.
Object.ObjectConstructor.keys(o: {}): string[] (+1 overload)Returns the names of the enumerable string properties and methods of an object.
keys(bag: D extends DefaultsBagbag)) {
if (const defaultsCollidesWithSpec: (
flat: FlatSpec,
key: string
) => boolean
True when a defaults bag key collides with a flat Spec path.
defaultsCollidesWithSpec(const flat: FlatSpecflat, const key: stringkey)) {
throw new constructor DuplicateDefaultKey<{
readonly key: string;
}>(args: {
readonly key: string;
}): DuplicateDefaultKey
A
defaults
bag key collides with a Spec path (or another defaults key).
DuplicateDefaultKey({ key: stringkey });
}
}
const const prior: DefaultsBagprior =
const defaultsSym: typeof defaultsSymWhere piped
defaults
are stowed on a Tag — merged onto the service at local/client
acquire (overridable via matching impl keys /
Layer.updateService
).
defaultsSym in tag: T extends PipeableTagtag
? ((tag: T & Record<typeof defaultsSym, unknown>tag as { readonly [const defaultsSym: typeof defaultsSymWhere piped
defaults
are stowed on a Tag — merged onto the service at local/client
acquire (overridable via matching impl keys /
Layer.updateService
).
defaultsSym]?: type DefaultsBag = {
readonly [key: string]: unknown
}
A bag of Tag-baked defaults for
defaults
.
DefaultsBag })[const defaultsSym: typeof defaultsSymWhere piped
defaults
are stowed on a Tag — merged onto the service at local/client
acquire (overridable via matching impl keys /
Layer.updateService
).
defaultsSym] ?? {})
: {};
for (const const key: stringkey of var Object: ObjectConstructorProvides functionality common to all JavaScript objects.
Object.ObjectConstructor.keys(o: {}): string[] (+1 overload)Returns the names of the enumerable string properties and methods of an object.
keys(bag: D extends DefaultsBagbag)) {
if (var Object: ObjectConstructorProvides functionality common to all JavaScript objects.
Object.ObjectConstructor.hasOwn(o: object, v: PropertyKey): booleanDetermines whether an object has a property with the specified name.
hasOwn(const prior: DefaultsBagprior, const key: stringkey)) {
throw new constructor DuplicateDefaultKey<{
readonly key: string;
}>(args: {
readonly key: string;
}): DuplicateDefaultKey
A
defaults
bag key collides with a Spec path (or another defaults key).
DuplicateDefaultKey({ key: stringkey });
}
}
// Soundness: test/defaults-handle.test-d.ts (Svc ⇄ Svc & bag; toolkit keeps named handle).
return const remapTagService: <TagWithDefaults<T, D>>(tag: {
readonly [specSym]: FlatSpec;
}) => TagWithDefaults<T, D>
Remap a HyperService Tag's handle (Svc) — the licensed cast for construction
adornments (
defaults
, …). Runtime identity is unchanged; only the
type-level claim on
Result
changes.
Generics + class-Self + invariant Shape block a proved remap, so this is
one as unknown as (same pattern as Gate nameRunService / WorkPool
nameQueueService). Caller owes a bidirectional assignability guard in a
.test-d.ts for the Result claim — cite that file at each call site.
remapTagService<type TagWithDefaults<
T,
D extends DefaultsBag
> = T & {
readonly Service: ServiceOfTag<T> &
MergedDefaultsBag<T, D>
readonly [defaultsSym]: MergedDefaultsBag<T, D>
} & Effect.Effect<
ServiceOfTag<T> & PriorDefaults<T> & D,
never,
never
>
Tag after
defaults
(pipe or factory { defaults } sugar).
Keeps T (shallow
PipeableTag
pipe — no HyperlinkTag<Self,…> rebuild,
so class X extends Tag<X>().pipe(defaults) does not recurse on X). Widens
what yield* Tag /
Shape
see by intersecting a covariant
Effect<Svc & Bag> and a widened Service property. Soundness:
test/defaults-handle.test-d.ts.
TagWithDefaults<function (type parameter) T in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>T, function (type parameter) D in <T extends PipeableTag, D extends DefaultsBag>(tag: T, bag: D): TagWithDefaults<T, D>D>>(
var Object: ObjectConstructorProvides functionality common to all JavaScript objects.
Object.ObjectConstructor.assign<T, {
[defaultsSym]: {
[key: string]: unknown;
} & D;
}>(target: T, source: {
[defaultsSym]: {
[key: string]: unknown;
} & D;
}): T & {
[defaultsSym]: {
[key: string]: unknown;
} & D;
} (+3 overloads)
Copy the values of all of the enumerable own properties from one or more source objects to a
target object. Returns the target object.
assign(tag: T extends PipeableTagtag, {
[const defaultsSym: typeof defaultsSymWhere piped
defaults
are stowed on a Tag — merged onto the service at local/client
acquire (overridable via matching impl keys /
Layer.updateService
).
defaultsSym]: { ...const prior: DefaultsBagprior, ...bag: D extends DefaultsBagbag },
}),
);
},
);