0.10.0-beta.19 • Published 4 years ago

@otpjs/core v0.10.0-beta.19

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

Open Telecom Platform on JS

Build Status Coverage Status

This project makes heavy use of ES6 features and as such requires NodeJS v16

This is an endeavor to replicate the Open Telecom Platform in NodeJS. You should probably use either NodeJS or Erlang, but this implementation tries to bridge the gap between the two languages by implementing what is essentially the Erlang Standard Library: OTP.

Starting OTPJS

For example, this script would print the string "Hello world"

import {OTPNode} from '@otpjs/core';
const node = new OTPNode();

const pid = node.spawn(async (ctx) => {
    const message = await ctx.receive();
    console.log("Hello %s", message);
});

node.spawn(async (ctx) => {
    ctx.send(pid, "world");
})

Symbols

Symbols are used in places to replicate the behavior of atoms in Erlang. @otpjs/core provides its own serialize/deserialize functions for passing data over a text channel. These serialize/deserialize functions properly handle both Symbol types (as long as they are built with Symbol.for(...)), and the built-in String extensions Pid and Ref.

The end result of serialization is a valid JSON string. Deserialization will restore available Symbol, Pid, and Ref elements.

As with JSON.stringify and JSON.parse, these serialize/deserialize functions accept a replacer and reviver parameter respectively so that you can handle your own data serialization as well.

import {serialize, deserialize, Symbols} from '@otpjs/core';
const [ok, _] = Symbols

const tuple = [ok, 'test', 123, false];
const json = serialize(tuple);

console.log(json); // [{"$otp.symbol": "$otp.symbol.ok"}, "test", 123, false]

const tupleB = deserialize(json);

console.log(tupleB); // [Symbol($otp.symbol.ok), "test", 123, false]

Pattern Matching

Usage

This module may be broken out for further development, but for now it resides within @otpjs/core. Pattern matching is done by constructing a comparison function using a provided pattern as a guide. When applied to a value, this comparison function returns either true or false.

Pattern matching is a huge element of Erlang development, and it did not feel right to have an OTP implementation without at least an homage to the Erlang's insane pattern matching power.

Underscore

Understanding the underscore symbol is important. Its usage in otpjs reflects the underscore's usage in Erlang. When provided in a pattern, the underscore matches against any value.

import {compare} from '@otpjs/core';


compare(_, undefined) // true;
compare(_, BigInt(1000)) // true;
compare(_, [1, 2, 3]) // true;

API

compile(pattern)
import {compile, Symbols} from '@otpjs/core';

const {ok, _} = Symbols;

const pattern = [ok, 'fixed string', Number.isInteger, _];

// The basis of pattern matching is the pattern compiler. You can use this
// directly, but we'll see other approaches later on 
const compiled = compile(pattern);

// Pattern compiler constructs a function to assess the incoming
// value against the specified pattern.
compiled([ok, 'fixed string', 1, {}]) // true

// Fixed simple values are required to be  equal in value between the 
// pattern and incoming value. If its conditions are satisified, it returns
// true, otherwise false.
compiled([ok, 'different string', 1, {}]) // false

// Complex types like objects, arrays, and functions are handled differently.
// Functions are assumed to be a predicate which must be satisfied.
// Objects and arrays are traversed to find matching values.
compiled([ok, 'fixed string', 1.1, {}]) // false
compare(pattern, value)
import {compare, Symbols} from '@otpjs/core';

const {ok, _} = Symbols;

const pattern = [ok, 'fixed string', Number.isInteger, _];

// Compare is a simple utility that compiles and compares the provided pattern
// against the provided value.
compare(pattern, [ok, 'fixed string', 1, {}]) // true
caseOf(value)
import {compile, Symbols} from '@otpjs/core';

const {ok, _} = Symbols;

// caseOf flips the compile/pattern theory on its head. It focuses on the incoming
// value, and provides a comparison function which accepts and compiles incoming
// patterns to validate against the provided value.
const compare = caseOf([1, '2', 3.3]);
compare([Number.isInteger, '2', Number.isFinite]); // true
compare([1, '2', Number.isInteger]); // false

With Receive

receive accepts a pattern or list of patterns as its first argument. These patterns are compiled if they are not already.

receive accepts multiple predicates to compare against incoming values for the individual call. However, to determine which predicate was satisified, one would need to re-run each predicate until one is matched.

receiveWithPredicate attempts to work around this issue using the following pattern:

import {OTPNode, Symbols, compile, Pid} from '@otpjs/core';
const node = new OTPNode();

const predicates = {
    justOK: compile(ok),
    okWithPid: compile([ok, Pid.isPid]),
    okWithRef: compile([ok, Ref.isRef]),
    okWithOther: compile([ok, _]),
}
const pid = node.spawn(ctx => {
    const [message, predicate] = ctx.receiveWithPredicate(
        [
            predicates.justOK,
            predicates.okWithPd,
            predicates.okWithRef,
            predicates.okWithOther
        ]
    );
    
    if (predicate === predicates.okWithPid) {
        const [ok, pid] = message;
        // ...
    } else if (predicate === predicates.okWithRef) {
        const [ok, ref] = message;
        // ...
    } // ...
})

Processes

Lifecycle

As in Erlang, every process has a unique identifier associated with it, as well as message queues.

Process lifecycles are tracked through Promises. As long as you have an unresolved Promise in your context it will be considered to be alive. Once your promise chain ends, your context is considered to be dead! In this way, think of your context/promise-chain as an Erlang process.

