<Self, I>(): <const C extends Spec>(
key: string,
contract: C & Validate<C, I>,
options?: { readonly description?: string; readonly kind?: string }
) => HyperlinkTag<Self, ResolveLocals<C, I>>Build a resource tag from an existing service interface as the single source of truth. The
type parameter I is the interface; the contract gives a schema only for the members you want on
the wire — every other interface member becomes a local (surfaced via InjectLocal,
carrying Local<I>). One merged handle, identical whether you hold the local layer or a client;
the only difference is that a client can't call the locals (a compile error — unsatisfied
Local).
Locals are written bare — Hyperlink.local, no () — and take their type from I. A bare
local with no matching interface member is rejected at the call (see Validate).
Two type parameters, like Tag: Self (the class — the tag's nominal identity, Local
brand) and I (the service interface — a standalone type; passing the class itself as I
would be a circular base reference).
interface CounterShape {
readonly current: Effect.Effect<number>; // local (no schema)
readonly add: (by: number) => Effect.Effect<number>; // wired
}
class Counter extends Hyperlink.fromService<Counter, CounterShape>()("counter", {
current: Hyperlink.local,
add: Hyperlink.effectFn(Schema.Number, Schema.Number),
}) {}export const const fromService: <Self, I>() => <
C extends Spec
>(
key: string,
contract: C & Validate<C, I>,
options?: {
readonly description?: string
readonly kind?: string
}
) => HyperlinkTag<Self, ResolveLocals<C, I>>
Build a resource tag from an existing service interface as the single source of truth. The
type parameter I is the interface; the contract gives a schema only for the members you want on
the wire — every other interface member becomes a local (surfaced via
InjectLocal
,
carrying Local<I>). One merged handle, identical whether you hold the local layer or a client;
the only difference is that a client can't call the locals (a compile error — unsatisfied
Local).
Locals are written bare — Hyperlink.local, no () — and take their type from I. A bare
local with no matching interface member is rejected at the call (see
Validate
).
Two type parameters, like
Tag
: Self (the class — the tag's nominal identity, Local
brand) and I (the service interface — a standalone type; passing the class itself as I
would be a circular base reference).
interface CounterShape {
readonly current: Effect.Effect<number>; // local (no schema)
readonly add: (by: number) => Effect.Effect<number>; // wired
}
class Counter extends Hyperlink.fromService<Counter, CounterShape>()("counter", {
current: Hyperlink.local,
add: Hyperlink.effectFn(Schema.Number, Schema.Number),
}) {}
fromService = <function (type parameter) Self in <Self, I>(): <const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}) => HyperlinkTag<Self, ResolveLocals<C, I>>
Self, function (type parameter) I in <Self, I>(): <const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}) => HyperlinkTag<Self, ResolveLocals<C, I>>
I>() => {
function function (local function) build<C extends Spec>(key: string, contract: C & Validate<C, I>, options?: { readonly description?: string; readonly kind?: string }): HyperlinkTag<Self, ResolveLocals<C, I>>build<const function (type parameter) C in build<const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}): HyperlinkTag<Self, ResolveLocals<C, I>>
C extends Spec>(
key: stringkey: string,
contract: C & Validate<C, I>contract: function (type parameter) C in build<const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}): HyperlinkTag<Self, ResolveLocals<C, I>>
C & type Validate<C, I> = {
readonly [K in keyof C]: C[K] extends BareLocal
? K extends keyof I
? C[K]
: LocalNeedsType<K>
: C[K] extends Spec
? K extends keyof I
? Validate<C[K], I[K]>
: LocalNeedsType<K>
: C[K] extends {
readonly kind: MethodKind
}
? K extends keyof I
? WireHonors<
SuccessOf<AsMethod<C[K]>>,
IfaceSuccess<I[K]>
> extends true
? C[K]
: WireMismatch<K>
: C[K]
: C[K]
}
Validate a
fromService
contract C against its service interface I: a bare
local
at a key absent from I (or with no I at all) becomes a
LocalNeedsType
error the user's value can't satisfy, so the contract argument is rejected at the call site.
Every other entry (a wired method, an explicit local<T>(), a nested group) passes through.
Validate<function (type parameter) C in build<const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}): HyperlinkTag<Self, ResolveLocals<C, I>>
C, function (type parameter) I in <Self, I>(): <const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}) => HyperlinkTag<Self, ResolveLocals<C, I>>
I>,
options: | {
readonly description?: string
readonly kind?: string
}
| undefined
options?: { readonly description?: string | undefineddescription?: string; readonly kind?: string | undefinedkind?: string },
): interface HyperlinkTag<Self, S extends Spec, Svc = Simplify<{ readonly [K in keyof S]: S[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : S[K] extends LocalMethod<infer T> ? LocalEffect<...> : S[K] extends { ...; } ? SuccessOf<...> : S[K] extends { ...; } ? Subscribable<...> : S[K] extends { ...; } ? ClientMethod<...> : S[K] extends Spec ? Simplify<...> : never; }>>The type of a resource tag carrying spec S — what
Hyperlink.Tag
/ a
Hyperlink.tagFor
factory produce (and what you extend). Lets a consumer write
<S extends Spec>(tag: HyperlinkTag<Self, S>) and read the spec through named types
(
specOf
/
groupOf
) instead of a Parameters<typeof specOf> workaround.
HyperlinkTag<function (type parameter) Self in <Self, I>(): <const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}) => HyperlinkTag<Self, ResolveLocals<C, I>>
Self, type ResolveLocals<C, I> = {
readonly [K in keyof C]: C[K] extends BareLocal
? FromLocalMethod<
K extends keyof I ? I[K] : unknown
>
: C[K] extends Spec
? K extends keyof I
? ResolveLocals<C[K], I[K]>
: C[K]
: C[K]
}
Resolve a
fromService
contract C into a runnable
Spec
: each bare
local
becomes a
FromLocalMethod
carrying the service interface's type at that key, so the impl
(
ImplOf
) and service (
ServiceOf
) both derive from I. Wired methods and explicit
local<T>()s pass through unchanged.
ResolveLocals<function (type parameter) C in build<const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}): HyperlinkTag<Self, ResolveLocals<C, I>>
C, function (type parameter) I in <Self, I>(): <const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}) => HyperlinkTag<Self, ResolveLocals<C, I>>
I>> {
// single resource: key doubles as the group id (its wire prefix). The contract *value* is the
// runtime spec (bare `local`s carry the LocalMethod brand); `S` is presented resolved at the type
// level so `ImplOf`/`FromServiceOf` derive local types from the interface `I`.
const claimGroupId: (
groupId: string
) => void
Reserve a group id (wire prefix); a duplicate throws — two resources can't share a prefix.
claimGroupId(key: stringkey);
return const buildInstanceTag: <Self, ResolveLocals<C, I>>(groupId: string, key: string, spec: Spec, group: RpcGroupOf<ResolveLocals<C, I>>, description: string | undefined, node: NodeKey<unknown> | undefined, kindOverride: string | undefined, fromServiceMarker?: boolean) => Context.ServiceClass<Self, string, Simplify<{ readonly [K in keyof ResolveLocals<C, I>]: ResolveLocals<C, I>[K] extends FromLocalMethod<infer M> ? InjectLocal<M, Self> : ResolveLocals<C, I>[K] extends LocalMethod<...> ? LocalEffect<...> : ResolveLocals<...>[K] extends {
...;
} ? SuccessOf<...> : ResolveLocals<...>[K] extends {
...;
} ? Subscribable<...> : ResolveLocals<...>[K] extends {
...;
} ? ClientMethod<...> : ResolveLocals<...>[K] extends Spec ? Simplify<...> : never; }>> & {
...;
}
The single tag-creation primitive: dup-key guard + Context.Service + stow groupId/spec/group.
Both
makeTag
(per-tag spec) and
tagFor
(shared spec) go through it. key is the
instance identity (Context key + routing header); groupId is the wire prefix.
buildInstanceTag<function (type parameter) Self in <Self, I>(): <const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}) => HyperlinkTag<Self, ResolveLocals<C, I>>
Self, type ResolveLocals<C, I> = {
readonly [K in keyof C]: C[K] extends BareLocal
? FromLocalMethod<
K extends keyof I ? I[K] : unknown
>
: C[K] extends Spec
? K extends keyof I
? ResolveLocals<C[K], I[K]>
: C[K]
: C[K]
}
Resolve a
fromService
contract C into a runnable
Spec
: each bare
local
becomes a
FromLocalMethod
carrying the service interface's type at that key, so the impl
(
ImplOf
) and service (
ServiceOf
) both derive from I. Wired methods and explicit
local<T>()s pass through unchanged.
ResolveLocals<function (type parameter) C in build<const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}): HyperlinkTag<Self, ResolveLocals<C, I>>
C, function (type parameter) I in <Self, I>(): <const C extends Spec>(key: string, contract: C & Validate<C, I>, options?: {
readonly description?: string;
readonly kind?: string;
}) => HyperlinkTag<Self, ResolveLocals<C, I>>
I>>(
key: stringkey,
key: stringkey,
contract: C & Validate<C, I>contract,
const buildRpcGroup: (
groupId: string,
spec: FlatSpec
) => RpcGroup.RpcGroup<any>
Build the shared RPC contract group from a
Spec
, namespaced by groupId. A bare
Schema becomes a payload-free rpc returning that schema; a descriptor maps straight to
its parts. Every procedure's wire tag is
wireTag
-prefixed by groupId.
buildRpcGroup(key: stringkey, const flattenSpec: (
spec: Spec,
prefix?: string
) => FlatSpec
Flatten a nested spec to a flat path-keyed record (identity for a flat spec).
flattenSpec(contract: C & Validate<C, I>contract)),
options: | {
readonly description?: string
readonly kind?: string
}
| undefined
options?.description?: string | undefineddescription,
var undefinedundefined,
options: | {
readonly description?: string
readonly kind?: string
}
| undefined
options?.kind?: string | undefinedkind,
true,
);
}
return function (local function) build<C extends Spec>(key: string, contract: C & Validate<C, I>, options?: { readonly description?: string; readonly kind?: string }): HyperlinkTag<Self, ResolveLocals<C, I>>build;
};