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)
- Immutable
- Implemented as a TypeScript interface
- Can be presented as a pure JSON object
- Intended to be constructed and utilized using an entity class
- No methods (only utility functions)
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:
@Request.mapping
instead of@RequestMapping
,@Request.param
instead of@RequestParam
,@Request.header
instead of@RequestHeader
,@Request.body
instead of@RequestBody
,@Request.getMapping(...)
instead ofGetMapping(...)
orRequest.mapping(Request.Method.GET, ...)
@Request.putMapping(...)
instead ofPutMapping(...)
orRequest.mapping(Request.Method.PUT, ...)
@Request.postMapping(...)
instead ofPostMapping(...)
orRequest.mapping(Request.Method.POST, ...)
@Request.deleteMapping(...)
instead ofDeleteMapping(...)
orRequest.mapping(Request.Method.DELETE, ...)
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