Complete and featureful typescript definitions for the Factorio modding lua API, for use with TypescriptToLua.
This project aims to provide type definitions that are as complete as possible.
To use in your TypescriptToLua project:
-
Install this package:
npm install --save-dev typed-factorio
Note: When types are updated for a new factorio version, you will need to update this package.
-
Add types for the Factorio stages used to your
tsconfig.json
undercompilerOptions > types
. The available stages are"typed-factorio/settings"
,"typed-factorio/prototype"
, and"typed-factorio/runtime"
.
Example:
// in tsconfig.json
{
"compilerOptions": {
+ "types": [ "typed-factorio/runtime" ]
}
}
Each stage will define the global variables used in that stage. You can include multiple stages, but there are some caveats. For more info, see Using multiple stages in the same project.
Some more info on using types.
No matter which stage(s) are selected, type definitions for all stages are available in the modules "factorio:settings"
, "factorio:prototype"
, and "factorio:runtime"
:
import { BoolSettingDefinition } from "factorio:settings"
import { ItemPrototype } from "factorio:prototype"
import { LuaEntity } from "factorio:runtime"
You can also include just "typed-factorio"
in your tsconfig's types
. This will define only global variables that are available to all stages.
The storage
table (in the runtime stage) can have any shape, so it is not defined here. Instead, you can define it yourself:
- Add
declare const storage: <Your type>
in a.d.ts
file. Make sure this file is included by your tsconfig! - Add
declare global { const storage: <Your type> }
in a.ts
file included in your project. - Add
declare const storage: {...}
to each file where needed. This way, you can define only properties that each file specifically uses.
There are types for the following Factorio lualib modules:
util
mod-gui
These can be imported as modules:
import * as util from "util"
const foo = util.copy(bar)
If you wish to see types for more lualib modules, feel free to open an issue or pull request.
In the settings and prototype stages, the data
global variable is available.
For performance reasons, data.extend()
is by default loosely typed.
To get full strict type checking, you can use one of the following methods:
// Use `satisfies` to check types:
data.extend([
{
type: "ammo-category",
name: "foo",
} satisfies AmmoCategory,
{
type: "item",
name: "bar",
// ...other fields
} satisfies ItemPrototype,
])
// List types used as a type argument to `extend`:
data.extend<AmmoCategory | ItemPrototype>([
{
type: "ammo-category",
name: "foo",
},
{
type: "item",
name: "bar",
// ...other fields
},
])
// Use types on separate variables:
const fooCategory: AmmoCategory = {
/* ... */
}
const barItem: ItemPrototype = {
/* ... */
}
data.extend([fooCategory, barItem])
Every Factorio loading stage declares different global variables. To add types for multiple Factorio stages, you have a few options:
- Add multiple stages to the "types" field, e.g.
"types": ["typed-factorio/prototype", "typed-factorio/runtime"]
. This will define global variables for all stages selected. With this option, take care that you only use global variables available in the stages the code is run. - Add only the runtime stage, then manually declare other global variables in files that use them. There are types in
"factorio:common"
to allow this:// -- For the prototype stage -- import { PrototypeData, ActiveMods, FeatureFlags } from "factorio:common" declare const data: PrototypeData declare const mods: ActiveMods declare const feature_flags: FeatureFlags // The `settings` global variable is already declared in the runtime stage. // However, in the prototype stage _only_ `settings.startup` are available.
// -- For the settings stage -- import { SettingsData, ActiveMods, FeatureFlags } from "factorio:common" declare const data: SettingsData declare const mods: ActiveMods declare const feature_flags: FeatureFlags
- Use a separate
tsconfig.json
for each stage. In eachtsconfig.json
, add only files in that stage to the"include"
field, e.g.include: ["src/control.ts"]
for the runtime stage. However, this means you need to runtstl
separately for each stage, and files shared by multiple stages will be compiled multiple times.
Here is some info on type features that you may find useful:
The nil
type is equivalent to undefined
.
A class attribute is marked as possibly nil if the read type is possibly nil
. For properties where nil
is possible on write, but not read, you can use undefined!
or myNullableValue!
, e.g. controlBehavior.parameters = undefined!
.
Parameter tables with variants (having "additional attributes can be specified depending on type ...") are defined as a union of all variants. The type for a specific variant is prefixed with the variant name (e.g. AmmoDamageTechnologyModifier
), or prefixed with "Other" for variants without extra properties (e.g. OtherTechnologyModifier
).
Event IDs (defines.events
) hold type info on their corresponding event data type and filters, which is used by various methods in script
.
You can pass a type parameter to script.generate_event_name<T>()
, and it will return a CustomEventId
that includes type info.
You can optionally enable type-checking for custom input names (for script.on_event
and CustomInputPrototype
).
To do so, specify names by extending the CustomInputNames interface like so:
declare module "factorio:common" {
export interface CustomInputNames {
"my-custom-input": any
}
}
script.on_event("my-custom-input", () => {}) // type-checked
// script.on_event("my-customm-input", () => {}) // mispelled, will error
The type CustomInputName
(not plural) will be a union of strings, for all custom input names.
If not specified like above, CustomInputName
defaults to just string
.
A few classes that have an index operator, a length operator, and have an array-like structure, which will subclass from (Readonly)Array
. These are LuaInventory
, LuaFluidBox
, LuaTransportLine
.
This allows you to use these classes like arrays, e.g. having array methods and .length
translating to the lua length operator. However, this means like typescript arrays, they are 0-indexed, not 1-indexed.
For concepts that can take a table-or-array form, the main type (e.g. MapPosition
) defines the table form, and a type suffixed with Array
(e.g. MapPositionArray
) defines the array form.
Concepts in a "read" position are in table form. Concepts in a "write" position may be in either table or array form (e.g. MapPosition | MapPositionArray
).
Concepts that include table-or-array concepts may have an additional Write
variant (e.g. ScriptArea
, ScriptAreaWrite
).
Some classes have attributes that only work for particular subclasses (e.g. LuaEntity.recipe only works if this is crafting-machine). For these classes, there are more specific types you can optionally use:
- A "Base" type (e.g.
BaseEntity
) which contains only members usable by all subclasses - Subclass types, e.g.
CraftingMachineEntity
, which extends the base type with members specific to that subclass.
The original class name (e.g. LuaEntity
) contains attributes for all subclasses.
For stricter types, use the Base
type generally, and the specific subclass type when needed.
You can also create your own type-narrowing functions:
function isCraftingMachineEntity(entity: BaseEntity): entity is CraftingMachineEntity {
return entity.type === "crafting-machine"
}
LuaGuiElement
is broken up into a discriminated union, for each gui element type. Individual gui element types can be referred to by <Type>GuiElement
, e.g. ButtonGuiElement
.
Similarly, GuiSpec
(the table passed to LuaGuiElement.add
), is also a discriminated union. The type for a specific GuiSpec is <Type>GuiSpec
, e.g. ListBoxGuiSpec
. LuaGuiElement.add
will return the appropriate gui element type corresponding to the GuiSpec type passed in.
This is done both to provide more accurate types, and for possible integration with JSX.
If you find this project useful, consider tipping me on Kofi!