Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Ukendio/jecs/llms.txt

Use this file to discover all available pages before exploring further.

jecs provides strong type safety through TypeScript definitions and Luau type checking. Understanding these types helps you write safer, more maintainable ECS code.

Core Types

Entity

A unique identifier for an entity in the world. The generic type T defines the data type when this entity is used as a component.
export type Entity<TData = unknown> = number & {
    readonly __nominal_Entity: unique symbol
    readonly __type_TData: TData
}
export type Entity<T = nil> = { __T: T }
Usage:
type Position = { x: number, y: number }
const Position: Entity<Position> = world.component<Position>()

type Health = { value: number, max: number }
const Health: Entity<Health> = world.component<Health>()
type Position = { x: number, y: number }
local Position = world:component() :: jecs.Entity<Position>

type Health = { value: number, max: number }
local Health = world:component() :: jecs.Entity<Health>

Tag

An entity with no associated data when used as a component. Tags are markers that indicate the presence of a characteristic without storing values.
export type Tag = Entity<TagDiscriminator>
-- Tags are entities without data
local Alive = world:component() :: jecs.Entity<nil>
Usage:
const Alive: Tag = world.component()
const Dead: Tag = world.component()

world.add(entity, Alive)
if (world.has(entity, Dead)) {
    // Handle dead entity
}
local Alive = world:component()
local Dead = world:component()

world:add(entity, Alive)
if world:has(entity, Dead) then
    -- Handle dead entity
end

Pair

A pair of entities used for relationships. The first entity (predicate) describes the relationship type, and the second (object) is the target.
export type Pair<P = unknown, O = unknown> = number & {
    readonly __nominal_Pair: unique symbol
    readonly __pred: P
    readonly __obj: O
}
export type Pair<First=any, Second=any> = ecs_pair_t<Entity<First>, Entity<Second>>
Usage:
import { pair } from "@jecs/core"

const ChildOf = world.component()
const parent = world.entity()
const child = world.entity()

// Create a ChildOf(parent) relationship
const childOfParent = pair(ChildOf, parent)
world.add(child, childOfParent)

// Get the parent
const retrievedParent = world.target(child, ChildOf)
local jecs = require("@jecs")

local ChildOf = jecs.ChildOf
local parent = world:entity()
local child = world:entity()

-- Create a ChildOf(parent) relationship
world:add(child, jecs.pair(ChildOf, parent))

-- Get the parent
local retrievedParent = world:target(child, ChildOf)

Id

A component identifier that can be either a single Entity or a Pair of Entities.
export type Id<TData = unknown> = 
    | Entity<TData> 
    | Pair<TData, unknown> 
    | Pair<TagDiscriminator, TData>
export type Id<T = any> = { __T: T }
Usage:
function removeComponent(entity: Entity, id: Id) {
    world.remove(entity, id)
}

removeComponent(player, Position) // Remove entity
removeComponent(child, pair(ChildOf, parent)) // Remove pair

Component

An alias for Entity used to indicate component entities.
export type Component<T> = Entity<T>
export type Component<T=any> = { __T: T }

Query Types

Query

A query object for searching entities with specific components.
export type Query<T extends Id[]> = {
    iter(): IterableFunction<[Entity, ...InferComponents<T>]>
    cached(): CachedQuery<T>
    with(...components: Id[]): Query<T>
    without(...components: Id[]): Query<T>
    archetypes(): Archetype<T>[]
    has(entity: Entity): boolean
} & Iterable<[Entity, ...InferComponents<T>]>
export type Query<T...> = typeof(setmetatable(
    {} :: {
        iter: Iter<T...>,
        with: ((Query<T...>, ...Component) -> Query<T...>),
        without: ((Query<T...>, ...Component) -> Query<T...>),
        archetypes: (Query<T...>) -> { Archetype },
        cached: (Query<T...>) -> Cached_Query<T...>,
        has: (Query<T...>, Entity) -> boolean,
    },
    {} :: { __iter: Iter<T...> }
))

CachedQuery

A cached query for efficient repeated iterations.
export type CachedQuery<T extends Id[]> = {
    iter(): IterableFunction<[Entity, ...InferComponents<T>]>
    archetypes(override?: boolean): Archetype<T>[]
    has(entity: Entity): boolean
    fini(): void
} & Iterable<[Entity, ...InferComponents<T>]>
export type Cached_Query<T...> = typeof(setmetatable(
    {} :: {
        iter: Cached_Query_Iter<T...>,
        archetypes: (Cached_Query<T...>, override: boolean?) -> { Archetype },
        has: (Cached_Query<T...>, Entity) -> boolean,
        fini: (Cached_Query<T...>) -> (),
    },
    {} :: { __iter: Cached_Query_Iter<T...> }
))

