# @fnioc/core > **Private, unpublished package.** `@fnioc/core` is never published to npm. Its > source is **inlined** into `@fnioc/di` and `@fnioc/transformer` at their build > time — they ship self-contained JS and `.d.ts` with zero `@fnioc/core` > references. Install `@fnioc/di` (and, for the compile-time plugin, > `@fnioc/transformer`); you never depend on `@fnioc/core` directly. The runtime > authoring surfaces below are re-exported from `@fnioc/di`. The substrate for `ioc`. It exports nothing that touches resolution or compilation — only the immutable dependency-signature types (`DepSlot` and its variants), the open-generic token grammar, and the plain data-constructor helpers (`union`, `typeArg`) a hand-authored signature is built from. There is no metadata store here, or anywhere else: a constructor/factory's dependency signature rides on the registration itself — see [`@fnioc/di`](../di/README.md) for where it's read. --- ## `Token` A plain `string`. The DI key identifying an interface. No branding, no literal types. Generated by the transformer at build time; passed explicitly in plugin-less usage. ```ts export type Token = string; ``` --- ## `FactoryRef` Marks a constructor parameter to be injected as a factory rather than a resolved instance. ```ts export interface FactoryRef { readonly type: Token; readonly params?: readonly Token[]; } ``` `type` is the token of the produced type T. `params` is the complete, authored-order list of caller-supplied parameter tokens — passing it pins the factory's shape so it no longer drifts as registration state changes. Omit `params` to get a strict zero-arg `() => T` where every slot must resolve from the container. --- ## `ScopeRef` Marks a constructor parameter to receive the live resolution scope. ```ts export interface ScopeRef { readonly scope: true; } ``` --- ## `Union` A slot that tries each member in declaration order and resolves to the first one that is registered. ```ts export interface Union { readonly union: readonly DepSlot[]; } ``` Members are tried in array order (first = highest precedence). If no member is resolvable, the container throws. Each member is itself a `DepSlot`, so nesting is allowed. ### `union(...slots)` Ergonomic constructor: ```ts export function union(...slots: DepSlot[]): Union ``` ```ts services.add("pkg:IHandler", Handler, [[ union("pkg:IRedis", "pkg:IMemoryCache"), "pkg:ILogger", ]]); ``` --- ## `TypeArgRef` Marks a constructor parameter to be injected with the **token string** of one of an open registration's type arguments — the `typeof(T)` analog for open-generic templates. ```ts export interface TypeArgRef { readonly typeArg: number; } ``` `typeArg` is the 1-based hole number (`{ typeArg: 1 }` names the argument bound to `$1`). It only ever appears raw inside an **open** registration's carried signature. At close time, substitution replaces it with a `LiteralRef` carrying the substituted argument's token string — a raw `TypeArgRef` reaching resolution is a bug (unsatisfiable; fails loud rather than silently falling into the token branch). Authored via `Typeof` (below) or, by hand, `typeArg(n)`. --- ## `Inject` A phantom brand type. Pins the token for one constructor or factory parameter without changing the value type. ```ts declare const TOK: unique symbol; export type Inject = T & { readonly [TOK]?: K }; ``` The brand is optional — a plain `T` is still assignable. Zero runtime cost. ```ts // Pin a specific token for one arg; others derive normally class Handler { constructor( a: Inject, b: ILogger, ) {} } ``` Works in any type position the transformer reads: class ctor params, inline factory params, return types. Use it as the escape hatch for anonymous or purely structural types — types with no name that the transformer cannot otherwise tokenize — `Inject<{ n: number }, "my:opts">`. Named types (interfaces, classes, primitive keywords) derive a token on their own and do not need `Inject`. Re-exported by `@fnioc/transformer`. --- ## `Hole` and `$` Compile-time skolems — placeholders standing in for the `N`th type argument of an open-generic template (1-based, mirrors the `Inject` brand pattern above). ```ts declare const HOLE: unique symbol; export type Hole = C & { readonly [HOLE]?: N }; export type $ = Hole; ``` `$` is unbounded sugar for the common unconstrained case (`$<1>` = `Hole<1>`, `$<2>` = `Hole<2>`, … no upper bound). Write `Hole` directly when the implementation's type parameter carries a constraint the skolem needs to satisfy — `Hole<1, Entity>` **is** an `Entity` (the brand property is optional, so the intersection stays assignable to `C`), letting a constrained impl like `class SqlRepository` accept a hole as its type argument: ```ts services.add>>(SqlRepository>); ``` Zero runtime footprint — like `Inject`, these are pure compile-time brands read structurally by `@fnioc/transformer`. See [`@fnioc/transformer`](../transformer/README.md#open-generics) for how they drive token derivation and instantiation expressions. --- ## `Typeof` A phantom brand marking a constructor parameter that receives the **token string** of type argument `T` — the `typeof(T)` analog (hence the name). ```ts declare const ARG: unique symbol; export type Typeof = Token & { readonly [ARG]?: T }; ``` `Typeof` is type-driven — the transformer infers the hole from `T`; its manual counterpart `typeArg(n)` is positional. The value type stays `Token` (a plain string) — the brand property is optional, so any string is assignable. When `T` resolves to a `Hole`, the transformer emits an open `TypeArgRef` slot (`{ typeArg: N }`) that substitution closes per registration; when `T` is concrete (a closed registration via an instantiation expression), it emits the derived token directly as a `LiteralRef` value slot — no substitution step needed. ```ts class SqlRepository implements IRepository { constructor( private db: IDbConnection, private entityToken: Typeof, ) {} } services.add>>(SqlRepository<$<1>>); const userRepo = scope.resolve>(); // userRepo.entityToken === "pkg:User" ``` --- ## `DepSlot` One positional slot in a constructor signature: ```ts export type DepSlot = Token | FactoryRef | ScopeRef | Union | LiteralRef | TypeArgRef; ``` - `Token` — a container-resolved dep (registered) or a caller-supplied param (unregistered); the live registration map determines which at resolve time. - `FactoryRef` — a factory-injected parameter. - `ScopeRef` — the live resolution scope. - `Union` — member-level alternatives; first resolvable wins. - `LiteralRef` — a singular literal or nullish-singleton value, injected directly, no container lookup, always satisfiable. - `TypeArgRef` — the token string of an open registration's `N`th type argument; substituted to a `LiteralRef` at close time (see `TypeArgRef` above). An unregistered token slot is caller-supplied by definition; a `TypeArgRef` slot is only ever satisfiable through substitution, never directly. --- ## `DepRecord` The shape of a registration's carried dependency metadata — not stored anywhere globally; it's the type of the array a registration passes as its own signature(s). ```ts export interface DepRecord { readonly signatures: readonly (readonly DepSlot[])[]; } ``` `signatures` holds one or many signature arrays, supporting constructor overloads without an ABI break — the engine picks the first satisfiable one, scanned longest to shortest (see [`@fnioc/di`](../di/README.md#greedy-overload-selection)). --- ## Authoring dependency signatures by hand There is no global metadata store and no decorator. A signature rides directly on the registration, passed as the optional **third argument** to `@fnioc/di`'s `add` / `addFactory`: ```ts import { ServiceManifest } from "@fnioc/di"; const services = new ServiceManifest(); services.add("pkg:IHandler", Handler, [ ["pkg:ILogger", "pkg:IDb"], // overload 0 — one signature per constructor overload ]); ``` `@fnioc/transformer` emits this array inline for every registration it can statically extract a signature from, rewriting the type-driven `add(Foo)` to `add("pkg:IFoo", Foo, [[...]])` — there is no separate prelude call and nothing hoisted; the signature travels with the registration itself. Hand authoring (no transformer) means writing the array yourself, using the slot builders above (`union`, `typeArg`) plus plain token strings, `FactoryRef` / `ScopeRef` / `LiteralRef` object literals, and the token-grammar helpers below for open generics. Omitting the third argument leaves the registration signature-less — fine for a zero-arg constructor, but resolution throws `MissingMetadataError` for any constructor with parameters. Because the array is keyed on the **registration record**, not on the constructor function, one JS class can back any number of independent registrations with different signatures — the mechanism open-generics registrations depend on, where the same erased class serves every closing of a template. See [Open-generic token grammar](#open-generic-token-grammar) below and [`@fnioc/di`](../di/README.md#open-generics). --- ## Open-generic token grammar Closing a generic is token algebra, not runtime type machinery — TypeScript generics are erased, so there is exactly one JS class per generic implementation. `@fnioc/transformer` renders the grammar below at build time; these functions are how `@fnioc/di`'s resolve-time fallback (and any hand-written manual registration) works with it directly. **Closed-generic grammar (canonical, recursive):** `base` — no whitespace around `<` `>` `,`. Each arg is itself a token, so nesting recurses (`pkg:IFoo>`). A **hole** is an arg that is exactly `$N` (decimal, `N ≥ 1`); a token containing a hole at any depth is an *open template*. Literal-type args keep their interior spaces/quotes (`"a" | "b"`) — the parser is quote-aware, so commas and angle brackets inside double quotes never count as separators. ### `closeToken(base, ...args)` ```ts export function closeToken(base: Token, ...args: Token[]): Token ``` Renders the canonical form. With no args, returns `base` unchanged. ```ts closeToken("pkg:IRepository", "pkg:User"); // "pkg:IRepository" closeToken("pkg:IMap", "string", "$1"); // "pkg:IMap" ``` ### `parseToken(token)` ```ts export interface ParsedToken { readonly base: Token; readonly args: readonly Token[]; } export function parseToken(token: Token): ParsedToken | undefined ``` Splits a closed-generic token into its base and top-level args. Returns `undefined` for non-generic tokens (no top-level `<`) and for malformed input (unbalanced brackets, empty arg, trailing text after the closing `>`, unterminated quote) — callers fall through to their normal exact-match / unregistered-token handling either way. ### `isOpenToken(token)` ```ts export function isOpenToken(token: Token): boolean ``` True when `token` contains a hole (`$N`) at any depth — grammar-aware, so a `$N` inside a quoted literal arg is not a hole. ### `substituteToken(template, args)` ```ts export function substituteToken(template: Token, args: readonly Token[]): Token ``` Grammar-aware, recursive substitution — NOT a naive string replace. A node that is exactly `$N` is replaced by `args[N - 1]` (which may itself be a closed-generic token); everything else recurses through the parsed structure. Throws `RangeError` if the template references a hole beyond the supplied args. ```ts substituteToken("pkg:IRepository<$1>", ["pkg:User"]); // → "pkg:IRepository" ``` ### `substituteSignatures(signatures, args)` ```ts export function substituteSignatures( signatures: readonly (readonly DepSlot[])[], args: readonly Token[], ): readonly (readonly DepSlot[])[] ``` Substitutes `args` through every slot of every signature — the whole-record counterpart to `substituteToken`, used to close an open registration's carried dep signatures at resolve time. Per slot: a string token → `substituteToken`; a `FactoryRef` → its `type` and each `params` token substituted; a `Union` → members substituted recursively; a `TypeArgRef` → a `LiteralRef` carrying `args[typeArg - 1]`; `LiteralRef`/`ScopeRef` pass through unchanged. ### `typeArg(n)` and `isTypeArgRef` ```ts export function typeArg(n: number): TypeArgRef export function isTypeArgRef(slot: DepSlot): slot is TypeArgRef ``` `typeArg(n)` is the manual-authoring counterpart to `Typeof` — build a `{ typeArg: n }` slot by hand when writing an open registration's signature array directly (no transformer). `isTypeArgRef` is the type guard, alongside `isFactoryRef` / `isScopeRef` / `isUnionSlot` / `isLiteralRef`. --- ## Versioning `@fnioc/core` is versioned independently via release-please (semver). The dep-metadata format (`DepRecord`) is kept backward-compatible across `core` semver minors; a breaking change to the wire format would require a coordinated update across all packages. --- ## Exports summary | Export | Kind | Description | |---|---|---| | `Token` | Type alias | `string` — the DI key type. | | `FactoryRef` | Interface | `{ type: Token; params?: readonly Token[] }` — factory-injected parameter slot. | | `ScopeRef` | Interface | `{ scope: true }` — live resolution scope slot. | | `Union` | Interface | `{ union: readonly DepSlot[] }` — member-level alternatives. | | `union` | Function | Construct a `Union` slot from variadic `DepSlot` args. | | `LiteralRef` | Interface | `{ value }` — a singular literal / nullish-singleton slot, injected directly. | | `TypeArgRef` | Interface | `{ typeArg: number }` — the token string of an open registration's `N`th type argument. | | `Inject` | Type alias | Phantom brand — pins a token for one param without changing the value type. | | `Hole` | Type alias | `Hole` — compile-time skolem for the `N`th type argument of an open template, optionally constrained to `C`. | | `$` | Type alias | `$` — unbounded unconstrained sugar for `Hole`. | | `Typeof` | Type alias | Phantom brand — a ctor param receiving the token string of type argument `T` (`typeof(T)` analog). | | `DepSlot` | Type alias | `Token \| FactoryRef \| ScopeRef \| Union \| LiteralRef \| TypeArgRef` — one positional slot in a signature. | | `DepTarget` | Type alias | `Ctor \| Func` — a ctor or factory function a signature describes. | | `DepRecord` | Interface | `{ signatures }` — per-registration signature-array shape. | | `isFactoryRef` | Function | Type guard for `FactoryRef` slots. | | `isScopeRef` | Function | Type guard for `ScopeRef` slots. | | `isUnionSlot` | Function | Type guard for `Union` slots. | | `isLiteralRef` | Function | Type guard for `LiteralRef` slots. | | `closeToken` | Function | Render the canonical closed-generic token `base`. | | `parseToken` | Function | Parse a closed-generic token into `{ base, args }`, or `undefined`. | | `isOpenToken` | Function | True when a token contains a hole (`$N`) at any depth. | | `substituteToken` | Function | Grammar-aware substitution of holes in a token template. | | `substituteSignatures` | Function | Substitute type args through every slot of every signature. | | `typeArg` | Function | Build a `TypeArgRef` slot (`{ typeArg: n }`) by hand. | | `isTypeArgRef` | Function | Type guard for `TypeArgRef` slots. |