-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(server-context): server/api request base classes (#1219)
- Loading branch information
Showing
14 changed files
with
350 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './server-request.js'; | ||
export * from './api-request.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters