Architecture

Internal design and structure of the Jepia framework.

1. Project Overview

Jepia is a TypeScript framework for programmatic Minecraft Bedrock Add-On generation using Bun. It provides a fluent, object-oriented API for creating behavior packs, resource packs, and their components.

PropertyValue
LanguageTypeScript (Bun runtime)
TargetMinecraft Bedrock Edition
Format Version2 (stable)
ValidationAJV + Zod
Outputbehavior_pack/ + resource_pack/ folders, .mcaddon / .mcpack

The core idea: describe your add-on in TypeScript, let Jepia handle manifest generation, file layout, schema validation, and packaging.

import { Addon } from 'jepia';

const addon = new Addon('TechMod', 'Industrial automation add-on');
// addon.behaviorPack and addon.resourcePack are auto-created
addon.addItem(myItem);
addon.addBlock(myBlock);
await addon.generate('./output');

2. Architecture Diagram

The layered architecture separates concerns into entry point, packs, components, scripting, and utilities.


 ┌──────────────────────────────────────────────────────────────┐
 │                           Addon                              │
 │                  (entry point, orchestrator)                  │
 └────────────┬───────────────────────┬──────────┬─────────────┘
              │                       │          │
              ▼                       ▼          ▼
 ┌──────────────────┐  ┌──────────────────┐  ┌─────────────────────┐
 │   BehaviorPack   │  │  ResourcePack    │  │  ScriptPipeline     │
 │                  │  │                  │  │                     │
 │  - Items         │  │  - Textures      │  │  - Script Blocks    │
 │  - Blocks        │  │  - Models        │  │  - File Refs        │
 │  - Entities      │  │  - Materials     │  │  - Custom Comps     │
 │  - Recipes       │  │  - RenderCtrl    │  │  - Custom Commands  │
 │  - Loot Tables   │  │  - TextureSets   │  │  - Constants        │
 │  - Script Module │  │  - TextureAtlas  │  │  - Bun.build        │
 └──────────────────┘  └──────────────────┘  └─────────────────────┘
          │                       │                    │
          └───────────────────────┴──────────┬─────────┘
                                             ▼
 ┌────────────────────────────────────────────────────────────────┐
 │                         Components                             │
 │                                                                │
 │  Item  Block  Entity  Texture  Model  Material                 │
 │  RenderController  TextureLayer  TextureSet                    │
 │  TextureAtlas  ComponentRegistry  DependencyResolver           │
 └────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
 ┌────────────────────────────────────────────────────────────────┐
 │                         Utilities                              │
 │                                                                │
 │  UUID    Molang    Validation (AJV)    AtlasPacker             │
 │  FileSystem    Zod Config    Archiver (ZIP)                    │
 └────────────────────────────────────────────────────────────────┘
                              │
                              ▼
 ┌────────────────────────────────────────────────────────────────┐
 │                           Output                               │
 │                                                                │
 │   behavior_pack/            resource_pack/                     │
 │   ├── manifest.json         ├── manifest.json                  │
 │   ├── scripts/main.js       ├── textures/                      │
 │   ├── items/                ├── materials/                     │
 │   ├── blocks/               └── render_controllers/            │
 │   └── entities/             .mcaddon / .mcpack                 │
 │   jepia.config.json                                            │
 └────────────────────────────────────────────────────────────────┘
                    

3. Component System

All game objects derive from an abstract Component base class that enforces a consistent interface.

Base Component

MemberTypePurpose
idstringNamespaced identifier (e.g. myaddon:iron_sword)
displayNamestringHuman-readable name
descriptionstringOptional description
validate()booleanAJV schema validation
toJSON()objectBedrock-format JSON output

Component Hierarchy


 Component (abstract)
 ├── Item           — behavior pack items
 ├── Block          — behavior pack blocks
 ├── Entity         — behavior + resource pack entities
 ├── Texture        — resource pack texture references
 ├── TextureLayer   — individual images with PBR roles
 ├── TextureSet     — groups layers into .texture_set.json
 ├── Material       — .material files with inheritance
 ├── RenderController — wires geometry + textures + materials
 ├── Model          — geometry definitions
 ├── TextureAtlas   — sprite sheet packing
 ├── Recipe         — crafting, furnace, brewing recipes
 ├── LootTable      — loot pools and item drops
 ├── SpawnRule      — natural entity spawning conditions
 └── TradeTable     — merchant tier trades
                    

ComponentRegistry

Centralized registration of all components within a pack, enabling lookup by ID and type-safe retrieval.

// Components register themselves when added to a pack
addon.addItem(sword);   // registers in BehaviorPack registry
addon.addBlock(ore);    // registers in BehaviorPack registry

DependencyResolver

Resolves cross-component references at build time. For example, an Entity references a RenderController, which in turn references a Material and Texture. The resolver ensures all referenced components exist before output.

4. Pack System

Both pack types extend a shared Pack base class that handles manifest generation.

Base Pack MemberPurpose
namePack display name
descriptionPack description
uuid / moduleUUIDPersisted identifiers
versionSemantic version array [major, minor, patch]
generateManifest()Outputs manifest.json per Bedrock spec

BehaviorPack

Contains server-side game logic.

Content TypeOutput Folder
Itemsitems/
Blocksblocks/
Entitiesentities/
Recipesrecipes/
Loot Tablesloot_tables/
Spawn Rulesspawn_rules/
Trade Tablestrading/

ResourcePack

Contains client-side visuals and rendering definitions.

Content TypeOutput Folder
Texturestextures/
Models / Geometrymodels/
Materialsmaterials/
Render Controllersrender_controllers/
Client Entity Defsentity/
Texture Sets (PBR)textures/ (alongside base textures)

5. Rendering Pipeline

The rendering pipeline was built across Phases 1 through 5 and maps directly to Minecraft Bedrock's deferred rendering system.


 TextureLayer ──▶ TextureSet ──▶ Material ──▶ RenderController ──▶ TextureAtlas
   (images)       (PBR groups)   (.material)   (Molang wiring)     (sprite sheets)
                    

Phase 1: TextureLayer

Represents a single image file with a designated PBR role.

RoleDescription
colorBase color / albedo (RGB)
normalNormal map for surface detail
merMetalness / Emissive / Roughness packed channels
heightmapHeight data for parallax
const colorLayer = new TextureLayer('iron_color', {
  role: 'color',
  path: 'textures/blocks/iron_block',
  resolution: { width: 16, height: 16 }
});

Phase 2: TextureSet

Groups multiple TextureLayers into a .texture_set.json file for Bedrock's PBR pipeline.

const textureSet = new TextureSet('iron_pbr');
textureSet
  .setColorLayer(colorLayer)
  .setNormalLayer(normalLayer)
  .setMERLayer(merLayer);

Phase 3: Material

Defines .material files with vanilla inheritance, shader defines, blend states, and render states.

const mat = new Material('custom:glowing_ore');
mat
  .setParent('entity_alphatest')
  .addDefine('FANCY')
  .setBlendSrc('SourceAlpha')
  .setBlendDst('OneMinusSrcAlpha')
  .setState('DisableCulling', true);

Materials support the full inheritance chain: a child material inherits all defines, states, and blend factors from its parent, then overrides selectively.

Phase 4: RenderController

Wires geometry, textures, and materials together using Molang expressions. Outputs .render_controllers.json files.

const rc = new RenderController('controller.render.my_entity');
rc
  .setGeometry("Geometry.default")
  .addTexture("Texture.default")
  .addMaterial("Material.default", "*")
  .setPartVisibility("head", "query.is_baby");

Phase 5: TextureAtlas

Packs multiple textures into optimized sprite sheets using two strategies.

