Saxonberg Server API
    Preparing search index...

    Class MixinApi

    Static API for mixin management and introspection.

    Index

    Constructors

    Methods

    • Once-per-class composition validation hook.

      Walks the prototype chain calling each level's static __validateComposition__(ctor) method exactly once per concrete class. Mixins that want to enforce composition constraints declare the static; everyone else is a no-op.

      Called from StuffApi.register so the check fires the first time an instance of a given class lands in the registry. Subsequent registrations of the same class short-circuit on the WeakSet memo.

      The memo is keyed on constructor identity, not class name. HotReloadApi.reload re-evaluates a module and produces a NEW class binding (per ModuleApi.stamp's "first-stamp-wins" rule — same name, fresh identity). The new class is not in the WeakSet, so the next first-instance-of-class registration re-runs validation against whatever __validateComposition__ is on the NEW chain. Old class identities stay memoized — fine, because nothing creates new instances of a post-HMR-retired class.

      Leaf reload required. Reloading a mixin module alone is NOT enough to pick up a new check. JS class inheritance is bound at class-definition time: class Coin extends GlobbableMixin(Idea) captures whatever GlobbableMixin returned at that expression's evaluation. Reloading Globbable.ts produces a new mixin function and registers it with HotReloadApi, but Coin's prototype chain still points at the OLD mixin output. Since Coin's constructor identity hasn't changed, the WeakSet hit memoizes the old validation forever.

      To rotate the validation: reload the leaf class too. That re-evaluates its class Coin extends GlobbableMixin(Idea) expression against the new mixin output, produces a fresh Coin constructor identity, and the next first-instance triggers the new check.

      No auto-cascade — there's no machinery that reloads leaves when a mixin reloads. That's intentional: bulk re-instantiation while a player is mid-action would be jarring. The right tool for "refresh every Globbable in the world" is an MQL query (e.g., world:[mixin.GlobbableMixin]) plus an explicit reload, run by the dev when they're ready. Forgetting to reload leaves doesn't create inconsistency — the old check just keeps applying; the new constraint silently doesn't tighten, but nothing breaks.

      Cross-reference: docs/subsystems/mixins.md §Composition validation, docs/subsystems/hot-reload.md §Composition validation.

      • GlobbableMixin⊥ Container, ⊥ Singleton, globIdentityFields ⊂ persistentFields.
      • PerceiverMixin — requires Sensor on the chain. The TS bound is loose (MixinConstructor); the Perceiver extends Sensor interface relationship narrows the type but doesn't enforce composition. Without runtime co-composition, MixinApi.isPerceiver would lie.

      A bound is cheaper and more specific — see the principle in docs/subsystems/mixins.md §Composition validation. These mixins document a constraint, but the constraint is enforced at compile time and doesn't need the runtime hook:

      • AdornableMixinMixinConstructor<Stuff & Container>.
      • WearableMixin / WieldableMixinMixinConstructor<Stuff & Slottable & Containable>.
      • MobileMixinMixinConstructor<Stuff & Containable>.
      • PosturedMixin / MountableMixin / DrivableMixinMixinConstructor<Stuff & Slotted>.
      • WorkspaceMixinMixinConstructor<Stuff & Environment>.

      Soft pairings documented in JSDoc but not strictly required (e.g., AdornmentMixin typically composed with Containable so it can become inventory after detach — but a never-detached adornment is fine without it) are not modeled here either.

      Parameters

      Returns void

    • Collect the settings-schema entries declared across a class's prototype chain — the raw walk over each layer's own static settings array, one (entry, sourceMixin) pair per declared entry. Picks up static settings from both mixin layers (the common case, provenance = _mixinName) AND from substrate classes whose concept they own (provenance falls back to name, then <anonymous>).

      Sibling of getAllFieldMarshallers — same mixin-static-slot collection shape. Shared by EnvironmentMixin (whose collectSchema wrapper layers duplicate-key validation on top) and ShellApi.resolveSetting's non-Environment fallback. The duplicate-key throw is intentionally NOT here — that's settings validation, which belongs in the Environment wrapper.

      Parameters

      Returns { entry: SettingsSchemaEntry; sourceMixin: string }[]

      Array of (entry, sourceMixin) pairs in chain order

    • Active-mixin substrate (augmentation Wave 1).

      Returns the set of mixin constructors currently "active" on stuff. The substrate splits build-time composition from runtime activation:

      • Most mixins are NOT augment-gated (_augmentGated !== true). Native composition is sufficient — active iff composed.
      • Augment-gated mixins (_augmentGated === true) are active ONLY when an installed augment confers them via AugmentMixin.confers().

      The walk is lazy in v1 (no cache); installing or removing an augment takes effect on the next call. Future optimization caches the set on the entity, invalidated on slot occupy/release.

      The implementation looks at slot occupancy via SlotApi.getAllOccupants for hosts that compose SlottedMixin; non-slotted hosts have no augments and active = composed for everything.

      Parameters

      Returns readonly MixinClass[]

    • Get the field-marshaller registry for a class — the map of persistent-field names → marshaller templatePaths declared on mixins / classes in the prototype chain.

      Walks the prototype chain concrete-class-first, so a subclass's declaration wins over a base mixin's for the same field. The returned map is keyed by field name; values are templatePath strings that callers resolve via StuffApi.findByTemplatePath at use time.

      Mirrors the shape of getAllPersistentFields and is the companion lookup for PersistentHydrator / Document's marshaller-aware coercion path.

      Parameters

      Returns Record<string, string>

      Map of field name to marshaller templatePath

    • Walk the prototype chain unioning the static globIdentityFields arrays declared at each level. Deduplicates. Mirrors the shape of getAllPersistentFields.

      A glob's "kind" is defined by the values of these fields plus templatePath; two globs merge iff their templatePath matches and every glob-identity field has equal values.

      Parameters

      Returns string[]

    • Walk the prototype chain unioning the static instructionFields arrays declared at each level. Deduplicates. Mirrors the shape of getAllPersistentFields but for instruction fields — declarations applied to produce/modify derived runtime state via an applyX method, rather than stored as the value of a property.

      Instruction fields are the second half of the property/instruction split (see feedback_property_vs_instruction_fields). exits on ExitableMixin is the canonical example: the YAML data is a Record<string, ExitInstruction> recipe, applied by applyExits to populate the runtime exits: Map<string, Exit>. There is no paired getter for the spec; the runtime collection has its own API (getExit, addExit, …).

      PersistentHydrator dispatches in two phases: Phase 1 reads every entry in getAllPersistentFields and writes via setX (or bracket-assigns when no setter exists); Phase 2 reads every entry in getAllInstructionFields and calls applyX. An instruction field whose applyX method is missing is a configuration bug, surfaced as a clear runtime error at hydrate time.

      Parameters

      Returns string[]

      Array of all instruction field names (deduplicated)

    • Walk the prototype chain unioning the static markupAugmenters arrays declared at each level. Returned in parent-first → child-last order so Mml.augment's fold applies the more fundamental transformations first and the more specific (deeper in the chain) ones last — same intuition as CSS specificity.

      Distinct from getAllSubscribableFields, which is leaf-first because a subclass's descriptor needs to override the parent's on the collision Map; augmenters have no override semantics, so we order for readability instead.

      The augmenter signature (text, host, viewer) => string is defined in api/mml.ts (alongside the Mml.augment static); we use a structural local type here to avoid a runtime import cycle (mixin.ts is below mml.ts in the layering).

      Parameters

      Returns ((text: string, host: unknown, viewer: unknown, opts?: unknown) => string)[]

    • Get all persistent fields (from mixins and every class in the chain).

      Walks the prototype chain and collects persistentFields declared at every level — mixins carry them as static arrays, and concrete classes do too. Since a subclass that declares its own persistentFields shadows the parent's static in JS, we need hasOwnProperty at each level to pick up contributions from the whole ancestry (Stuff → Idea → Location → …).

      Parameters

      Returns string[]

      Array of all persistent field names (deduplicated)

    • Walk the prototype chain unioning the static subscribableFields arrays declared at each level. Mirrors the shape of getAllPersistentFields. Order is parent-first → child- last; later entries on the same name win in the caller's downstream merge (collectSubscribableFields in mql-subscription.ts).

      SubscribableFieldDescriptor is defined in mql-subscription.ts; we use a structural local type here to avoid a runtime import cycle (mixin.ts is below mql-subscription.ts in the layering).

      Parameters

      Returns { name: string; [k: string]: unknown }[]

    • Get all persistent fields from mixins applied to a class. Walks the prototype chain collecting fields from all mixins.

      This is used by PersistApi to automatically sync fields without requiring manual field lists in every class.

      Parameters

      Returns string[]

      Array of persistent field names

    • Check if a class or Stuff instance uses a specific mixin.

      Two overloads:

      • hasMixin(constructor, name) — pure constructor check, walks the prototype chain. Used for static introspection.
      • hasMixin(stuffInstance, name) — also walks shadows attached to the host. A host without the mixin but carrying a shadow that composes it returns true. This is what the Witness pattern relies on so that a shadow can opt into receiving notifications even when the host doesn't compose the relevant mixin itself.

      Usage:

      import { Mixins } from './mixin';

      if (MixinApi.hasMixin(Player, Mixins.Named)) {
      // Player class composes NamedMixin
      }

      if (MixinApi.hasMixin(somePlayerInstance, Mixins.Named)) {
      // The instance OR any shadow on it composes NamedMixin
      }

      Parameters

      Returns boolean

    • Check if a class or Stuff instance uses a specific mixin.

      Two overloads:

      • hasMixin(constructor, name) — pure constructor check, walks the prototype chain. Used for static introspection.
      • hasMixin(stuffInstance, name) — also walks shadows attached to the host. A host without the mixin but carrying a shadow that composes it returns true. This is what the Witness pattern relies on so that a shadow can opt into receiving notifications even when the host doesn't compose the relevant mixin itself.

      Usage:

      import { Mixins } from './mixin';

      if (MixinApi.hasMixin(Player, Mixins.Named)) {
      // Player class composes NamedMixin
      }

      if (MixinApi.hasMixin(somePlayerInstance, Mixins.Named)) {
      // The instance OR any shadow on it composes NamedMixin
      }

      Parameters

      Returns boolean

    • getActiveMixins membership predicate. Returns true iff a mixin named mixinName is in the active set on stuff. For un-gated mixins, equivalent to hasMixin; for gated ones, reflects augment-toggled state.

      Parameters

      • stuff: Stuff
      • mixinName: string

      Returns boolean

    • Type-predicate narrowing helpers.

      Each predicate performs a runtime mixin check and threads the matching interface into TypeScript's control-flow narrowing:

      if (MixinApi.isContainer(obj)) {
      obj.getContents(); // obj is Stuff & Container here
      }

      Prefer these over hasMixin(obj.constructor, Mixins.X) + cast when the goal is to call interface methods on the narrowed object. hasMixin remains the primitive for dynamic introspection (iterating queryMixins).

      Parameters

      Returns obj is Stuff & Container

    • Whether obj can currently fulfill an order. MakerMixin is augment-gated, so this routes through isActive (activeness), not hasMixin (composition): a bar Crafter composes MakerMixin always but is a maker only while its on-shift Position confers it. The two isMaker consumers — CraftingLogic.resolveMaker (order fulfilment) and BankingControllerBase (the house representative) — thereby resolve only the on-shift bartender.

      Parameters

      Returns obj is Stuff & Maker

    • Convert a lowerCamel field name to its PascalCase form — the suffix used when deriving a method name from a field name (e.g., 'coords''Coords', so a hydrator can dispatch 'set' + pascalCase('coords')'setCoords').

      Used by PersistentHydrator (Phase 1 set<X> / Phase 2 apply<X> dispatch) and Zone.lookupField (get<X> reflection). Lives here because the field-name-to-method-name convention is the same one getAllPersistentFields / getAllInstructionFields presume — callers that introspect mixin field names also need to derive method names from them.

      Empty string passes through unchanged.

      Parameters

      • field: string

      Returns string