Type
local Operator = require(game:GetService("ServerStorage").Operator)
local Type = Operator.Type
-- or
local Type = Operator.TypeBuilds an argument type: something that turns one piece of typed text into a real value, and optionally offers autocomplete suggestions. One definition serves both sides. The client uses it for suggestions and inline validation, the server re-runs the same transform authoritatively at execution time.
Functions
Type.new
Type.new<T>(name: string): Builder<T>return Type.new("itemId")
:transform(function(raw, executor)
local item = ItemConfig.Items[raw]
if item == nil then
return false, `"{raw}" is not an item`
end
return true, item
end)
:suggestions(function(query, executor)
return ItemConfig.NamesMatching(query)
end, { queryDependent = true })
:build()Type.runTransform
Type.runTransform(argType: AnyArgType, raw: any, executor: Player?): (boolean, any)Runs a type's transform with its timeout applied. A transform that raises, times out, or returns nothing at all is reported as a normal rejection with a message. It never escapes as an error.
local ok, value = Type.runTransform(Types.Player, "@me", executor)
if not ok then
warn(value)
endType.runSuggestions
Type.runSuggestions(argType: AnyArgType, query: string, executor: Player?): (boolean, { string })Runs a type's suggestions with its timeout applied.
Builder<T>
:transform
:transform(fn: TransformFn): Builder<T>Sets the transform. Return two values, never a wrapper object:
return true, value -- success
return false, "message" -- rejection, with a message shown to the playerA transform must be pure
It may read game state and it may yield, but it must not change anything: no writing data, no firing remotes, no mutating shared tables, no side effects of any kind.
The console runs your transform locally to show inline errors as the player types, and to keep the render path from ever blocking it resumes the transform in a coroutine and closes it the moment it yields. A transform that yields is abandoned partway through. If it had already mutated something, that mutation is left half-applied, on every keystroke.
The authoritative run happens on the server, which always runs the transform to completion. Put side effects in the command's run, where they belong.
:suggestions
:suggestions(fn: SuggestionsFn, options: SuggestionOptions?): Builder<T>May return a list directly or a promise-like value (anything with andThen). Requests are debounced and results cached.
:extends
:extends(base: AnyArgType): Builder<T>Runs the base transform first and feeds its result forward, so you never reimplement parsing. If the base rejects, the chain short-circuits and the base message is returned.
local PositiveInteger = Type.new("positiveInteger")
:extends(Types.Integer)
:transform(function(value)
if value <= 0 then
return false, "expected a number above zero"
end
return true, value
end)
:build()A type that defines no suggestions of its own inherits the base ones, along with the base caching options.
:timeout
:timeout(seconds: number): Builder<T>Overrides the transform timeout for this type. The default is 5 seconds.
:build
:build(): ArgType<T>Returns the finished type.
Types
ArgType<T>
type ArgType<T> = {
name: string,
transform: TransformFn,
suggestions: SuggestionsFn?,
suggestionsQueryDependent: boolean,
transformTimeout: number,
suggestionTtl: number,
suggestionDebounce: number,
suggestionTimeout: number,
}AnyArgType is ArgType<any>.
TransformFn
type TransformFn = (value: any, executor: Player?) -> (boolean, any)executor is the player running the command, or nil when there is not one.
SuggestionsFn
type SuggestionsFn = (query: string, executor: Player?) -> anySuggestionOptions
type SuggestionOptions = {
ttl: number?,
debounce: number?,
timeout: number?,
queryDependent: boolean?,
}| Option | Default | Meaning |
|---|---|---|
ttl | 10 | seconds a cached suggestion list stays fresh |
debounce | 0.15 | seconds of quiet before a fetch is issued |
timeout | 5 | seconds before a suggestion fetch is abandoned |
queryDependent | false | whether the result depends on what has been typed |
queryDependent is the one worth understanding. By default a type ignores query and returns its whole candidate list; the cache stores one entry per type and filters by prefix on the way out, so typing ten characters costs one fetch. Set it to true when the type narrows results itself, such as a DataStore lookup over thousands of rows, and the cache keys on the query and passes results through unfiltered.