Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server-context): server/api request base classes #1219

Merged
merged 13 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions core/fsm2/src/fsm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ export abstract class FiniteStateMachine<S extends string, E extends string> ext
}

/**
* Subscribe to context changes.
* Subscribe to state changes.
*/
subscribe(listenerCallback: ListenerCallback<this, S>, options?: SubscribeOptions): SubscribeResult {
return super._subscribe(listenerCallback, options);
}

/**
* Unsubscribe from context.
* Unsubscribe from changes.
*/
unsubscribe(listenerCallback: ListenerCallback<this, S>): void {
return super._unsubscribe(listenerCallback);
Expand Down
Empty file.
3 changes: 3 additions & 0 deletions core/server-context/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Alwatr Server Context - `@alwatr/server-context`

Elegant powerful server-context manager base on alwatr signal, written in tiny TypeScript, ES module.
44 changes: 44 additions & 0 deletions core/server-context/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@alwatr/server-context",
"version": "0.32.0",
"description": "Elegant powerful context manager base on alwatr signal, written in tiny TypeScript, ES module.",
"keywords": [
"context",
"server-context",
"signal",
"typescript",
"esm",
"alwatr"
],
"main": "index.js",
"type": "module",
"types": "index.d.ts",
"author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
"license": "MIT",
"files": [
"**/*.{d.ts.map,d.ts,js.map,js,html,md}"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/AliMD/alwatr",
"directory": "core/server-context"
},
"homepage": "https://github.com/AliMD/alwatr/tree/main/core/server-context#readme",
"bugs": {
"url": "https://github.com/AliMD/alwatr/issues"
},
"dependencies": {
"@alwatr/fetch": "^0.32.0",
"@alwatr/fsm": "^0.32.0",
"@alwatr/logger": "^0.32.0",
"@alwatr/signal": "^0.32.0",
"@alwatr/util": "^0.32.0",
"tslib": "^2.5.2"
},
"devDependencies": {
"@alwatr/type": "^0.32.0"
}
}
103 changes: 103 additions & 0 deletions core/server-context/src/api-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {NODE_MODE} from '@alwatr/logger';
import {getClientId} from '@alwatr/util';

import {AlwatrServerRequestBase} from './server-request.js';

import type {ServerRequestState} from './server-request.js';
import type {FetchOptions} from '@alwatr/fetch/type.js';
import type {ListenerCallback, SubscribeOptions, SubscribeResult} from '@alwatr/signal2';
import type {AlwatrServiceResponse, Stringifyable, StringifyableRecord} from '@alwatr/type';

export abstract class AlwatrApiRequestBase<
TData extends Stringifyable = Stringifyable,
TMeta extends StringifyableRecord = StringifyableRecord
> extends AlwatrServerRequestBase {
protected _responseJson?: AlwatrServiceResponse<TData, TMeta>;

protected override async _$fetch(options: FetchOptions): Promise<void> {
if (!NODE_MODE) {
options.headers ??= {};
if (!options.headers['client-id']) {
options.headers['client-id'] = getClientId();
}
}

await super._$fetch(options);

let responseText: string;
try {
responseText = await this._response!.text();
}
catch (err) {
this._logger.error('_$fetch', 'invalid_response_text', err);
throw err;
}

try {
this._responseJson = JSON.parse(responseText);
}
catch (err) {
this._logger.error('_$fetch', 'invalid_response_json', err, {responseText});
throw err;
}

const responseJson = this._responseJson!;
if (responseJson.ok !== true) {
if (typeof responseJson.errorCode === 'string') {
this._logger.accident('_$fetch', responseJson.errorCode, 'fetch response not ok', {responseJson});
throw new Error(responseJson.errorCode);
}
else {
this._logger.error('_$fetch', 'fetch_nok', 'fetch response json not ok', {responseJson});
throw new Error('fetch_nok');
}
}
}

protected override _reset(): void {
super._reset();
delete this._responseJson;
}
}

export class AlwatrApiRequest<
TData extends Stringifyable = Stringifyable,
TMeta extends StringifyableRecord = StringifyableRecord
> extends AlwatrApiRequestBase<TData, TMeta> {
/**
* Current state.
*/
get state(): ServerRequestState {
return this._state;
}

get response(): AlwatrServiceResponse<TData, TMeta> | undefined {
return this._responseJson;
}

get _fetchResponse(): Response | undefined {
return this._response;
}

request(options?: Partial<FetchOptions>): void {
return this._request(options);
}

/**
* Subscribe to state changes.
*/
subscribe(listenerCallback: ListenerCallback<this, ServerRequestState>, options?: SubscribeOptions): SubscribeResult {
return this._subscribe(listenerCallback, options);
}

/**
* Unsubscribe from changes.
*/
unsubscribe(listenerCallback: ListenerCallback<this, ServerRequestState>): void {
return super._unsubscribe(listenerCallback);
}

cleanup(): void {
this._reset();
}
}
2 changes: 2 additions & 0 deletions core/server-context/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './server-request.js';
export * from './api-request.js';
134 changes: 134 additions & 0 deletions core/server-context/src/server-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {fetch} from '@alwatr/fetch';
import {FiniteStateMachineBase} from '@alwatr/fsm2';

