diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..c393a8c --- /dev/null +++ b/index.ts @@ -0,0 +1,206 @@ +interface ObserverHandlers { + next?: (value: T) => void; + error?: (error: unknown) => void; + complete?: () => void; +} + +interface Subscription { + unsubscribe(): void; +} + +type UnsubscribeFunction = () => void; + +type SubscribeFunction = (observer: Observer) => UnsubscribeFunction; + +class Observer { + private readonly handlers: ObserverHandlers; + private isUnsubscribed: boolean; + private unsubscribeFunction?: UnsubscribeFunction; + + constructor(handlers: ObserverHandlers) { + this.handlers = handlers; + this.isUnsubscribed = false; + } + + next(value: T): void { + if (this.isUnsubscribed) { + return; + } + + this.handlers.next?.(value); + } + + error(error: unknown): void { + if (this.isUnsubscribed) { + return; + } + + this.handlers.error?.(error); + this.unsubscribe(); + } + + complete(): void { + if (this.isUnsubscribed) { + return; + } + + this.handlers.complete?.(); + this.unsubscribe(); + } + + setUnsubscribe(unsubscribeFunction: UnsubscribeFunction): void { + this.unsubscribeFunction = unsubscribeFunction; + + if (this.isUnsubscribed) { + this.unsubscribeFunction(); + this.unsubscribeFunction = undefined; + } + } + + unsubscribe(): void { + if (this.isUnsubscribed) { + return; + } + + this.isUnsubscribed = true; + + if (this.unsubscribeFunction) { + this.unsubscribeFunction(); + this.unsubscribeFunction = undefined; + } + } +} + +class Observable { + private readonly subscribeFunction: SubscribeFunction; + + constructor(subscribeFunction: SubscribeFunction) { + this.subscribeFunction = subscribeFunction; + } + + static from(values: readonly T[]): Observable { + return new Observable((observer: Observer) => { + values.forEach((value) => { + observer.next(value); + }); + + observer.complete(); + + return () => { + console.log('unsubscribed'); + }; + }); + } + + subscribe(handlers: ObserverHandlers): Subscription { + const observer = new Observer(handlers); + const unsubscribeFunction = this.subscribeFunction(observer); + + observer.setUnsubscribe(unsubscribeFunction); + + return { + unsubscribe(): void { + observer.unsubscribe(); + }, + }; + } +} + +const HTTP_METHOD = { + post: 'POST', + get: 'GET', +} as const; + +const HTTP_STATUS = { + ok: 200, + internalServerError: 500, +} as const; + +const USER_ROLE = { + user: 'user', + admin: 'admin', +} as const; + +type HttpMethod = (typeof HTTP_METHOD)[keyof typeof HTTP_METHOD]; +type HttpStatus = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS]; +type UserRole = (typeof USER_ROLE)[keyof typeof USER_ROLE]; + +interface User { + name: string; + age: number; + roles: UserRole[]; + createdAt: Date; + isDeleted: boolean; +} + +type RequestParams = Record; + +interface ApiRequest { + method: HttpMethod; + host: string; + path: string; + params: RequestParams; + body?: TBody; +} + +interface ApiResponse { + status: HttpStatus; +} + +const userMock: User = { + name: 'User Name', + age: 26, + roles: [USER_ROLE.user, USER_ROLE.admin], + createdAt: new Date(), + isDeleted: false, +}; + +const requestsMock: ApiRequest[] = [ + { + method: HTTP_METHOD.post, + host: 'service.example', + path: 'user', + body: userMock, + params: {}, + }, + { + method: HTTP_METHOD.get, + host: 'service.example', + path: 'user', + params: { + id: '3f5h67s4s', + }, + }, +]; + +const handleRequest = (request: ApiRequest): ApiResponse => { + // handling of request + console.log(request.method, request.path); + + return { + status: HTTP_STATUS.ok, + }; +}; + +const handleError = (error: unknown): ApiResponse => { + // handling of error + console.error(error); + + return { + status: HTTP_STATUS.internalServerError, + }; +}; + +const handleComplete = (): void => { + console.log('complete'); +}; + +const requests$ = Observable.from>(requestsMock); + +const subscription = requests$.subscribe({ + next: handleRequest, + error: handleError, + complete: handleComplete, +}); + +subscription.unsubscribe();