Home

Awesome

Join our Discord to discuss about our software!

@hyperifyio/io.hyperstack.core

Common code for Hyperify Framework for TypeScript (as a Git Submodule).

Types in hyperstack

Hyper stack architecture evolves around types. Everything can be presented as a low level independent JSON data transfer object. Entity classes are higher level presentations of these DTO types with an easy-to-use public API for the programmer of Hyper stack application.

Data transfer objects (DTOs)

Data transfer objects are pure JSON-compatible objects which present an immutable state of a unit of something. Each DTO type has its own counterpart entity class. Usual way to construct a DTO object and it's utility functions is using the entity class and its higher level chainable methods.

Entity interfaces

Entity interfaces describe and document the public API of an entity class.

Entity classes

Entity classes are implementations of these entity interfaces. These classes will have the same internal properties as counterpart DTOs, but only provide access to these properties using setter and getter methods. Names strictly follow the same pattern based on the properties of the counterpart DTO.

Entity factory can be used to implement these classes without actually implementing the class itself. This is possible because of the strict pattern for naming the functionality. Only the entity interface must be defined by the programmer.

See also

It doesn't have many runtime dependencies

We don't have traditional releases

We don't have traditional releases. This project evolves directly to our git repository in an agile manner.

This git repository contains only the source code for a compile time use case. It is meant to be used as a git submodule in a NodeJS or webpack project.

See also hg.fi for easy NPM package creators for your project and other additional modules from us.

License

Copyright (c) Heusala Group Ltd. All rights reserved.

Each software release is initially under the HG Evaluation and Non-Commercial License for the first two years. This allows use, modification, and distribution for non-commercial and evaluation purposes only. Post this period, the license transitions to the standard MIT license, permitting broader usage, including commercial applications. For full details, refer to the LICENSE.md file.

Commercial usage licenses can be obtained under separate agreements.

Index

Install & maintain our library

Run the installation commands from your project's root directory. Usually it's where your package.json is located.

For these sample commands we expect your source files to be located in ./src and we'll use ./src/io/hyperify/core for location for our submodule.

Setup git submodule:

mkdir -p src/fi/hg
git submodule add git@github.com:hyperifyio/io.hyperify.core.git src/io/hyperify/core
git config -f .gitmodules submodule.src/io/hyperify/core.branch main

Next install our required dependencies (newest lodash library and reflect-metadata library):

npm install --save-dev lodash @types/lodash
npm install --save-dev reflect-metadata

We also use the moment library for time:

npm i 'moment-timezone' '@types/moment-timezone'

If you're going to develop NodeJS app, you might want to install also types for NodeJS (this should be obvious though):

npm install --save-dev @types/node

TypeScript configurations

The "experimentalDecorators": true, option must also be enabled in your TypeScript configuration in your project's ./tsconfig.json.

Checking out a project with git submodules

Git doesn't automatically clone your sub modules.

You'll need to command:

git clone --recurse-submodules git@github.com:heusalagroup/your-project.git your-project

...or:

git clone git@github.com:heusalagroup/your-project.git your-project
cd your-project
git submodule init
git submodule update

Updating upstream library code

Later when you want to update your submodules, you may do:

git pull
git submodule update --remote

Why git submodules, you may wonder?

NPM doesn't provide a good way to implement pure compile time typescript libraries.

We would have to compile our whole library in our bundle even though you probably don't use everything.

It wouldn't be possible to use compile time optimizations and other ENV based feature flags.

LogService

Our simple wrapper for console which allows naming the log context.

import LogService from "./src/io/hyperify/core/LogService";

const LOG = LogService.createLogger("FooService");

export class FooService {
    run(arg: string) {
        LOG.debug("Did something: ", arg);
    }
}

Observer

This is a simple observer implementation for implementing synchronous in-process events for a local service.

You'll use it like this:

import Observer from "./src/io/hyperify/core/Observer";

enum FooEvent {
    CHANGED = "FooService:changed",
}

class FooService {
    private static _data: any;

    private static _data : any;
    private static _observer : Observer<FooEvent> = new Observer<FooEvent>("GeoIpService");

    public static getData () : any {
        return this._data;
    }