import type {FetchOptions} from '@alwatr/fetch/type.js';
import type {ListenerCallback, SubscribeOptions, SubscribeResult} from '@alwatr/signal2';

export interface ServerRequestConfig extends Partial<FetchOptions> {
name: string;
}

export type ServerRequestState = 'initial' | 'loading' | 'failed' | 'complete';

export type ServerRequestEvent = 'request' | 'requestFailed' | 'requestSuccess';

export abstract class AlwatrServerRequestBase extends FiniteStateMachineBase<ServerRequestState, ServerRequestEvent> {
protected _$fetchOptions?: FetchOptions;
protected _response?: Response;

constructor(protected _config: ServerRequestConfig) {
super({name: _config.name, initialState: 'initial'});

this._stateRecord = {
initial: {
request: 'loading',
},
loading: {
requestFailed: 'failed',
requestSuccess: 'complete',
},
failed: {
request: 'loading',
},
complete: {
request: 'loading',
},
};

this._actionRecord = {
_on_loading_enter: this._requestAction,
};
}

protected _request(options?: Partial<FetchOptions>): void {
this._logger.logMethodArgs?.('_request', options);
this._setOptions(options);
this._transition('request');
}

protected async _$fetch(options: FetchOptions): Promise<void> {
this._logger.logMethodArgs?.('_$fetch', options);
this._response = await fetch(options);

if (!this._response.ok) {
throw new Error('fetch_nok');
}
}

protected async _requestAction(): Promise<void> {
this._logger.logMethod?.('_requestAction');

try {
if (this._$fetchOptions === undefined) {
throw new Error('invalid_fetch_options');
}

await this._$fetch(this._$fetchOptions);

this._transition('requestSuccess');
}
catch (err) {
this._logger.error('_request', 'fetch_failed', err);
this._transition('requestFailed');
}
}

protected _setOptions(options?: Partial<FetchOptions>): void {
this._logger.logMethodArgs?.('_setOptions', {options});

const fetchOptions = {
...this._config,
...options,
queryParameters: {
...this._config.queryParameters,
...options?.queryParameters,
},
};

if (fetchOptions.url == null) {
throw new Error('invalid_fetch_options');
}

this._$fetchOptions = fetchOptions as FetchOptions;
}

protected override _reset(): void {
super._reset();
delete this._response;
}
}

export class AlwatrServerRequest extends AlwatrServerRequestBase {
/**
* Current state.
*/
get state(): ServerRequestState {
return this._state;
}

get response(): Response | undefined {
return this._response;
}

request(options?: Partial<FetchOptions>): void {
return this._request(options);
}

/**
* Subscribe to state changes.
*/
subscribe(listenerCallback: ListenerCallback<this, ServerRequestState>, options?: SubscribeOptions): SubscribeResult {
return this._subscribe(listenerCallback, options);
}

/**
* Unsubscribe from changes.
*/
unsubscribe(listenerCallback: ListenerCallback<this, ServerRequestState>): void {
return this._unsubscribe(listenerCallback);
}

cleanup(): void {
this._reset();
}
}
20 changes: 20 additions & 0 deletions core/server-context/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"extends": "../../tsconfig.base",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": ".tsbuildinfo",
"rootDir": "src",
"outDir": "."
},

"include": ["src/**/*.ts"],
"exclude": [],
"references": [
{"path": "../logger"},
{"path": "../signal2"},
{"path": "../util"},
{"path": "../fetch"},
{"path": "../fsm2"},
{"path": "../type"}
]
}
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<li><a href="./es-bench/">ES Bench</a></li>
<li><a href="./finite-state-machine/">FSM</a></li>
<li><a href="./validator/">Validator</a></li>
<li><a href="./server-context/">Server Context</a></li>
</ol>
</body>
</html>
13 changes: 13 additions & 0 deletions demo/server-context/api-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {AlwatrApiRequest} from '@alwatr/server-context';

const apiRequest = new AlwatrApiRequest({
name: 'demo.server-request',
});

apiRequest.request({
url: 'https://order.soffit.co/api/v1/publistore/',
});

apiRequest.subscribe(() => {
console.log('serverRequest: %o', {state: apiRequest.state, response: apiRequest.response});
});
13 changes: 13 additions & 0 deletions demo/server-context/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Context</title>
<script type="module" src="./server-context.js"></script>
</head>
<body>

</body>
</html>
13 changes: 13 additions & 0 deletions demo/server-context/server-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {AlwatrServerRequest} from '@alwatr/server-context';

const serverRequest = new AlwatrServerRequest({
name: 'demo.server-request',
});

serverRequest.request({
url: 'http://httpbin.org/uuid',
});

serverRequest.subscribe(async () => {
console.log('serverRequest: %o', {state: serverRequest.state, response: await serverRequest.response?.text()});
});
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
{"path": "./core/signal"},
{"path": "./core/signal2"},
{"path": "./core/context"},
{"path": "./core/server-context"},
{"path": "./core/router"},
{"path": "./core/i18n"},
{"path": "./core/math"},
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8443,7 +8443,7 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==

tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.5.3:
tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.5.2, tslib@^2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
Expand Down