@daniel-nagy/transporter
v1.0.0-beta.6
Published
Typesafe distributed computing in TypeScript.
Downloads
447
Maintainers
Readme
Core
The core package contains APIs designed to work in any JavaScript runtime.
npm add @daniel-nagy/transporter
Transporter is distributed as ES modules. Generally speaking, modules encapsulate a type and export functions that act as either a constructor or operator on that type. The module has the same name as the type it encapsulates. You will often see this type reexported with the alias t
. This is a common convention found in functional programming languages that allows dereferencing the type from the module without typing out the name twice, which feels icky. This makes using namespace imports with Transporter modules a bit nicer. For example,
import * as Observable from "@daniel-nagy/transporter/Observable";
// To access the type you need to type Observable twice 🤮.
const observable: Observable.Observable = Observable.of(1, 2, 3);
// Equivalent to the above. Not perfect but better.
const observable: Observable.t = Observable.of(1, 2, 3);
Transporter makes heavy use of namespace imports internally. If that makes you concerned about tree-shaking then don't be. Webpack, Rollup, and esbuild all handle namespace imports fine. It is namespace exports that may be problematic when it comes to tree-shaking. Though both webpack and Rollup seem to handle those as well, making esbuild the standout.
API
Transporter contains the following modules.
- BehaviorSubject
- Cache
- Injector
- Json
- Message
- Metadata
- Observable
- Proxy
- PubSub
- Session
- Subject
- Subprotocol
- SuperJson
BehaviorSubject
Module
A BehaviorSubject
is a Subject
that replays the most recent value when subscribed to.
Types
Constructors
Methods
Type
class BehaviorSubject<T> extends Subject<T> {}
A Subject
that replays the last emitted value.
Of
Constructor
function of<T>(value: T): BehaviorSubject<T>;
Creates a new BehaviorSubject
with an initial value of type T
.
Example
import * as BehaviorSubject from "@daniel-nagy/transporter/BehaviorSubject";
BehaviorSubject.of("👍").subscribe(console.log);
GetValue
Method
getValue(): T;
The getValue
method can be used to synchronously retrieve the value held by the BehaviorSubject
. If the BehaviorSubject
is in an error state then getValue
will throw the error.
Example
import * as BehaviorSubject from "@daniel-nagy/transporter/BehaviorSubject";
BehaviorSubject.of("👍").getValue();
Cache
Module
A Cache
may be used to memoize remote function calls. Transporter guarantees that proxies are referentially stable so other memoization APIs are likely compatible with Transporter as well.
In order to memoize a function its arguments must be serializable. A stable algorithm is used to serialize a function's arguments and index the cache. The cache supports any arguments of type SuperJson
.
Types
Constructors
Methods
Type
class Cache {}
A Cache
is used to memoize remote function calls. A Cache
is double-keyed by a function and its arguments.
Init
Constructor
function init(): Cache;
Creates a new Cache
.
Example
import * as Cache from "@daniel-nagy/transporter/Cache";
const cache = Cache.init();
Add
Method
add(func: JsFunction.t, args: SuperJson.t[], value: unknown): void;
Adds the value to the cache for the specified function and arguments. Used internally by the memo
method, which is the preferred way to add a value to the cache.
Example
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
Cache.init().add(identity, "🥸", "🥸");
Get
Method
get<Args extends SuperJson.t[], Return>(
func: (...args: Args) => Return,
args: Args
): Return | NotFound;
Get a value from the cache. Returns NotFound
if the value does not exist.
Example
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
Cache.init().get(identity, "🥸"); // NotFound
Has
Method
has(func: JsFunction.t, args?: SuperJson.t[]): boolean
Checks if the value is in the cache. If no arguments are provided then it will return true
if any value is cached for the function.
Example
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
Cache.init().has(identity, "🥸"); // false
Memo
Method
memo<Args extends SuperJson.t[], Return>(
func: (...args: Args) => Return
): (...args: Args) => Return
Takes a function as input and returns a memoized version of the same function as output. Using a memoized function is the preferred way of adding values to the cache.
Example
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
const cache = Cache.init();
const memo = Cache.memo(identity);
memo("🥸");
cache.has(identity, "🥸"); // true
Remove
Method
remove(func: JsFunction.t, args?: SuperJson.t[]): boolean
Removes a value from the cache. Returns true
if the value was found and removed. If no arguments are provided then all values for that function will be removed.
Example
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
const cache = Cache.init();
const memo = Cache.memo(identity);
memo("🥸");
cache.remove(identity, "🥸"); // true
Update
Method
update<Args extends SuperJson.t[], Return>(
func: (...args: Args) => Return,
args: Args,
callback: (value: Return) => Return
): void
Updates a value in the cache. The callback function will receive the current value in the cache. Does nothing if there is a cache miss on the value.
Example
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
const cache = Cache.init();
const memo = Cache.memo(identity);
memo("🥸");
cache.update(identity, "🥸", () => "🤓");
Injector
Module
An Injector
is IoC container and can be used to inject dependencies into functions invoked by Transporter.
Types
Constructors
Methods
Functions
Type
class Injector {}
An Injector
is a dependency container. Values may be added or read from the container using tags.
Tag
Type
type Tag<Value> {}
A Tag
is a value that is bound to a single dependency type and is used to index the container.
Empty
Constructor
function empty(): Injector;
Creates a new empty Injector
.
Example
import * as Injector from "@daniel-nagy/transporter/Injector";
const injector = Injector.empty();
Constructor
function Tag<T>(): Tag<T>;
Creates a new Tag
.
Example
import * as Injector from "@daniel-nagy/transporter/Injector";
type Session = {
userId?: string;
};
const SessionTag = Injector.Tag<Session>();
Method
function add<Value>(tag: Tag<Value>, value: Value): Injector;
Adds a value to the container.
Example
import * as Injector from "@daniel-nagy/transporter/Injector";
type Session = {
userId?: string;
};
const SessionTag = Injector.Tag<Session>();
const Session: Session = { userId: "User_123" };
Injector.empty().add(SessionTag, Session);
Method
function get(tag: Tag<unknown>): unknown;
Gets a value from the container using a Tag
.
Example
import * as Injector from "@daniel-nagy/transporter/Injector";
const tag = Injector.Tag<string>();
Injector.empty().get(tag);
GetTags
Function
function getTags(func: JsFunction.t): : Tag<unknown>[];
Returns a list of tags from a function returned by provide
. If the function does not have DI metadata an empty list is returned.
Example
import * as Injector from "@daniel-nagy/transporter/Injector";
const getUser = Injector.provide([Prisma, Session], (prisma, session) =>
prisma.user.findUnique({ where: { id: session.userId } })
);
Injector.getTags(getUser);
Provide
Function
function provide<
const Tags extends readonly Tag<unknown>[],
const Args extends [...Values<Tags>, ...unknown[]],
const Return
>(
tags: Tags,
func: (...args: Args) => Return
): (...args: JsArray.DropFirst<Args, JsArray.Length<Tags>>) => Return;
Returns a new function that has a list of tags stored as metadata. The call signature of the new function will omit any injected dependencies. Type parameters of generic functions will be propagated to the new function.
Example
import * as Injector from "@daniel-nagy/transporter/Injector";
const getUser = Injector.provide(
[Prisma, Session],
<S extends Prisma.UserSelect>(prisma, session, select: S) =>
prisma.user.findUnique({ where: { id: session.userId }, select })
);
// $ExpectType
// const getUser = <S extends Prisma.UserSelect>(
// select: S
// ) => Prisma.Prisma__User<S> | null;
Json
Module
A Json
type may be used as a data type for a subprotocol. If both ends of your communication channel are JavaScript runtimes then you may use the SuperJson module instead for a much larger set of types.
Types
Functions
type
export type Json =
| null
| number
| string
| boolean
| { [key: string]: Json }
| Json[];
Represents a JSON value.
Serialize
function serialize(value: Json): string;
Serializes a JSON value in a way that is deterministic, such that 2 strings are equal if they encode the same value.
Example
import * as Json from "@daniel-nagy/transporter/Json";
Json.serialize({ name: "Jane Doe" });
sortDeep
function sortDeep(value: Json): Json;
Recursively sorts the properties of an object. Array values retain their sort order.
Example
import * as Json from "@daniel-nagy/transporter/Json";
Json.sortDeep({
c: "c",
b: [{ f: "f", e: "e" }, 12],
a: "a"
});
// $ExpectType
// {
// a: "a",
// b: [{ e: "e", f: "f" }, 12],
// c: "c"
// }
Message
Module
Defines the Transporter message protocol. The creation and interpretation of messages should be considered internal. However, it is ok to intercept message and perform your own logic or encoding.
Types
Constants
Functions
CallFunction
Type
type CallFunction<Args> = {
readonly address: string;
readonly args: Args;
readonly id: string;
readonly noReply: boolean;
readonly path: string[];
readonly protocol: "transporter";
readonly type: Type.Call;
readonly version: Version;
};
A Call
message is sent to the server to call a remote function.
Error
Type
type Error<Error> = {
readonly address: string;
readonly error: Error;
readonly id: string;
readonly protocol: "transporter";
readonly type: Type.Error;
readonly version: Version;
};
An Error
message is sent to the client when calling a remote function throws or rejects.
GarbageCollect
Type
type GarbageCollect = {
readonly address: string;
readonly id: string;
readonly protocol: "transporter";
readonly type: Type.GarbageCollect;
readonly version: Version;
};
A GarbageCollect
message is sent to the server when a proxy is disposed on the client.
Type
type Message<Value> =
| CallFunction<Value[]>
| Error<Value>
| GarbageCollect
| SetValue<Value>;
A discriminated union of the different message types.
SetValue
Type
type SetValue<Value> = {
readonly address: string;
readonly id: string;
readonly protocol: "transporter";
readonly type: Type.Set;
readonly value: Value;
readonly version: Version;
};
A Set
message is sent to the client after calling a remote function.
Type
Type
enum Type {
Call = "Call",
Error = "Error",
GarbageCollect = "GarbageCollect",
Set = "Set"
}
An enumerable of the different message types.
Version
Type
type Version = `${number}.${number}.${number}`;
A semantic version string.
Protocol
Constant
const protocol = "transporter";
The name of the protocol.
Version
Constant
const version: Version;
The version of the protocol.
IsCompatible
Function
function isCompatible(messageVersion: Version): boolean;
Returns true if a message is compatible with the current protocol version. A message is considered compatible if its major and minor versions are the same.
Example
import * as Message from "@daniel-nagy/transporter/Message";
Message.isCompatible(message);
IsMessage
Function
function isMessage<T, Value>(
message: T | Message<Value>
): message is Message<Value>;
Returns true if the value is a Transporter message.
Example
import * as Message from "@daniel-nagy/transporter/Message";
Message.isMessage(value);
ParseVersion
Function
function parseVersion(
version: Version
): [major: string, minor: string, patch: string];
Parses a semantic version string and returns a tuple of the version segments.
Example
import * as Message from "@daniel-nagy/transporter/Message";
Message.parseVersion(message.version);
Metadata
Module
The Metadata module allows information to be extracted from a proxy.
Types
Functions
Type
type Metadata = {
/**
* The id of the client agent managing this proxy.
*/
clientAgentId: string;
/**
* The path to the value in the original object.
*/
objectPath: string[];
};
Contains information about a proxy object.
function get<Proxy extends object>(proxy: Proxy): Metadata | null;
Returns metadata about a proxy. If the object is not a proxy it returns null
.
Example
import * as Metadata from "@daniel-nagy/transporter/Metadata";
const metadata = Metadata.get(obj);
Observable
Module
The Observable module provides ReactiveX APIs similar to rxjs. If you make heavy use of Observables then you may decide to use rxjs instead.
Transporter observables should have interop with rxjs observables. If you encounter issues transforming to or from rxjs observables then you may report those issues.
Transporter operators may behave differently than rxjs operators of the same name.
Types
- BufferOverflowError
- BufferOverflowStrategy
- BufferOptions
- EmptyError
- Event
- EventTarget
- Observable
- ObservableLike
- Observer
- Operator
- Subscription
- State
- TimeoutError
Constructors
Methods
Functions
BufferOverflowError
Type
class BufferOverflowError extends Error {}
Thrown if a buffer overflow occurs and the buffer overflow strategy is Error
.
BufferOverflowStrategy
Type
enum BufferOverflowStrategy {
/**
* Discard new values as they arrive.
*/
DropLatest = "DropLatest",
/**
* Discard old values making room for new values.
*/
DropOldest = "DropOldest",
/**
* Error if adding a new value to the buffer will cause an overflow.
*/
Error = "Error"
}
Specifies what to do in the event of a buffer overflow.
BufferOptions
Type
type BufferOptions = {
/**
* The max capacity of the buffer. The default is `Infinity`.
*/
limit?: number;
/**
* How to handle a buffer overflow scenario. The default is `Error`.
*/
overflowStrategy?: BufferOverflowStrategy;
};
Options for operators that perform buffering.
EmptyError
Type
class EmptyError extends Error {}
May be thrown by operators that expect a value to be emitted if the observable completes before emitting a single value.
Event
Type
interface Event {
type: string;
}
Represents a JavaScript event. Necessary since Transporter does not include types for a specific runtime.
EventTarget
Type
interface EventTarget {
addEventListener(type: string, callback: (event: Event) => void): void;
dispatchEvent(event: Event): boolean;
removeEventListener(type: string, callback: (event: Event) => void): void;
}
Represents a JavaScript event target. Necessary since Transporter does not include types for a specific runtime.
Type
class Observable<T> implements ObservableLike<T> {}
Observables are lazy push data structures that can emit values both synchronously and asynchronously. Observables are unicast and, unlike promises, may never emit a value or may emit many values.
ObservableLike
Type
interface ObservableLike<T> {
subscribe(observerOrNext?: Observer<T> | ((value: T) => void)): Subscription;
}
A value is ObservableLike
if it has a subscribe
method that takes a function or Observer
as input and returns a Subscription
.
Observer
Type
type Observer<T> = {
next?(value: T): void;
error?(error: unknown): void;
complete?(): void;
};
An Observer
subscribes to an observable.
Operator
Type
type Operator<T, U> = (observable: ObservableLike<T>) => ObservableLike<U>;
An Operator
is a function that takes an observable as input and returns a new observable as output.
Subscription
Type
type Subscription = {
unsubscribe(): void;
};
A Subscription
is returned when an observer subscribes to an observable.
State
Type
enum State {
Complete = "Complete",
Error = "Error",
NotComplete = "NotComplete",
Unsubscribed = "Unsubscribed"
}
A discriminated type for the different states of an observable.
TimeoutError
Type
class TimeoutError extends Error {}
Thrown by the timeout
operator if a value is not emitted within the specified amount of time.
Cron
Constructor
function cron<T>(
interval: number,
callback: () => T | Promise<T>
): Observable<T>;
Creates an observable that calls a function at a regular interval and emits the value returned by that function.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.cron(1000, () => Math.random()).subscribe(console.log);
Fail
Constructor
function fail<E>(errorOrCallback: E | (() => E)): Observable<never>;
Creates an observable that will immediately error with the provided value. If the value is a function then the function will be called to get the value.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.fail("💩");
From
Constructor
function from<T>(observable: ObservableLike<T> | PromiseLike<T>): Observable<T>;
Creates a new Observable
from an object that is observable like or promise like.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.from(Promise.resolve("👍"));
FromEvent
Constructor
function function fromEvent<T extends Event>(target: EventTarget, type: string): Observable<T>;
Creates a hot observable from an event target and an event type.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.fromEvent(button, "click");
Constructor
function of<T>(...values: [T, ...T[]]): Observable<T>;
Creates a new Observable
that emits each argument synchronously and then completes.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).subscribe(console.log);
Pipe
Method
pipe<A, B, ..., M, N>(
...operations: [Operator<A, B>, ..., Operator<M, N>]
): Observable<N>
Allows chaining operators to perform flow control.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, "2", 3, 4.5).pipe(
filter(Number.isInteger),
map((num) => num * 2)
);
Subscribe
Method
subscribe(observerOrNext?: Observer<T> | ((value: T) => void)): Subscription
Causes an Observer
to start receiving values from an observable as they are emitted.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).subscribe(console.log);
BufferUntil
Function
function bufferUntil<T, S>(
signal: ObservableLike<S>,
options?: BufferOptions
): (observable: ObservableLike<T>) => Observable<T>;
Buffers emitted values until a signal emits or completes. Once the signal emits or completes the buffered values will be emitted synchronously.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
import * as Subject from "@daniel-nagy/transporter/Subject";
const signal = Subject.init();
Observable.of(1, 2, 3).pipe(bufferUntil(signal)).subscribe(console.log);
setTimeout(() => signal.next(), 2000);
CatchError
Function
function catchError<T>(
callback: <E>(error: E) => ObservableLike<T>
): (observable: ObservableLike<T>) => Observable<T>;
Catches an error emitted by an upstream observable. The callback function can return a new observable to recover from the error. The new observable will completely replace the old one.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3)
.pipe(
Observable.flatMap(() => Observable.fail("💩")),
Observable.catchError(() => Observable.of(4, 5, 6))
)
.subscribe(console.log);
Filter
Function
function filter<T, S extends T>(
callback: ((value: T) => value is S) | ((value: T) => boolean)
): (observable: ObservableLike<T>) => Observable<S>;
Selectively keeps values for which the callback returns true
. All other values are discarded.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3)
.pipe(Observable.filter((num) => num % 2 === 0))
.subscribe(console.log);
FirstValueFrom
Function
function firstValueFrom<T>(observable: ObservableLike<T>): Promise<T>;
Transforms an observable into a promise that resolves with the first emitted value from the observable. If the observable errors the promise is rejected. If the observable completes without ever emitting a value the promise is rejected with an EmptyError
.
WARNING
If the observable never emits the promise will never resolve.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
await Observable.firstValueFrom(Observable.of(1, 2, 3));
FlatMap
Function
function flatMap<T, U>(
callback: (value: T) => ObservableLike<U> | PromiseLike<U>
): (observable: ObservableLike<T>) => Observable<U>;
Calls the callback function for each value emitted by the observable. The callback function returns a new observable that is flattened to avoid creating an observable of observables.
The observable completes when the source observable and all inner observables complete.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(
Observable.flatMap((num) => Observable.of(num * 2))
);
Map
Function
function map<T, U>(
callback: (value: T) => U
): (observable: ObservableLike<T>) => Observable<U>;
Calls the callback function for each value emitted by the observable and emits the value returned by the callback function.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(Observable.map((num) => num * 2));
Merge
Function
function merge<T>(...observables: ObservableLike<T>[]): Observable<T>;
Merges 2 or more observables into a single observable. The resulting observable does not complete until all merged observables complete.
Values will be emitted synchronously from each observable in the order provided. Any asynchronous values will be emitted in the order they arrive.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.merge(Observable.of(1, 2, 3), Observable.of(4, 5, 6)).subscribe(
console.log
);
Take
Function
function take(
amount: number
): <T>(observable: ObservableLike<T>) => Observable<T>;
Takes the first n
values from an observable and then completes.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(Observable.take(2)).subscribe(console.log);
TakeUntil
Function
function takeUntil(
signal: ObservableLike<unknown>
): <T>(observable: ObservableLike<T>) => Observable<T>;
Takes values from an observable until a signal emits or completes.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
import * as Subject from "@daniel-nagy/transporter/Subject";
const signal = Subject.init();
Observable.cron(1000, () => Math.random()).pipe(Observable.takeUntil(signal));
setTimeout(() => signal.next(), 2000);
Tap
Function
function tap<T>(
callback: (value: T) => unknown
): <T>(observable: ObservableLike<T>) => Observable<T>;
Allows performing effects when a value is emitted without altering the value that is emitted.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(Observable.tap(console.log)).subscribe();
Timeout
Function
function timeout<T>(
milliseconds: number,
callback?: (error: TimeoutError) => ObservableLike<T>
): <T>(observable: ObservableLike<T>) => Observable<T>;
Causes an observable to error if a value is not emitted within the specified timeout limit. The timer resets every time a value is emitted.
The callback function may return a new observable to replace the old one or to return a specific error.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.cron(1000, () => Math.random())
.pipe(Observable.timeout(999))
.subscribe();
ToObserver
Function
function toObserver<T>(
observerOrNext?: Observer<T> | ((value: T) => void)
): Observer<T>;
Takes a value that may be an observer or a function and returns an observer. If called without an argument it will return an empty object.
Example
import * as Observable from "@daniel-nagy/transporter/Observable";
const identity = (value) => value;
const observer = toObserver(identity);
Proxy
Module
The Proxy module is used to create proxy objects. Transporter will proxy these objects instead of cloning them.
Types
Constructors
Functions
Type
type Proxy<T extends object> = JsObject.ReadonlyDeep<
JsObject.PickDeep<T, JsFunction.t | PubSub.t>
>;
A Proxy
is a readonly object who's properties are functions.
Constructor
function from<T extends object>(value: T): Proxy<T>;
Creates a proxy from any object. The resulting object will be readonly and only functions will remain as properties.
Example
import * as Proxy from "@daniel-nagy/transporter/Proxy";
const proxy = Proxy.from({
a: "a",
b: async () => "👍",
c: [12, async () => "👌"]
});
// $ExpectType
// {
// readonly b: async () => "👍",
// readonly c: {
// readonly 1: async () => "👌"
// }
// }
IsProxy
Function
function isProxy<T extends object, U>(value: Proxy<T> | U): value is Proxy<T>;
Returns true
if the object is a Proxy
.
Example
import * as Proxy from "@daniel-nagy/transporter/Proxy";
const proxy = Proxy.from({
b: async () => "👍"
});
Proxy.isProxy(proxy); // true
PubSub
Module
The PubSub module is used to wrap an Observable so that it may be used for pub/sub. A PubSub is essentially an Observable who's subscribe and unsubscribe methods are asynchronous.
Types
Constructors
AsyncObserver
Type
type AsyncObserver<T> = {
next?(value: T): Promise<void>;
error?(error: unknown): Promise<void>;
complete?(): Promise<void>;
};
An Observer
who's methods are asynchronous.
Type
interface PubSub<T> {
subscribe(
observerOrNext: AsyncObserver<T> | ((value: T) => Promise<void>)
): Promise<RemoteSubscription>;
}
An Observable
who's subscribe method is asynchronous.
RemoteSubscription
Type
type RemoteSubscription = {
unsubscribe(): Promise<void>;
};
A Subscription
that returns a promise when unsubscribed.
Constructor
function from<T>(observable: Observable.ObservableLike<T>): PubSub<T>;
Turns an ordinary observable into an asynchronous one.
Example
import * as PubSub from "@daniel-nagy/transporter/PubSub";
const pubSub = PubSub.from(Observable.of(1, 2, 3));
Session
Module
The Session module is used to create client and server sessions.
Types
Constants
Constructors
Methods
Agent
Type
type Agent = ClientAgent.t | ServerAgent.t;
An Agent
is a background task that manages a single resource and fulfills the
Transporter protocol.
ClientOptions
Type
interface ClientOptions<DataType, Value> {
injector?: Injector.t;
protocol: Subprotocol<DataType, Value>;
resource: Resource<Value>;
}
Options for creating a ClientSession
.
ClientSession
Type
class ClientSession<DataType, Value> extends Session<DataType, Value> {
createProxy(): Proxy.t<Value>;
}
A ClientSession
is created on the client to proxy a remote resource.
Resource
Type
interface Resource<Value> {}
A Resource
is a value that is provided by a server.
ServerOptions
Type
interface ServerOptions<DataType, Value> {
injector?: Injector.t;
protocol: Subprotocol<DataType, Value>;
provide: Value;
}
Options for creating a ServerSession
.
ServerSession
Type
class ServerSession<DataType, Value> extends Session<DataType, Value> {}
A ServerSession
is created on the server to provide a resource.
Type
abstract class Session<DataType, Value> extends Supervisor.t<Agent> {
readonly injector?: Injector.t;
readonly input: Observable.Observer<Message.t<DataType>>;
readonly output: Observable.t<Message.t<DataType>>;
readonly protocol: Subprotocol<DataType, Value>;
}
A Session
spawns and observes agents. A session may spawn multiple server or client agents while active. Terminating a session will terminate all agents spawned by the session that are still active.
If all agents spawned by the session are terminated then the session is automatically terminated.
RootSupervisor
Constant
const rootSupervisor = Supervisor.init<Session>("RootSupervisor");
The root supervisor observers all active sessions.
Constructor
const Resource = <Value>(): Resource<Value>;
Creates a new Resource
. This container type is necessary because TypeScript
lacks partial inference of type parameters.
import * as Session from "@daniel-nagy/transporter/Session";
import type { Api } from "...";
const resource = Session.Resource<Api>();
Client
Constructor
function client<DataType, Value>(
options: ClientOptions<DataType, Value>
): ClientSession<DataType, Value>;
Creates a new ClientSession
.
Example
import * as Session from "@daniel-nagy/transporter/Session";
import * as Subprotocol from "@daniel-nagy/transporter/Subprotocol";
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
import type { Api } from "...";
const httpProtocol = Subprotocol.init({
connectionMode: Subprotocol.ConnectionMode.ConnectionLess,
dataType: Subprotocol.DataType<SuperJson.t>(),
operationMode: Subprotocol.OperationMode.Unicast,
transmissionMode: Subprotocol.TransmissionMode.HalfDuplex
});
const session = Session.client({
protocol: httpProtocol,
resource: Session.Resource<Api>()
});
const client = session.createProxy();
Server
Constructor
function server<DataType, Value>(
options: ServerOptions<DataType, Value>
): ServerSession<DataType, Value>;
Creates a new ServerSession
.
Example
import * as Session from "@daniel-nagy/transporter/Session";
import * as Subprotocol from "@daniel-nagy/transporter/Subprotocol";
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
module User {
export async greet = () => "👋"
}
const httpProtocol = Subprotocol.init({
connectionMode: Subprotocol.ConnectionMode.ConnectionLess,
dataType: Subprotocol.DataType<SuperJson.t>(),
operationMode: Subprotocol.OperationMode.Unicast,
transmissionMode: Subprotocol.TransmissionMode.HalfDuplex
});
const session = Session.server({
protocol: httpProtocol,
provide: {
User
}
});
Terminate
Method
terminate(): void;
Terminates the session. Terminating the session will complete its input and output and terminate all currently active agents.
Subject
Module
A Subject
can be used to multicast an Observable
.
Types
Constructors
Methods
Type
class Subject<T>
implements Observable.ObservableLike<T>, Observable.Observer<T> {}
A Subject
is both an Observable
and an Observer
.
Constructor
function init<T>(): Subject<T>;
Creates a new Subject
.
Example
import * as Subject from "@daniel-nagy/transporter/Subject";
const subject = Subject.init<boolean>();
AsObservable
Method
asObservable(): Observable.t<T>;
Transforms the subject into a hot observable.
Complete
Method
complete(): void;
Changes the subject's state to Complete
.
Method
error(error: unknown): void;
Changes the subject's state to Error
.
Next
Method
next(value: T): void;
Emits the value to all subscribers.
Method
subscribe(
observerOrNext?: Observable.Observer<T> | ((value: T) => void)
): Observable.Subscription;
Subscribes to state changes and values emitted by the subject.
Subprotocol
Module
The Transporter protocol is type agnostic. In order to provide type-safety a subprotocol is required. The subprotocol restricts what types may be included in function IO. For example, if the data type of a subprotocol is JSON then only JSON data types may be input or output from remote functions.
In addition, Transporter can perform recursive RPC if certain subprotocol and network conditions are met. Recursive RPC means functions or proxies may be included in function IO. This is an interesting concept because it allows state between processes to be held on the call stack. For example, recursive RPC allows Observables to be used for pub-sub.
In order to use recursive RPC your subprotocol must be connection-oriented and bidirectional. If those conditions are met then the call signature for remote functions will allow functions or proxies as input or output. It turns out that these types of connections are common in the browser.
Types
Constructors
Functions
ConnectionMode
Type
enum ConnectionMode {
/**
* A message can be sent from one endpoint to another without prior
* arrangement. For example, HTTP is a connectionless protocol.
*/
Connectionless = "Connectionless",
/**
* A session or connection is established before data can be transmitted. For
* example, TCP is a connection-oriented protocol.
*/
ConnectionOriented = "ConnectionOriented"
}
Used to define the type of connection between the client and the server.
Type
interface DataType<T> {}
Constrains the input and output types of a procedure to a specific data type.
OperationMode
Type
enum OperationMode {
/**
* A single message is sent to every node in a network. This is a one-to-all
* transmission.
*/
Broadcast = "Broadcast",
/**
* A single message is sent to a subset of nodes in a network. This is a
* one-to-many transmission.
*/
Multicast = "Multicast",
/**
* A single message is sent to a single node. This is a one-to-one
* transmission.
*/
Unicast = "Unicast"
}
Used to define how data is distributed to nodes in a network.
Type
interface Subprotocol<DataType, Input, Output> {}
Used to restrict function input and output types as well as determine if recursive RPC can be enabled or not.
TransmissionMode
Type
enum TransmissionMode {
/**
* Either side may transmit data at any time. This is a 2-way communication.
*/
Duplex = "Duplex",
/**
* Only one side can transmit data at a time. This is a 2-way communication.
*/
HalfDuplex = "HalfDuplex",
/**
* Only the sender can transmit data. This is a 1-way communication.
*/
Simplex = "Simplex"
}
Used to define how data is transmitted over a network.
Constructor
function DataType<const T>(): DataType<T>;
Creates a new DataType
.
Example
import * as Json from "@daniel-nagy/transporter/Json";
import * as Subprotocol from "@daniel-nagy/transporter/Subprotocol";
const jsonDataType = Subprotocol.DataType<Json.t>();
Constructor
function init(options: {
connectionMode: ConnectionMode;
dataType: DataType;
operationMode: OperationMode;
transmissionMode: TransmissionMode;
}): Subprotocol;
Creates a new Subprotocol
.
Example
import * as Subprotocol from "@daniel-nagy/transporter/Subprotocol";
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
const subprotocol = Subprotocol.init({
connectionMode: Session.ConnectionMode.ConnectionLess,
dataType: Subprotocol.DataType<SuperJson.t>(),
operationMode: Session.OperationMode.Unicast,
transmissionMode: Session.TransmissionMode.HalfDuplex
});
IsBidirectional
Function
function isBidirectional(protocol: Subprotocol<unknown>): boolean;
Returns true
is the subprotocol is bidirectional. The connection is considered bidirectional if its operation mode is unicast and its transmission mode is duplex or half-duplex.
Example
import * as Subprotocol from "@daniel-nagy/transporter/Subprotocol";
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
const subprotocol = Subprotocol.init({
connectionMode: Session.ConnectionMode.ConnectionLess,
dataType: Subprotocol.DataType<SuperJson.t>(),
operationMode: Session.OperationMode.Unicast,
transmissionMode: Session.TransmissionMode.HalfDuplex
});
Subprotocol.isBidirectional(subprotocol); // true
SuperJson
Module
The SuperJson module extends JSON to include many built-in JavaScript types, including Date
, RegExp
, Map
, ect.
Types
Functions
Type
type SuperJson =
| void
| null
| undefined
| boolean
| number
| bigint
| string
| Date
| RegExp
| Array<SuperJson>
| Map<SuperJson, SuperJson>
| Set<SuperJson>
| { [key: string]: SuperJson };
An extended JSON type that includes many builtin JavaScript types.
FromJson
Function
function fromJson(value: Json.t): SuperJson;
Revives an encoded SuperJson
value from a JSON value.
Example
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
SuperJson.fromJson(value);
ToJson
Function
function toJson(value: SuperJson): Json.t;
Encodes a SuperJson
value as a valid JSON value.
Example
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
SuperJson.toJson(new Date());