    public static on (name : FooEvent, callback : ObserverCallback<FooEvent>) : ObserverDestructor {
        return this._observer.listenEvent(name, callback);
    }

    public static refreshData() {
        HttpService.doSomething()
            .then((response) => {
                this._data = response.data;

                this._observer.triggerEvent(FooEvent.CHANGED);
            })
            .catch((err) => {
                console.error("Error: ", err);
            });
    }
}

FooService.on(FooEvent.CHANGED, () => {
    const currentData = FooService.getData();
    // ...
});

FooService.refreshData();

Request

HTTP request mapping annotations for TypeScript in the same style as in Java's Spring @RequestMapping.

import Request, {
    GetMapping, 
    PostMapping, 
    RequestBody, 
    ResponseEntity, 
    RequestHeader, 
    RequestParam,
    Headers
} from "./src/io/hyperify/core/Request";

export interface ListDTO<T> {
    pageNumber: number;
    pageSize: number;
    content: Array<T>;
}

@RequestMapping("/foo/users")
@RequestMapping("/users")
export class UserController {
    private readonly _userService: UserService;

    constructor(userService: UserService) {
        this._userService = userService;
    }

    @GetMapping("/", "/list")
    public async getUserList(
        @RequestParam("p", Request.ParamType.INTEGER)
        pageNumber: number = 0,
        @RequestParam("l", Request.ParamType.INTEGER)
        pageSize: number = 10,
        @RequestHeader('accept', {defaultValue: '*/*'})
        accept: string
    ): Promise<ResponseEntity<ListDTO<UserModel>>> {
        
        // const parsedPageNumber = pageNumber ? parseInt(pageNumber, 10) : 0;
        // const parsedPageSize   = pageSize   ? parseInt(pageSize, 10)   : 10;

        return ResponseEntity.ok({
            pageNumber: pageNumber,
            pageSize: pageSize,
            content: await this._userService.getUserList(pageNumber, pageSize),
        });
        
    }

    @GetMapping("/items/{id}")
    public async getUserList(
        @PathVariable('id')
        id: string
    ): Promise<ResponseEntity<Json>> {
        
        return ResponseEntity.ok({
           itemId: id
        });
        
    }

    @PostMapping("/addUser")
    public async addUser (
        @RequestBody   user    : Json,
        @RequestHeader headers : Headers
    ) : Promise<ResponseEntity<Json>> {
        
        const host = headers.getHost();
        
        await this._userService.addUser(user);
        
        return ResponseEntity.ok({
            user: user,
            host: host
        });
        
    }
    
}

You can also use:

For the actual server implementing REST API, see next chapter.

RequestServer

This project also includes a simple and pure NodeJS implementation for the REST server implementing our Request annotated controllers:

import RequestServer from "./io/hyperify/core/RequestServer";
const server = new RequestServer("http://0.0.0.0:3000");
server.attachController(UserController);
server.start();

See also our ProcessUtils for best practices implementing complete runtime support.

Repository

We also provide a Spring Data inspired annotation mechanism for entities and CrudRepository implementation.

It's available from @hyperifyio/io.hyperify.repository.

ProcessUtils

ProcessUtils.initEnvFromDefaultFiles()

This utility class includes a simple implementation for runtime .env file support.

import ProcessUtils from "./io/hyperify/core/ProcessUtils";

// Must be first import to define environment variables before anything else
ProcessUtils.initEnvFromDefaultFiles();

ProcessUtils.setupDestroyHandler(shutdownHandler, errorHandler)

This utility function can be used to implement default shutdown handlers for the common runtime events.

It will hook into events exit, SIGTERM, SIGINT, SIGUSR1, SIGUSR2 and uncaughtException.

The shutdownHandler will be called only once.

If an exception is thrown, the errorHandler will be called with the exception.

import ProcessUtils from "./io/hyperify/core/ProcessUtils";

const server = new Server();

server.start();

ProcessUtils.setupDestroyHandler( () => {
    server.stop();
}, (err : any) => {
    LOG.error('Error while shutting down the service: ', err);
});

Upgrade from previous sendanor organization

This project was originally under Sendanor's organization in Github.

If that's the case for your local submodule, fix your git's remote:

git remote set-url origin git@github.com:hyperifyio/io.hyperify.core.git