StrategyBest ForAlgorithm
shelfUniform-size texturesRow-based packing with shelf height tracking
maxrectsMixed-size texturesMaximal rectangles with best-short-side-fit heuristic
const atlas = new TextureAtlas('item_atlas', {
  strategy: 'maxrects',
  maxWidth: 2048,
  maxHeight: 2048,
  padding: 1
});
atlas.addSprite('sword', 16, 16);
atlas.addSprite('shield', 32, 32);
const result = atlas.pack(); // { placements, width, height }

5b. Scripting Pipeline

The scripting pipeline bridges TypeScript source authored at build time with JavaScript that runs inside Minecraft Bedrock at runtime. It is orchestrated by ScriptPipeline (src/script/pipeline.ts) and runs as part of addon.generate().


 addon.script(callback)          → ScriptPipeline.addBlock()
 addon.scriptFile(path)          → ScriptPipeline.addFile()
 block.addCustomComponent(...)   → ScriptPipeline.addRawSource()
 addon.addCustomCommand(...)     → ScriptPipeline.addRawSource()
 constants injection             → ScriptPipeline.addRawSource()
                 │
                 ▼
         ScriptPipeline.process()
                 │
         ┌───────┴──────────┐
         │                  │
         ▼                  ▼
   Extractor.ts        FileBundleOptions
   (inline blocks)     (file-based)
         │                  │
         ▼                  ▼
   assembleScriptFile()  read files
         │                  │
         └────────┬──────────┘
                  ▼
           Bun.build()
           @minecraft/* → external
                  │
                  ▼
        behavior_pack/scripts/main.js
                    

Extraction

Inline script blocks use Function.prototype.toString() to obtain the callback's source text at build time. The extractor (src/script/extractor.ts) then:

  1. Strips the outer function wrapper to isolate the body.
  2. Detects ctx.server, ctx.serverUi, etc. with word-boundary matching.
  3. Extracts destructured imports (const { world } = ctx.server) and generates proper ES import statements.
  4. Rewrites inline ctx.server.world references into direct variable references.
  5. Merges multiple blocks into a single ES module with deduplicated imports.
// Input (callback at build time):
addon.script((ctx) => {
  const { world } = ctx.server;
  world.afterEvents.playerJoin.subscribe((event) => {
    world.sendMessage(event.playerName + ' joined!');
  });
});

// Extracted and rewritten:
import { world } from '@minecraft/server';

world.afterEvents.playerJoin.subscribe((event) => {
  world.sendMessage(event.playerName + ' joined!');
});

Bundling

The bundler (src/script/bundler.ts) uses Bun.build() with the following configuration:

OptionValueReason
target"browser"Closest to Minecraft's QuickJS runtime
format"esm"Bedrock requires ES modules
external["@minecraft/*"]Provided by the game engine at runtime — must not be bundled
minifyconfigurableControlled via addon.setMinify() or ScriptPipeline

The assembled source is written to a temporary .ts file, passed to Bun.build(), the output is read, and the temp file is cleaned up. The final JS is written to behavior_pack/scripts/main.js.

Manifest Integration

After the pipeline processes scripts, BehaviorPack.addPackSpecificModules() adds two things to the manifest:

  1. A "type": "script" module with "language": "javascript" and "entry": "scripts/main.js".
  2. A dependencies array entry for each detected @minecraft/* module with its semver string version (e.g. "1.17.0").

Script module dependencies use semver strings (not version tuples) — this is the correct format per the official Bedrock pack manifest specification. The script module UUID is persisted to jepia.config.json so Minecraft recognizes the same pack across rebuilds.

Execution Model

Per the Minecraft Script API documentation, scripts have three execution contexts with different privilege levels:

ContextWhenPrivilege
Early (startup)system.beforeEvents.startup.subscribe()Registration only — blocks, items, commands. No world writes.
Restricted (beforeEvents)Before-event handlersRead-only. Use system.run() to defer writes.
DefaultAfter-events, system.run(), system.runInterval()Full API access.

Jepia respects these contexts automatically:

  • Custom component and custom command registrations are always placed inside system.beforeEvents.startup.subscribe() in the generated code.
  • Inline addon.script() blocks run at the default (module-level) context. Use system.run() inside event handlers when you need to modify world state from a before-event.

Source File Layout

src/script/
├── types.ts            — ScriptModuleId, ScriptOptions, ScriptBlock, ScriptDependency,
│                         CustomCommandConfig, CustomCommandRegistration, constants
├── extractor.ts        — Function.toString() extraction, ctx rewriting, import generation
├── bundler.ts          — Bun.build() wrapper, @minecraft/* externals, file bundling
├── pipeline.ts         — Orchestrator: collect → extract → merge → bundle → output
├── custom-components.ts — Startup registration code generation for custom components
├── custom-commands.ts  — Startup registration code generation for custom commands
├── constants-generator.ts — ITEMS / BLOCKS / ENTITIES export generation
└── index.ts            — Public exports

5c. Visual & Client Systems

Phase 9 adds five resource-pack component types covering Minecraft's client-side visual and audio pipeline. Unlike the behavior-pack components from Phases 7–8, all five write exclusively to the resource pack.

Component Overview

ComponentOutput pathformat_version
Particleresource_pack/particles/<id>.json1.10.0
Animationresource_pack/animations/<id>.json1.8.0
AnimationControllerresource_pack/animation_controllers/<id>.json1.10.0
Attachableresource_pack/attachables/<id>.json1.8.0
ClientBiomeresource_pack/biomes_client/<id>.json1.0.0

Identifier Derivation

Animation and controller identifiers are derived automatically from the component id to match Minecraft's naming conventions:

Component id          → JSON dict key
"myaddon:walk"        → "animation.myaddon.walk"        (Animation)
"myaddon:base"        → "controller.animation.myaddon.base"  (AnimationController)

Both can be overridden with setAnimationId() / setControllerId().

Source File Layout

src/component/
├── particle.ts           — Particle (format_version 1.10.0; emitter rate/lifetime/shape, billboard, tinting, curves)
├── animation.ts          — Animation (format_version 1.8.0; bone keyframes, timeline events)
├── animation-controller.ts — AnimationController (format_version 1.10.0; state machine, Molang transitions)
├── attachable.ts         — Attachable (format_version 1.8.0; materials, textures, geometry, scripts)
└── client-biome.ts       — ClientBiome (format_version 1.0.0; water/sky/fog/grass/foliage colors)

ResourcePack Storage

Particles and animations were already stored in ResourcePack maps. Phase 9 adds three new maps:

animationControllers: Map<string, object>  // addAnimationController / getAnimationControllers
attachables:         Map<string, object>  // addAttachable        / getAttachables
clientBiomes:        Map<string, object>  // addClientBiome       / getClientBiomes

All three are written to disk in addon.generate() after the existing particle write step, using the standard writeComponent() helper.

6. File Output Structure

When addon.generate() is called, the following directory tree is produced.


my_addon/
├── behavior_pack/
│   ├── manifest.json
│   ├── items/
│   │   └── *.json
│   ├── blocks/
│   │   └── *.json
│   ├── entities/
│   │   └── *.json
│   ├── recipes/
│   │   └── *.json
│   ├── loot_tables/
│   │   └── *.json
│   ├── spawn_rules/
│   │   └── *.json
│   └── trading/
│       └── *.json
├── resource_pack/
│   ├── manifest.json
│   ├── textures/
│   │   ├── *.png
│   │   └── *.texture_set.json
│   ├── models/
│   │   └── *.geo.json
│   ├── materials/
│   │   └── *.material
│   ├── render_controllers/
│   │   └── *.render_controllers.json
│   └── entity/
│       └── *.entity.json
└── jepia.config.json
                    

Each component's toJSON() method produces the exact JSON structure expected by Minecraft Bedrock. Jepia writes files to the correct subfolder automatically based on component type.

7. Validation Strategy

Validation runs at three levels, catching errors before files are written.

LevelToolWhat It Checks
ComponentAJVEach component's JSON output against its Bedrock JSON schema
PackAJVManifest structure, required fields, version format
AddonCustomCross-pack consistency (e.g. entity in BP has matching client def in RP)

Bundled Schemas

SchemaValidates
itemItem component JSON
blockBlock component JSON
entityEntity behavior definition
material.material file structure
render_controllerRender controller JSON
texture_setPBR texture set JSON
// Validation happens automatically during generate()
// or can be triggered manually:
const errors = sword.validate();
if (errors.length > 0) {
  console.error('Validation failed:', errors);
}

8. Configuration System

Jepia uses a jepia.config.json (or .ts) file validated with Zod schemas.

OptionTypePurpose
outputstringOutput directory path
packagingFormat"mcaddon" | "mcpack"Archive format for distribution
validationbooleanEnable/disable schema validation
minifybooleanMinify JSON output
uuidobjectPersisted UUIDs for stable pack identity across rebuilds
{
  "output": "./build",
  "packagingFormat": "mcaddon",
  "validation": true,
  "minify": false,
  "uuid": {
    "behaviorPack": "a1b2c3d4-...",
    "behaviorModule": "e5f6a7b8-...",
    "resourcePack": "c9d0e1f2-...",
    "resourceModule": "a3b4c5d6-..."
  }
}

UUIDs are generated on first build and persisted to jepia.config.json so that Minecraft recognizes the same pack across updates rather than treating each build as a new add-on.

9. Packaging

Jepia uses Archiver to create distributable ZIP archives in Minecraft's expected formats.

FormatExtensionContents
Combined.mcaddonBoth behavior pack and resource pack in one archive
Individual.mcpackA single pack (behavior or resource) per archive
// Package after generation
await addon.generate('./output');
// produces: output/MyAddon.mcaddon
// or: output/MyAddon_BP.mcpack + output/MyAddon_RP.mcpack

10. Design Decisions

DecisionRationale
Auto-create packs on Addon instantiation Every add-on needs at least one pack. Eliminates boilerplate and prevents forgetting to create a pack.
Fluent API with method chaining Reduces verbosity. Component configuration reads like a declarative spec rather than imperative setup code.
Schema-driven validation with AJV Catches errors at build time rather than in-game. Schemas stay close to Mojang's official format definitions.
Bun runtime Native TypeScript execution without a compile step. Fast file I/O and built-in test runner.
UUID persistence Minecraft identifies packs by UUID. Changing UUIDs between builds causes duplicate pack entries and breaks world references.
Component base class Enforces a uniform contract (validate(), toJSON()) across all content types, making the system extensible.
Layered rendering pipeline Mirrors Bedrock's own rendering stack. Each layer (texture → texture set → material → render controller) maps 1:1 to a Bedrock concept.
Function.prototype.toString() for script extraction Allows users to write Script API code in the same TypeScript file as JSON components, with IDE type-checking. The extractor handles the build/runtime boundary transparently.
Bun.build() as the script bundler Zero extra dependencies — Bun is already the runtime. Provides native TypeScript compilation, external support for @minecraft/*, and fast I/O.
Custom Components V2 auto-registration Eliminates the need for users to manually write startup registration code. Jepia generates the correct system.beforeEvents.startup.subscribe() block from handler functions automatically.
// The fluent API in action:
const block = new Block('Reactor Core', 'techmod:reactor_core')
  .setCategory('construction')
  .addComponent('minecraft:destructible_by_mining', {
    seconds_to_destroy: 5.0
  })
  .addComponent('minecraft:light_emission', 14)
  .addComponent('minecraft:map_color', '#00ff88');
// Each method returns `this`, enabling chaining.

Jepia — a TypeScript framework for Minecraft Bedrock Add-On generation.