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.
Transporter contains the following modules.
- BehaviorSubject
- Cache
- Injector
- Json
- Message
- Metadata
- Observable
- Proxy
- PubSub
- Session
- Subject
- Subprotocol
- SuperJson
Module
A BehaviorSubject
is a Subject
that replays the most recent value when subscribed to.
Type
class BehaviorSubject<T> extends Subject<T> {}
A Subject
that replays the last emitted value.
Constructor
function of<T>(value: T): BehaviorSubject<T>;
Creates a new BehaviorSubject
with an initial value of type T
.
import * as BehaviorSubject from "@daniel-nagy/transporter/BehaviorSubject";
BehaviorSubject.of("👍").subscribe(console.log);
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.
import * as BehaviorSubject from "@daniel-nagy/transporter/BehaviorSubject";
BehaviorSubject.of("👍").getValue();
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
.
Type
class Cache {}
A Cache
is used to memoize remote function calls. A Cache
is double-keyed by a function and its arguments.
Constructor
function init(): Cache;
Creates a new Cache
.
import * as Cache from "@daniel-nagy/transporter/Cache";
const cache = Cache.init();
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.
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
Cache.init().add(identity, "🥸", "🥸");
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.
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
Cache.init().get(identity, "🥸"); // NotFound
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.
import * as Cache from "@daniel-nagy/transporter/Cache";
const identity = (value) => value;
Cache.init().has(identity, "🥸"); // false
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.
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
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.
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
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.
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, "🥸", () => "🤓");
Module
An Injector
is IoC container and can be used to inject dependencies into functions invoked by Transporter.
Type
class Injector {}
An Injector
is a dependency container. Values may be added or read from the container using tags.
Type
type Tag<Value> {}
A Tag
is a value that is bound to a single dependency type and is used to index the container.
Constructor
function empty(): Injector;
Creates a new empty Injector
.
import * as Injector from "@daniel-nagy/transporter/Injector";
const injector = Injector.empty();
Constructor
function Tag<T>(): Tag<T>;
Creates a new Tag
.
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.
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
.
import * as Injector from "@daniel-nagy/transporter/Injector";
const tag = Injector.Tag<string>();
Injector.empty().get(tag);
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.
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);
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.
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;
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.
type
export type Json =
| null
| number
| string
| boolean
| { [key: string]: Json }
| Json[];
Represents a JSON value.
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.
import * as Json from "@daniel-nagy/transporter/Json";
Json.serialize({ name: "Jane Doe" });
function sortDeep(value: Json): Json;
Recursively sorts the properties of an object. Array values retain their sort order.
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"
// }
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.
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.
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.
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.
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
enum Type {
Call = "Call",
Error = "Error",
GarbageCollect = "GarbageCollect",
Set = "Set"
}
An enumerable of the different message types.
Type
type Version = `${number}.${number}.${number}`;
A semantic version string.
Constant
const protocol = "transporter";
The name of the protocol.
Constant
const version: Version;
The version of the protocol.
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.
import * as Message from "@daniel-nagy/transporter/Message";
Message.isCompatible(message);
Function
function isMessage<T, Value>(
message: T | Message<Value>
): message is Message<Value>;
Returns true if the value is a Transporter message.
import * as Message from "@daniel-nagy/transporter/Message";
Message.isMessage(value);
Function
function parseVersion(
version: Version
): [major: string, minor: string, patch: string];
Parses a semantic version string and returns a tuple of the version segments.
import * as Message from "@daniel-nagy/transporter/Message";
Message.parseVersion(message.version);
Module
The Metadata module allows information to be extracted from a proxy.
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
.
import * as Metadata from "@daniel-nagy/transporter/Metadata";
const metadata = Metadata.get(obj);
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.
- BufferOverflowError
- BufferOverflowStrategy
- BufferOptions
- EmptyError
- Event
- EventTarget
- Observable
- ObservableLike
- Observer
- Operator
- Subscription
- State
- TimeoutError
- bufferUntil
- catchError
- filter
- firstValueFrom
- flatMap
- map
- merge
- take
- takeUntil
- tap
- timeout
- toObserver
Type
class BufferOverflowError extends Error {}
Thrown if a buffer overflow occurs and the buffer overflow strategy is Error
.
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.
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.
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.
Type
interface Event {
type: string;
}
Represents a JavaScript event. Necessary since Transporter does not include types for a specific runtime.
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.
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
.
Type
type Observer<T> = {
next?(value: T): void;
error?(error: unknown): void;
complete?(): void;
};
An Observer
subscribes to an observable.
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.
Type
type Subscription = {
unsubscribe(): void;
};
A Subscription
is returned when an observer subscribes to an observable.
Type
enum State {
Complete = "Complete",
Error = "Error",
NotComplete = "NotComplete",
Unsubscribed = "Unsubscribed"
}
A discriminated type for the different states of an observable.
Type
class TimeoutError extends Error {}
Thrown by the timeout
operator if a value is not emitted within the specified amount of time.
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.cron(1000, () => Math.random()).subscribe(console.log);
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.fail("💩");
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.from(Promise.resolve("👍"));
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.
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).subscribe(console.log);
Method
pipe<A, B, ..., M, N>(
...operations: [Operator<A, B>, ..., Operator<M, N>]
): Observable<N>
Allows chaining operators to perform flow control.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, "2", 3, 4.5).pipe(
filter(Number.isInteger),
map((num) => num * 2)
);
Method
subscribe(observerOrNext?: Observer<T> | ((value: T) => void)): Subscription
Causes an Observer
to start receiving values from an observable as they are emitted.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).subscribe(console.log);
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.
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);
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.
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);
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3)
.pipe(Observable.filter((num) => num % 2 === 0))
.subscribe(console.log);
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
await Observable.firstValueFrom(Observable.of(1, 2, 3));
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(
Observable.flatMap((num) => Observable.of(num * 2))
);
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(Observable.map((num) => num * 2));
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.merge(Observable.of(1, 2, 3), Observable.of(4, 5, 6)).subscribe(
console.log
);
Function
function take(
amount: number
): <T>(observable: ObservableLike<T>) => Observable<T>;
Takes the first n
values from an observable and then completes.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(Observable.take(2)).subscribe(console.log);
Function
function takeUntil(
signal: ObservableLike<unknown>
): <T>(observable: ObservableLike<T>) => Observable<T>;
Takes values from an observable until a signal emits or completes.
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);
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.of(1, 2, 3).pipe(Observable.tap(console.log)).subscribe();
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
Observable.cron(1000, () => Math.random())
.pipe(Observable.timeout(999))
.subscribe();
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.
import * as Observable from "@daniel-nagy/transporter/Observable";
const identity = (value) => value;
const observer = toObserver(identity);
Module
The Proxy module is used to create proxy objects. Transporter will proxy these objects instead of cloning them.
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.
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 () => "👌"
// }
// }
Function
function isProxy<T extends object, U>(value: Proxy<T> | U): value is Proxy<T>;
Returns true
if the object is a Proxy
.
import * as Proxy from "@daniel-nagy/transporter/Proxy";
const proxy = Proxy.from({
b: async () => "👍"
});
Proxy.isProxy(proxy); // true
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.
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.
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.
import * as PubSub from "@daniel-nagy/transporter/PubSub";
const pubSub = PubSub.from(Observable.of(1, 2, 3));
Module
The Session module is used to create client and server sessions.
Type
type Agent = ClientAgent.t | ServerAgent.t;
An Agent
is a background task that manages a single resource and fulfills the
Transporter protocol.
Type
interface ClientOptions<DataType, Value> {
injector?: Injector.t;
protocol: Subprotocol<DataType, Value>;
resource: Resource<Value>;
}
Options for creating a 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.
Type
interface Resource<Value> {}
A Resource
is a value that is provided by a server.
Type
interface ServerOptions<DataType, Value> {
injector?: Injector.t;
protocol: Subprotocol<DataType, Value>;
provide: Value;
}
Options for creating a 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.
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>();
Constructor
function client<DataType, Value>(
options: ClientOptions<DataType, Value>
): ClientSession<DataType, Value>;
Creates a new ClientSession
.
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();
Constructor
function server<DataType, Value>(
options: ServerOptions<DataType, Value>
): ServerSession<DataType, Value>;
Creates a new ServerSession
.
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
}
});
Method
terminate(): void;
Terminates the session. Terminating the session will complete its input and output and terminate all currently active agents.
Module
A Subject
can be used to multicast an Observable
.
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
.
import * as Subject from "@daniel-nagy/transporter/Subject";
const subject = Subject.init<boolean>();
Method
asObservable(): Observable.t<T>;
Transforms the subject into a hot observable.
Method
complete(): void;
Changes the subject's state to Complete
.
Method
error(error: unknown): void;
Changes the subject's state to Error
.
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.
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.
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.
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.
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
.
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
.
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
});
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.
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
Module
The SuperJson module extends JSON to include many built-in JavaScript types, including Date
, RegExp
, Map
, ect.
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.
Function
function fromJson(value: Json.t): SuperJson;
Revives an encoded SuperJson
value from a JSON value.
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
SuperJson.fromJson(value);
Function
function toJson(value: SuperJson): Json.t;
Encodes a SuperJson
value as a valid JSON value.
import * as SuperJson from "@daniel-nagy/transporter/SuperJson";
SuperJson.toJson(new Date());