Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tables

Tables are source-backed row collections. A table schema declares the table mode, source location, fields, and optional indexes.

Modes

ModeShapeTypical Use
mapRows keyed by one field.Items, quests, levels, buffs.
listOrdered rows without keyed lookup.Drop entries, weighted pools, ordered steps.
singletonOne row.Global settings, tuning constants.
[[tables]]
name = "Item"
mode = "map"
key = "id"

[[tables.fields]]
name = "id"
type = "i32"

For map tables, key names the table’s primary key field. Sora uses it for row uniqueness, generated lookup APIs, Excel template hints, and ref<Table.key> validation.

Source

[tables.source]
format = "xlsx"
file = "Core.xlsx"
sheet = "Item"

format can be omitted when the project or command provides a default source format. file is resolved under the command’s --data-root during export and validation.

Built-in source formats are xlsx, csv, toml, json, and yaml. JSON and YAML table files are arrays of row objects:

[
  { "id": 1001, "name": "Iron Sword" },
  { "id": 1002, "name": "Health Potion" }
]

For JSON and YAML, file can also point to a directory. In that case Sora recursively reads every matching .json, .yaml, or .yml file as one row object, sorted by path.

Indexes

Indexes are extra lookup paths on a table. They are different from the key of a mode = "map" table:

ConceptPurpose
table keyThe primary key. A map table uses it to keep rows unique and to generate the main get(id) lookup.
[[tables.indexes]]Additional lookup paths, such as lookup by name, grouping by type, or finding drops by stage.

For example, an Item table can use id as its primary key:

[[tables]]
name = "Item"
mode = "map"
key = "id"

[[tables.fields]]
name = "id"
type = "i32"

[[tables.fields]]
name = "name"
type = "string"

[[tables.fields]]
name = "item_type"
type = "enum<ItemType>"

Add a unique index when another field should also identify at most one row:

[[tables.indexes]]
name = "by_name"
fields = ["name"]
unique = true

Example data:

idnameitem_type
1001Iron SwordWeapon
1002Wood ShieldArmor

unique = true means name cannot repeat. Generated code for targets that support the index can expose a helper similar to get_by_name("Iron Sword"), returning one row or no row.

Use a non-unique index when a key can match many rows:

[[tables.indexes]]
name = "by_item_type"
fields = ["item_type"]
unique = false

Example data:

idnameitem_type
1001Iron SwordWeapon
1002Bronze AxeWeapon
2001Wood ShieldArmor

unique = false means one key can match several rows. Generated code for targets that support the index can expose a helper similar to get_by_item_type(ItemType::Weapon), returning the matching rows.

fields is a list, so a unique index can also express combined uniqueness:

[[tables.indexes]]
name = "by_world_stage"
fields = ["world", "stage"]
unique = true

This requires each (world, stage) pair to be unique. For example, (1, 1) can appear once, while (1, 2) is a different key. Current generated lookup helpers mainly support single-field indexes on non-singleton tables; combined indexes are most useful for validation today.

Validation

Sora validates table rows after loading source data:

  • non-optional fields must be present unless a default exists;
  • key fields must be unique for map tables;
  • enum values must be valid;
  • references must point to existing rows;
  • numeric ranges and length ranges must pass;
  • parser output must match the declared field type.