Library

proc_lib

A limited proc_lib implementation is defined.

npm i @otpjs/proc_lib

gen_server

A limited gen_server implementation is defined.

Install

npm i @otpjs/gen_server

Usage

import {OTPNode, caseOf, Symbols} from '@otpjs/core';
import * as gen_server from '@otpjs/gen_server';

const {ok} = Symbols;
const {reply} = gen_server.Symbols;

const callbacks = {
    init,
    handleCall,
    handleCast,
    handleInfo,
    terminate
};

export function start(ctx) {
    return gen_server.start(ctx, callbacks)
}

export function startLink(ctx) {
    return gen_server.startLink(ctx, callbacks)
}

export function myRemoteFunction(ctx, pid, ...args) {
    return gen_server.call(ctx, pid, 'my_remote_function', ...args);
}

function init(ctx) {
    return [ok, Math.random()]
}

function handleCall(ctx, call, from, state) {
    const nextState = Math.random();
    const compare = caseOf(call);
    return [reply, ok, nextState];
}

function handleCast(ctx, cast, state) {
    const nextState = Math.random();
    return [noreply, nextState];
} 

function handleInfo(ctx, info, state) {
    const nextState = Math.random();
    return [noreply, nextState];
}

function terminate(ctx, reason, state) {
    // Pre-death cleanup
    return ok;
}

Roadmap

Long term goals include but are not limited to:

  • Full replication of the OTP core process patterns
    • Finish
      • proc_lib
      • gen_server
      • supervisor
    • Develop
      • gen_statem
      • gen_event
      • gen_rpc
  • Functional net kernel
  • zmq transport
    • network discovery
    • inproc communication for multi-threaded communication via node's cluster module and/or related modules
  • erlang distribution protocol transport
  • pure wss transport (socket.io transport exists as a reference implementation)
  • babel plugin for extended syntax support
    • ! operator
    • case statements
    • function clauses
0.17.8

3 years ago

0.18.0

3 years ago

0.17.3

3 years ago

0.17.4

3 years ago

0.17.5

3 years ago

0.17.6

3 years ago

0.17.7

3 years ago

0.17.0

3 years ago

0.17.1

3 years ago

0.15.4

3 years ago

0.15.5

3 years ago

0.15.6

3 years ago

0.15.7

3 years ago

0.15.3

3 years ago

0.16.0

3 years ago

0.15.0

3 years ago

0.15.1

3 years ago

0.13.3

3 years ago

0.13.4

3 years ago

0.13.5

3 years ago

0.14.0

3 years ago

0.14.2

3 years ago

0.13.0

3 years ago

0.13.1

3 years ago

0.13.2

3 years ago

0.12.1

4 years ago

0.12.2

4 years ago

0.12.3

4 years ago

0.12.5

4 years ago

0.12.6

4 years ago

0.12.0

4 years ago

0.11.0

4 years ago

0.11.1

4 years ago

0.11.2

4 years ago

0.10.0

4 years ago

0.10.0-beta.21

4 years ago

0.10.0-beta.20

4 years ago

0.10.0-beta.2

4 years ago

0.10.0-beta.0

4 years ago

0.10.0-beta.1

4 years ago

0.10.0-beta.4

4 years ago

0.10.0-beta.5

4 years ago

0.10.0-beta.15

4 years ago

0.10.0-beta.18

4 years ago

0.10.0-beta.12

4 years ago

0.10.0-beta.11

4 years ago

0.10.0-beta.13

4 years ago

0.10.0-beta.19

4 years ago

0.10.0-beta.10

4 years ago

0.9.12

4 years ago

0.9.14

4 years ago

0.9.10

4 years ago

0.9.11

4 years ago

0.9.8

4 years ago

0.9.7

4 years ago

0.9.9

4 years ago

0.9.0

4 years ago

0.9.2

4 years ago

0.9.1

4 years ago

0.9.4

4 years ago

0.9.3

4 years ago

0.9.6

4 years ago

0.9.5

4 years ago

0.8.5

4 years ago

0.8.4

4 years ago

0.8.3

4 years ago

0.8.2

4 years ago

0.8.1

4 years ago

0.8.0

4 years ago

0.7.11

4 years ago

0.7.10

4 years ago

0.7.9

4 years ago

0.7.6

4 years ago

0.7.8

4 years ago

0.7.7

4 years ago

0.7.5

4 years ago

0.7.2

4 years ago

0.7.0

4 years ago

0.6.1

4 years ago

0.6.0

4 years ago

0.5.10

4 years ago

0.5.9

4 years ago

0.5.12

4 years ago

0.5.13

4 years ago

0.5.8

4 years ago

0.5.5

4 years ago

0.5.3

4 years ago

0.5.2

4 years ago

0.5.1

4 years ago

0.4.9

4 years ago

0.4.8

4 years ago

0.4.11

4 years ago

0.4.12

4 years ago

0.5.0

4 years ago

0.4.7

4 years ago

0.4.5

4 years ago

0.4.6

4 years ago

0.4.4

4 years ago

0.4.3

4 years ago

0.4.2

4 years ago

0.4.1

4 years ago

0.4.0

4 years ago

0.3.0

4 years ago

0.2.1

4 years ago

0.2.0

4 years ago

0.2.5

4 years ago

0.2.4

4 years ago

0.1.1

4 years ago