How should I organize my exported types to be maintainable and convenient to access?

TLDR;

Title: How should I organize my exported types to be maintainable and convenient to access?

Context

Right now, I have a single module script “Types" that holds all my types so that it is convenient to access any type from any script. So if I have a type Action representing some class and a type ActionMode representing some enumeration I can simply write code like this to access it:

local Action = require(Path.To.Action)
local Types = require(Path.To.Types)

local actionMode: Types.ActionMode = Types.ActionModes.default
local action: Types.Action = Action.new(actionMode)

This allows me to conveniently distinguish types from objects/classes and also have type-checked enumerations by centralizing everything under the “Types" module script. The reason I chose not to put the Action type under the Action module script is that in my code I likely have other types related to Action (for example’s sake, say ActionMode) and in order to get those I would have to write:

local Action = require(Path.To.Action)

local actionMode: Action.ActionMode = Action.ActionModes.default
local action: Action.Action = Action.new(actionMode)

Problem

While I think this looks fine for the enum ActionMode, it feels odd to write Action.Action. The issue with my current approach of having a single “Types” module script is that all the types are just put together in there, making it unwieldy and messy and I want to refactor before it gets any worse.

Plus if I have other module scripts whose parent is the “Action” module script and they have their own types, I would have to either re-define the exported types in the “Action” module in addition to in the child modules of “Action” if they themselves use said types or require both the “Action” module and the child modules. This is because chain-requiring does not give access to exported types in the sub-modules.

Attempted Solution

Before learning that chain-requiring is not supported by the type-checker, I thought it would be nice to have that single “Types” module which requires children modules that serve to group related types, thereby keeping the system maintainable and still easy-to-access by just having to write require(Path.To.Types) at the top of each script. But as I said the type-checker does not support this kind of chain requiring.

Anyone have any ideas?

1 Like

To my knowledge, there is currently no alternative solution other than re-exporting it using the current Lua solver. You can do this using a chatbot to generate your import block. I’m the original poster for one of the links, and I haven’t found a solution to that either.

local Action = require(Path.To.Action)
local Types = require(Path.To.Types)

export type ActionMode = Types.ActionMode
export type Action = Types.Action
...
-- import more types using an LLM to generate

local actionMode: ActionMode = Types.ActionModes.default
local action: Action = Action.new(actionMode)

You have to do this for every script, unfortunately, which is not very well scalable, but that’s what we’re given for now.

I’ve designed a couple of modules to be autocomplete friendly and in general it’s either you go with Action.Action or you generate export code blocks to push your types up the chain.

2 Likes

Thank you for the response! I think I’ll go with Action.Action for now because even if it’s not as pretty it lets me avoid export chains and still organize types by grouping related ones together into the same modules; I’ll mark your response as the answer for now

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.