StaticclassResolve the backing class constructor for a template path. Loads the
template doc, reads its class, and resolves it via
loadClassByPath. Throws when no template exists at ref.
Lets a caller dispatch on a target's class (its mixins, its
inheritance) without instantiating it — e.g. singletonOrClone
deciding clone-vs-singleton, or a spawn applier checking
cls.prototype instanceof Warren.
StaticclearClear all registries (for testing). WARNING: This will not properly clean up objects. Only use for testing or shutdown.
StaticcloneClone an object from a template in the domain collection.
Pipeline:
class module.hydratorClass, resolve it and
await hydrator.hydrate(backing, doc.data). When absent, no
hydration step runs — templates that want generic mixin-field
copy must opt in by naming
'/lib/persistence/PersistentHydrator'.PostRegistrationMixin, await
postRegister(context), forwarding the caller-supplied context.If hydration or postRegister throws, the object is unregistered
before the error propagates.
The optional context is a caller-supplied bag threaded through to
postRegister. It carries runtime setup that cannot come from the
template's data — e.g., an authenticated User for an avatar.
Objects that don't care ignore it; objects that do (Avatar) declare a
narrower context type locally and read what they need.
Path to the template (e.g., "/obj/Avatar/
Optionalcontext: unknownOptional runtime context passed to postRegister
The cloned and registered object
StaticcopyCopy a single named field's value from src to dst through
the inter-stuff method surface.
Prefers src.getX() / dst.setX(value) (X = name with the
first letter uppercased) — the canonical inter-stuff contract
(per CLAUDE.md). Falls back to direct property access on either
side when the accessor pair isn't defined, which covers the
common case of bare persisted scalars that don't expose a custom
getter / setter (a tarnished: boolean field on Coin).
Used by GlobbableApi.split to clone the glob-identity field
set onto the split-off; general enough to live on the Stuff
registry rather than buried in glob.
Doesn't validate the destination Stuff actually owns the field —
the caller is responsible for picking field names that make
sense for dst's class. Mismatched casing or typos write a
dynamic property that nobody reads, silently. Treat as a
framework primitive: the callers are short, well-typed lists of
known fields (e.g., static globIdentityFields).
StaticcreateCreate and register a Stuff object via a caller-supplied factory.
Sister of clone(): same register / postRegister tail, no hydration
step (the factory IS the construction). Use this for runtime-only
objects whose construction needs explicit arguments and which don't
round-trip through the CMS template pattern (Interactive being the
canonical example — socketId, sessionId, user all flow through
the closure).
Registration happens BEFORE postRegister() so that recursive
resolution during setup (e.g. a location whose exits resolve back to
itself via the registry) can observe the in-flight instance. If
postRegister() throws, the object is unregistered before the error
propagates.
Function that constructs the object
Optionalcontext: unknownOptional runtime context passed to postRegister
The created and registered object
StaticcreateSynchronous variant of create() for runtime objects whose
construction is purely synchronous — no Hydrator.hydrate() step
(the factory does the work) and no postRegister() (the class
does not compose PostRegistrationMixin).
Same sentinel-flip + Proxy-wrap + register guarantees as the
async path, so the result is interception-mediated and tracked
in the registry just like any other Stuff. Use this from inside
sync helpers (e.g. Exitable.addBidirectionalExit's new Exit(...) calls) where awaiting create() would force the
caller — and its callers — to become async too.
Reach for create() whenever async hydration or post-registration
matters; createSync() is the narrow-use sister.
Guardrail: throws if the constructed Stuff composes
PostRegistrationMixin. The point of createSync is "this Stuff
has no async setup" — silently skipping postRegister() would
yield a half-initialised object. The throw forces such classes
to use the async create() path instead.
StaticdestructDestroy an object.
This is the canonical destruction entry point — Stuff.destroy()
is @CallSecurity(ApiOnly) and rejects calls from outside the Api
layer. Lifecycle ordering:
canDestruct() Witness fires on the target. A
{ ok: false, reason } result throws DestructError and
aborts the rest of the chain. (Force-bypass via
forceDestruct() invokes the witness identically but skips
the assertion — observers still see the call.)onDestruct() Witness fires on the target. Cleanup hook,
runs while the target is still live (mirror of how the
retired prepareDestroy() ran before destroy()).@ShadowSecurity({ detach }) because host
destruction is unconditional.destroy() runs (FINAL, unshadowable) — marks
_isDestroyed, unregisters from StuffApi.Events.StuffDestructed fires.The object to destroy
StaticfindFind every runtime instance cloned from templatePath. Always
returns an array (possibly empty). Companion to
findByTemplatePath for the multi-instance case.
StaticfindFind an object by its stuffId. Returns undefined if not found or if the object has been destroyed.
The runtime ID to look up
The object, or undefined if not found
StaticfindFind every runtime instance whose templatePath matches pattern
under PathPatternApi glob syntax (*, **, ?).
Backs the MQL path-glob seed (/obj/Avatar/*). Stuff without a
template path are not in the index and never match. Result order
is unspecified — callers that need stable ordering must sort.
StaticfindFind the single runtime instance cloned from templatePath.
Template paths identify classes of world objects — the same notion as MQL identity. For singleton system Ideas (one template per class), this is the canonical lookup.
Returns the instance when exactly one exists, undefined when
none, throws when multiple share the path. Throwing on multi is
deliberate — if a caller treats the result as a singleton and
silently picks an arbitrary one, bugs become non-deterministic.
Use findAllByTemplatePath when multiple instances are
legitimate.
O(1) via the byTemplatePath index maintained in
#updateIndexes.
StaticfindSync lookup for live templated Stuff instances (those that carry
a path field) by exact path. Backs the MQL path-atom
fallback: when findByPathGlob returns no clones, the resolver
falls back to this so a non-glob path can address the template
record itself (e.g. destruct /obj/Avatar/foo to remove the
template doc when no live clone exists).
Walks the registry and structurally matches via obj.path —
avoids importing Template here, which would close the
StuffApi → Template → Stuff → StuffApi cycle. Templates are in
the registry by virtue of Template._materialize going through
StuffApi.create. O(N) walk; called only on path-atom miss.
StaticforceForce-bypass variant of destruct() — invokes the canDestruct
witness identically (so audit hooks / observers fire as usual)
but ignores the veto result. The onDestruct cleanup hook still
runs.
Gated to DestructController — the narrow-entry pattern. Only
DestructController can reach this entry point; the controller does
the AccessApi.can(giver, 'force-destruct', target) check before
invoking. Combined, the mutation has exactly one legitimate entry
path AND that path enforces who is authorized.
The controller is cloned per execution (destruct -f), and
FromModule matches it by its class module id (code provenance), so
the cloned instance is admitted directly. Direct calls from any other
module throw SecurityError.
StaticgenerateGenerate a unique ID using nanoid. Uses base58-encoded nanoid for short, URL-safe IDs.
StaticgetStaticgetGet destroyed object metadata (for debugging).
StaticgetGet count of active objects.
StaticisStaticloadLoad and return the class constructor at classPath.
Public companion to the inline class-loading logic in clone():
validates the path, consults HotReloadApi for an override
blueprint, and falls back to a bare dynamic import. Returns the
raw constructor (typed as unknown — caller decides what to do
with it).
Used by ZoneApi.isFolderClass and isSpatialZoneClass to
resolve a template's class: field to its TS class so the check
can be prototype instanceof Zone rather than membership in a
central allow-list. Content devs add a folder class by extends Zone — no central registry to edit.
StaticregisterRegister an object in the registry. Should be called during object construction.
The object to register
StaticresolveResolve a named export at classPath from the hot-reload
registry, warming the path (lazy reload) on a cold miss. The
synchronous, caller-named-export cousin of loadClassByPath
(which resolves the file-basename export and falls back to a bare
import). Used for path-resolved brain modules (exportName = 'brain'), whose concept-export is not the basename.
Only class-like exports are retained by the registry, so the brain
marker is a named class-expression (export const brain = class {…}). Returns null — never throws — for an invalid path, a
frozen path, or a missing export, so callers (the behavior wiring,
the CMS save-gate) treat "doesn't resolve" as a clean negative.
StaticresolveSynchronous sibling of resolveExport: resolves a named
export from the hot-reload registry with no lazy warm. Returns
null if the path was never warmed, is frozen, or lacks the
export. The per-invocation re-resolve seam for brains —
BehavedMixin warms each brain path once at wire time (via the
async resolveExport), then re-resolves the current class per fire
through this sync path (a registry-map hit), so HMR propagates
with no async on the hot path.
StaticsingletonCache-or-clone for templatePath-keyed singletons.
Returns the unique live instance for path if one exists in the
byTemplatePath index, otherwise routes through clone() to create
one. Works on any class — composition with SingletonMixin is the
separate enforcement layer that prevents bare clone() from
producing duplicates.
Throws when the index has multiple instances for path — that
means the caller violated the singleton contract by also using
clone() on a non-SingletonMixin class. Use clone() and track
instances explicitly in that case.
Template path (e.g., /narnia for the Narnia zone).
Optionalcontext: unknownForwarded to clone() when this resolves to a
first-time clone.
StaticsingletonInstantiate a template by the only question that matters at the
generic layer: should it be a shared singleton or a fresh instance?
If the class composes SingletonMixin → singleton(path) (reuse
the one instance, clone-if-absent); otherwise → clone(path) (a
fresh instance). Any domain semantics on top (a Warren landing in
its host, a recall) belong in the caller, not here.
Optionalcontext: unknownStaticunregisterUnregister an object from the registry. Called by destruct() — not typically invoked directly.
The object to unregister
Static API for object management and registry.