Archetype Types

Archetype

Represents a unique combination of components. Entities with the same components share an archetype.
export type Archetype<T extends Id[]> = {
    id: number
    types: Entity[]
    type: string
    entities: Entity[]
    columns: Column<unknown>[]
    columns_map: { [K in T[number]]: Column<InferComponent<K>> }
}
export type Archetype = {
    id: number,
    types: Ty,
    type: string,
    entities: { Entity },
    columns: { Column },
    columns_map: { [Component]: Column }
}
Properties:
  • id: Unique archetype identifier
  • types: Array of component entities in this archetype
  • type: String representation of the archetype
  • entities: All entities belonging to this archetype
  • columns: Component data storage
  • columns_map: Map from component to its column

Column

A storage array for component data of a specific type.
export type Column<T> = T[]
type Column = { any }

Utility Types

InferComponent

Extracts the data type from an Entity or Pair.
export type InferComponent<E> = 
    E extends Entity<infer D> ? D
    : E extends Pair<infer P, infer O> ? 
        P extends TagDiscriminator ? O : P
    : never
Usage:
type Position = { x: number, y: number }
const Position: Entity<Position> = world.component<Position>()

type PositionData = InferComponent<typeof Position> // { x: number, y: number }

InferComponents

Maps an array of Ids to their inferred component types.
type InferComponents<A extends Id[]> = { [K in keyof A]: InferComponent<A[K]> }

Built-in Components

jecs provides several built-in components and tags:

Hooks

export declare const OnAdd: Entity<(e: Entity, id: Id, data: any) => void>
export declare const OnRemove: Entity<(e: Entity, id: Id, deleted?: true) => void>
export declare const OnChange: Entity<(e: Entity, id: Id, data: any) => void>

Relationships

export declare const ChildOf: Tag
export declare const Wildcard: Entity
export declare const w: Entity // Alias for Wildcard

Cleanup Policies

export declare const OnDelete: Tag
export declare const OnDeleteTarget: Tag
export declare const Delete: Tag
export declare const Remove: Tag

Other

export declare const Component: Tag
export declare const Name: Entity<string>
export declare const Exclusive: Tag
export declare const Rest: Entity

Helper Functions

pair()

Creates a composite key (pair) from two entities.
export function pair<P, O>(pred: Entity<P>, obj: Entity<O>): Pair<P, O>
local function pair(pred: Entity, obj: Entity): Pair

IS_PAIR()

Checks if an ID is a pair.
export function IS_PAIR(value: Id): value is Pair

pair_first()

Gets the first entity (predicate) of a pair.
export function pair_first<P, O>(world: World, pair: Pair<P, O>): Entity<P>

pair_second()

Gets the second entity (object) of a pair.
export function pair_second<P, O>(world: World, pair: Pair<P, O>): Entity<O>

ECS_ID()

Extracts the ID from an entity, removing generation information.
export function ECS_ID(entity: Entity): number

ECS_GENERATION()

Extracts the generation number from an entity.
export function ECS_GENERATION(entity: Entity): number

Type Safety Examples

TypeScript

type Position = { x: number, y: number }
type Velocity = { dx: number, dy: number }

const Position = world.component<Position>()
const Velocity = world.component<Velocity>()

const entity = world.entity()

// Type-safe set
world.set(entity, Position, { x: 0, y: 0 })
// world.set(entity, Position, { x: "invalid" }) // Error!

// Type-safe get
const pos = world.get(entity, Position) // Position | undefined
if (pos) {
    console.log(pos.x, pos.y) // OK
    // console.log(pos.z) // Error: Property 'z' does not exist
}

// Type-safe query
for (const [e, position, velocity] of world.query(Position, Velocity)) {
    position.x += velocity.dx // All typed correctly
    position.y += velocity.dy
}

Luau

type Position = { x: number, y: number }
type Velocity = { dx: number, dy: number }

local Position = world:component() :: jecs.Entity<Position>
local Velocity = world:component() :: jecs.Entity<Velocity>

local entity = world:entity()

-- Type-safe set
world:set(entity, Position, { x = 0, y = 0 })

-- Type-safe get
local pos = world:get(entity, Position) :: Position?
if pos then
    print(pos.x, pos.y) -- OK
end

-- Type-safe query
for entity, position, velocity in world:query(Position, Velocity) do
    position.x += velocity.dx
    position.y += velocity.dy
end

Build docs developers (and LLMs) love