jess.worker: { spawn, send }Also require('@jess/worker'). A worker is a second Luau state the ScriptDomain owns, with its own globals, its own modules, and its own garbage collector, able to run while the domain's own code does. It sees nothing of the domain but the messages it is sent. Under jess run there is no domain, so spawn errors with worker.spawn: needs a ScriptDomain, and none owns this runtime; jess run, jess test, and command targets run scripts outside one.
jess.worker.spawn§
jess.worker.spawn(options: WorkerOptions) -> WorkerSpawns a worker and returns its handle. Needs the worker.spawn capability.
source: the worker module's Luau source. It is a chunk that returns the message handler,function(message) ... return reply end; the handler is called once per message, and the value it returns is the reply (nilis no reply).name: for diagnostics and profiles; by default, wherespawnwas called.capabilities: the names the worker holds, a subset of the calling context's. By default it inherits them all.shareServices:truegives the worker the domain's services (under a host, its live input). Off by default: a worker exists to run apart from the domain's state, and its capabilities still say what it may do with what it sees.
The worker's runtime comes from the same plan as the domain's, so a worker has the whole jess API and the same package exports, with state of its own.
- Errors:
unknown worker.spawn option '<key>': expected capabilities, name, shareServices, or source;worker.spawn needs source, the worker module's Luau source;capability 'worker.spawn' denied to <domain> ...;<domain> cannot delegate capabilities it does not hold: <names>; inside a worker,worker.spawn is for a domain's own code.
--!strict
local worker = require('@jess/worker')
local summer = worker.spawn {
name = 'summer',
capabilities = {},
source = [[
return function(message)
local total = 0
for _, value in message.values do
total += value
end
return { total = total }
end
]],
}
summer:send { values = { 1, 2, 3 } }
local reply = summer:receive(20)
print(reply.total)
summer:join()
A worker whose module does not load, does not return a function, or raises in its handler reports a structured failure to the host, attributed to the domain and worker, and (by default) stops; the domain is untouched. A worker's handler runs inside the budget the host gave it.
Worker§
type Worker = { send, receive, close, join }Worker:send§
worker:send(message: unknown) -> ()Queues a message for the worker's handler. Messages arrive in order. At most 1,024 wait for the handler at once: a domain that sends faster than its worker handles is told so, instead of queueing without bound.
Only transferable values cross: nil, booleans, numbers, integers, strings, vectors, buffers, tables of those with string or number keys, and resource handles. Anything else is an error naming where it was, and nothing is sent.
- Errors:
message.<path>: a <type> can't cross a worker or event boundary;the worker is stopped; it takes no more messages;the worker's queue is full: 1024 messages wait for its handler; receive its replies, or send fewer.
--!strict
local worker = require('@jess/worker')
local echo = worker.spawn { source = 'return function(message) return message end' }
echo:send { name = 'ada', bytes = buffer.fromstring('abc'), at = vector.create(1, 2, 3) }
local reply = echo:receive(20)
print(reply.name, buffer.tostring(reply.bytes), reply.at)
A buffer arrives as a fresh buffer of the same bytes: writing the copy changes nothing on the other side.
Worker:receive§
worker:receive(timeoutSeconds: number?) -> unknownThe worker's next reply, waiting up to timeoutSeconds for one; without a timeout it does not wait. The wait never outlasts the execution time limit of the code that waits: when the limit runs out first, the call is stopped with the limit's error, as a loop would be. nil when there is none in that time, or when the worker has ended with nothing left to say. A failure the worker reported comes back as an error, once.
- Errors: the worker's failure, as
<domain> <generation> <worker> (<module>) failed at <stage>: <message>.
Worker:close§
worker:close() -> ()Asks the worker to stop after the messages already queued for it.
Worker:join§
worker:join(timeoutSeconds: number?) -> stringCloses the worker and waits for it to end, up to timeoutSeconds (without a timeout, as long as it takes), and never past the execution time limit of the code that waits, then returns its final state: stopped or failed. Replies it sent before ending stay readable through receive.
- Errors:
<worker> did not answer in time.
jess.worker.send§
jess.worker.send(message: unknown) -> ()From inside a worker: sends a message to the domain, besides what the handler returns. For progress, or for more than one reply per message. It never waits: at most 1,024 messages wait for the domain to receive them, and a send past that fails, so a handler that floods a domain that isn't receiving finds out instead of stalling.
- Errors: in a domain's own code,
worker.send is for a worker's own code; a domain sends through the handle worker.spawn returned;jess.worker.send: the domain's queue is full: 1024 messages wait for it to receive them; send fewer, or reply less often.
--!strict
local worker = require('@jess/worker')
return function(count: number)
for index = 1, count do
worker.send { progress = index }
end
return 'done'
end
Related: jess.context, Script Domains.