1.0.0-beta.5 • Published 4 months ago

@daniel-nagy/transporter v1.0.0-beta.5

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
4 months ago

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

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 address of the server that provides the value.
   */
  address: 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
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());