Scalability is a type-safe service scaling facility built on Network-Services.
Scalability provides a simple and intuitive API for scaling Node.js modules using Worker threads. You can create a Service App in your scaled module and call its methods from the main thread using a Service API. Conversely, methods can be called in the main thread from scaled modules in the same way.
Scalability allows you to easily transform your single threaded application into a multithreaded one.
- Call methods on a Service App running in a Worker thread using a type-safe API: code completion, parameter types, and return types.
- Return values and Errors are marshalled back to the caller.
- Infinite property nesting; you can use a Service API to call nested properties on a Service App at any depth.
- Bi-directional asynchronous RPC - communication goes both ways - Scalability allows for calls from the main thread to a Worker and from a Worker to the main thread.
npm install scalability
Scalability is an extension of the Network-Services RPC Service facility; hence, the concepts that it introduces are Network-Services concepts e.g., Services, Service Apps, and Service APIs.
Please see the Network-Services documentation if you would like to learn more about these concepts.
A Scalability application consists of a main thread (e.g., index.js
) and a scaled module (e.g., service.js
). In this example the module that runs in the main thread is named index.js
and the module that will be scaled is named service.js
.
This is the module that runs in the main thread.
Import the createService
and createWorkerPool
helper functions and the type of the service application (i.e., Greeter
) that will run in the Worker thread.
import { once } from "node:events";
import { createService, createWorkerPool } from "scalability";
import { Greeter } from "./service.js";
const workerPool = createWorkerPool({
workerCount: 10,
workerURL: "./dist/service.js",
});
await once(workerPool, "ready");
The greeter
object will support code completion, parameter types, and return types.
const service = createService(workerPool);
const greeter = service.createServiceAPI<Greeter>();
The greeter.greet
method returns a promise because it is called asynchronously using a MessagePort
.
const results = [];
for (let i = 0; i < 100; i++) {
results.push(greeter.greet("happy"));
}
const result = await Promise.all(results);
console.log(result);
This is the scaled module specified in the options of the WorkerPool
constructor. It contains the Greeter
Service App.
import { createPortStream, createService } from "scalability";
export class Greeter {
// Create a friendly Greeter Application.
greet(kind: string) {
for (let now = Date.now(), then = now + 100; now < then; now = Date.now()); // Block for 100 milliseconds.
return `Hello, ${kind} world!`;
}
}
This adapter will wrap the Worker thread's parentPort
in a stream.Duplex
in order for it be used by Network-Services.
const portStream = createPortStream();
Create a Service using the portStream instance and create a Service App using an instance of Greeter
.
const service = createService(portStream);
service.createServiceApp(new Greeter());
That's all it takes to scale this Greeter
application. Please see the Hello, World! example for a complete working implementation.
stream
<WorkerPool | PortStream>
An instance of aWorkerPool
or an instance of aPortStream
. This is a type narrowed version of the Network-ServicescreateService
helper function. This helper function will accept either aWorkerPool
or aPortStream
as an argument, both of which arestream.Duplex
.
Returns: <Service>
public service.createServiceApp<T>(app, options)
app
<object>
An instance of your application.options
<ServiceAppOptions<T>>
paths
<Array<PropPath<Async<T>>>>
AnArray
of property paths (i.e., dot-pathstring
s). If defined, only property paths in this list may be called on the Service App. Each element of the Array is aPropPath
and aPropPath
is simply a dot-pathstring
representation of a property path. Default:undefined
.
Returns: <ServiceApp<T>>
public service.createServiceAPI<T>(options)
options
<ServiceAPIOptions>
timeout
<number>
Optional argument in milliseconds that specifies thetimeout
for function calls. Default:undefined
(i.e., no timeout).
Returns: <Async<T>>
A Proxy
of type <T>
that consists of asynchronous analogues of methods in <T>
.
options
<WorkerPoolOptions>
workerCount
<number>
Optional argument that specifies the number of worker threads to be spawned.workerURL
<string | URL>
The URL or path to the.js
module file. This is the module that will be scaled according to the value specified forworkerCount
.restartWorkerOnError
<boolean>
A boolean setting specifying if Workers should be restarted onerror
. Default:false
workerOptions
<worker_threads.WorkerOptions>
Optionalworker_threads.WorkerOptions
to be passed to each Worker instance.duplexOptions
<stream.DuplexOptions>
Optionalstream.DuplexOptions
to be passed to thestream.Duplex
i.e., the parent class of theWorkerPool
.
Returns: <WorkerPool>
A WorkerPool
wraps the MessagePorts
of the Worker threads into a single stream.Duplex
. Hence, a WorkerPool
is a stream.Duplex
, so it can be passed to the Network-Services createService
helper function. This is the stream adapter that is used in the module of the main thread.
options
<stream.DuplexOptions>
Optionalstream.DuplexOptions
to be passed to thestream.Duplex
i.e., the parent class of thePortStream
.
A PortStream
wraps the parentPort
of the Worker thread into a stream.Duplex
. Hence, a PortStream
is a stream.Duplex
, so it can be passed to the Network-Services createService
helper function. This is the stream adapter that is used in the Worker module.
If you have a feature request or run into any issues, feel free to submit an issue or start a discussion. You’re also welcome to reach out directly to one of the authors.