Architecture
This guide explains Tapestry's system design, runtime architecture, and how the various subsystems work together to provide a TypeScript-first modding framework for Minecraft.
System Overview
Tapestry is built on a strict phase-based lifecycle model with clear subsystem boundaries. The architecture ensures that mod code runs at the correct time and that illegal operations fail immediately with clear diagnostics.
Figure 1: Tapestry's system architecture showing the relationships between core subsystems, including lifecycle management, extension system, TypeScript runtime, mod graph, events and state, RPC transport, overlay presentation, and persistence.
High-Level Runtime Shape
Fabric loader
-> TapestryMod bootstrap/validation/TS pipeline
-> PhaseController (global phase machine)
-> Extension registration and frozen API tree
-> TypeScriptRuntime (GraalVM + API injection)
-> ModRegistry (TS_REGISTER/TS_ACTIVATE graph)
-> Runtime services (scheduler, event bus, config, state)
-> RPC stack (packet handler, handshake, dispatcher, client runtime bridge)
-> Client presentation overlays (when CLIENT_PRESENTATION_READY reached)
-> Persistence services and mod storesCore Subsystems
Tapestry is organized into distinct subsystems, each with clear responsibilities and ownership boundaries:
Lifecycle Management
Package: com.tapestry.lifecycle
The lifecycle subsystem is the authoritative source for phase state and guards. It ensures that all operations happen at the correct time in the boot sequence.
Key Classes:
TapestryPhase- Enum defining all lifecycle phasesPhaseController- Global phase state machine and transition enforcement
Responsibilities:
- Enforce strict single-step phase transitions
- Provide phase guards (
requirePhase,requireAtLeast,requireAtMost) - Prevent invalid phase transitions with fail-fast errors
Extension System
Package: com.tapestry.extensions
The extension system discovers, validates, and registers Java-based extensions that provide capabilities to TypeScript mods.
Key Classes:
ExtensionDiscovery- Discovers extension providersExtensionValidator- Validates extension descriptors and dependenciesExtensionRegistrationOrchestrator- Coordinates extension registrationDefaultApiRegistry- Manages API function registration and freezing
Responsibilities:
- Discover extension providers during DISCOVERY phase
- Validate extension capabilities and dependencies during VALIDATION phase
- Register extension APIs during REGISTRATION phase
- Freeze API tree before TypeScript runtime initialization
TypeScript Runtime
Package: com.tapestry.typescript
The TypeScript runtime owns the GraalVM JavaScript context and manages the tapestry namespace injection, script execution, and execution context tracking.
Key Classes:
TypeScriptRuntime- Owns JS context and API injectionTsModDiscovery- Discovers TypeScript mod filesTsModDefineFunction- Implementsmod.definebridgeTsWorldgenApi- Provides worldgen API to TypeScript
Responsibilities:
- Initialize GraalVM context for mod loading
- Inject
tapestrynamespace with phase-appropriate APIs - Execute TypeScript mod scripts
- Track current mod execution context
- Extend runtime APIs as phases progress
Mod Graph
Package: com.tapestry.mod
The mod graph subsystem manages TypeScript mod registration, dependency validation, and activation ordering.
Key Classes:
ModRegistry- Central registry for mod descriptorsModDescriptor- Metadata for a single modModDiscovery- Discovers and evaluates mod scripts
Responsibilities:
- Collect mod descriptors during TS_REGISTER phase
- Validate mod dependencies
- Build activation order based on dependency graph
- Manage mod exports and requires during TS_ACTIVATE phase
Events and State
Packages: com.tapestry.events, com.tapestry.state
The event and state subsystems provide synchronous event dispatch and deferred state flush coordination.
Key Classes:
EventBus- Synchronous event dispatch with namespace validationStateCoordinator- Coordinates deferred state change flushesModStateService- Per-mod state management
Responsibilities:
- Validate event namespace ownership
- Dispatch events synchronously to registered listeners
- Track dispatch depth for deferred state changes
- Flush pending state changes at dispatch depth zero
RPC Transport and Dispatch
Packages: com.tapestry.rpc, com.tapestry.rpc.client, com.tapestry.networking
The RPC subsystem handles client-server communication, including handshake, protocol validation, call dispatch, and response routing.
Key Classes:
RpcPacketHandler- Validates and routes incoming RPC packetsRpcServerRuntime- Server-side RPC runtime with handshake and rate limitingRpcDispatcher- Dispatches RPC calls to registered methodsRpcClientRuntime- Client-side RPC runtime with pending call trackingRateLimiter- Per-player rate limiting
Responsibilities:
- Validate RPC protocol and packet structure
- Enforce handshake requirements
- Rate-limit RPC calls per player
- Dispatch calls to registered server methods
- Route responses back to client callers
Overlay Presentation
Package: com.tapestry.overlay
The overlay subsystem manages client-side UI registration, validation, and rendering.
Key Classes:
OverlayApi- TypeScript API for overlay registrationOverlayRegistry- Validates and stores overlay definitionsOverlayRenderer- Renders overlays each frame
Responsibilities:
- Register overlays during CLIENT_PRESENTATION_READY phase
- Validate overlay definitions (id, anchor, render function)
- Render overlays each frame with sanitized snapshots
- Manage overlay visibility per mod
Persistence
Package: com.tapestry.persistence
The persistence subsystem provides per-mod state storage with service-side initialization.
Key Classes:
PersistenceService- Abstract persistence serviceServerPersistenceService- Server-side persistence implementationClientPersistenceService- Client-side persistence implementationModStateStore- Per-mod key-value store
Responsibilities:
- Initialize persistence backend during PERSISTENCE_READY phase
- Provide per-mod state stores
- Enforce same-mod access restrictions
- Persist state to disk
Boot Sequence
Understanding the boot sequence is critical to understanding when different operations are allowed.
Phase Progression
Tapestry progresses through phases in strict order. Each phase has specific allowed operations, and attempting operations outside their designated phase results in immediate failure.
BOOTSTRAP - Core initialization
- Instantiate core fields (API, registries, TypeScript runtime)
- Set up basic infrastructure
DISCOVERY - Extension scanning
- Register Fabric lifecycle hooks
- Discover extension providers
VALIDATION - Extension validation
- Validate extension descriptors
- Check dependencies and capabilities
- Freeze capability and type registries
REGISTRATION - API domain declaration
- Register extension APIs, hooks, and services
- Build extension registries
FREEZE - API shape is sealed
- Freeze all registries (API, hook, service)
- API tree becomes immutable
TS_LOAD - GraalVM starts, scripts loaded
- Initialize TypeScript runtime for mod loading
- Discover TypeScript mod files
TS_REGISTER - Mod registration and metadata collection
- Evaluate mod scripts
- Collect mod descriptors via
mod.define - Validate dependencies
- Build activation order
TS_ACTIVATE - Dependency resolution
- Activate mods in dependency order
- Process
mod.exportandmod.require
TS_READY -
onLoadhooks execute- Execute all mod
onLoadhandlers - Allow hook registration
- Execute all mod
PERSISTENCE_READY - Persistence backend ready
- Initialize persistence service
- Enable mod state stores
EVENT - Event system ready
- Extend runtime with event APIs
RUNTIME - Game logic and events active
- Initialize runtime services (scheduler, event bus, config, state)
- Extend runtime with gameplay APIs (players, RPC, etc.)
CLIENT_PRESENTATION_READY - Client UI ready
- Extend runtime with client presentation APIs
- Enable overlay registration
Server Lifecycle Integration
Tapestry integrates with Fabric's server lifecycle events:
- SERVER_STARTED: Initialize player service, persistence backend, and RPC system
- END_SERVER_TICK: Tick scheduler and event bus; emit
engine:tickevent - JOIN/DISCONNECT: Emit player join/leave events
Key Runtime Flows
TypeScript Mod Loading
TsModDiscoveryscans filesystem and classpath for mod scripts- Runtime advances to TS_REGISTER phase
- Each script is evaluated in the GraalVM context
- Scripts call
tapestry.mod.defineto register metadata ModRegistryvalidates dependencies and builds activation order- Runtime advances to TS_ACTIVATE phase
- Mods are activated in dependency order
- Runtime advances to TS_READY phase
- Each mod's
onLoadhandler is executed
RPC Call Flow
- Client calls
tapestry.rpc.server.<method>(...) RpcClientRuntimecreates call ID and registers pending future- Client sends RPC packet to server
RpcPacketHandlervalidates packet structureRpcServerRuntimeenforces handshake and rate limitsRpcDispatchervalidates method and executes handler- Server sends response packet
RpcClientRuntimeresolves pending future with result
Event Dispatch and State Flush
- Mod calls
tapestry.mod.emit(namespace, event, data) EventBusvalidates namespace ownershipStateCoordinatortracks dispatch depth- Event listeners execute synchronously
- Listeners may modify state via
tapestry.mod.state - When dispatch depth returns to zero,
StateCoordinatorflushes pending state changes - Internal
__state_change__events are emitted for state watchers
Ownership Boundaries
Clear ownership boundaries prevent subsystems from interfering with each other:
- PhaseController is the global authority for allowed operations
- TypeScriptRuntime owns the JS context thread/queue (
TWILA-JS) - ModRegistry owns mod metadata and activation constraints
- EventBus owns event namespace validation and dispatch
- StateCoordinator owns deferred state-change flush
- RpcServerRuntime enforces handshake and rate limits
- OverlayRegistry owns client presentation registration
- PersistenceService enforces per-mod store access
Global Invariants
These invariants are enforced throughout the system:
- Phase transitions are strict single-step increments
- Most API operations are phase-gated and fail fast on mismatch
- Mod dependency checks fail startup on missing/circular references
- JS API calls that depend on mod context fail when no current mod ID is set
- RPC calls require handshake and are rate-limited per player
- Event namespaces must be owned by the emitting mod
- State changes are deferred and flushed at dispatch depth zero
Error Handling
Tapestry follows a fail-fast philosophy. When something goes wrong, the system throws an exception immediately with a clear error message rather than allowing invalid state to propagate.
Common Error Scenarios
- Invalid phase transition: Attempting to skip phases or transition backward
- Phase mismatch: Calling an API outside its allowed phase
- Missing dependencies: Mod depends on another mod that doesn't exist
- Circular dependencies: Mod dependency graph contains cycles
- Namespace violation: Emitting events in a namespace not owned by the mod
- Cross-mod access: Attempting to access another mod's persistence store
- RPC handshake failure: Client attempts RPC call before handshake completes
- Rate limit exceeded: Client makes too many RPC calls in a short time
Further Reading
For more detailed information about specific aspects of the architecture:
- Boot Sequence Details - Detailed boot timeline
- Phase Contracts - Allowed operations per phase
- Runtime Data Flows - End-to-end runtime paths
Next Steps
Now that you understand Tapestry's architecture:
- Learn about Lifecycle Phases in detail
- Explore the Extension System
- Review the API Reference for specific function documentation
