Skip to content

Commit

Permalink
feat(client): add new package for client generated based on OpenAPI spec
Browse files Browse the repository at this point in the history
Signed-off-by: Jordan Shatford <jordanshatford@live.com>
  • Loading branch information
jordanshatford committed Aug 31, 2023
1 parent 72a0402 commit b5d0c08
Show file tree
Hide file tree
Showing 31 changed files with 1,102 additions and 12 deletions.
19 changes: 10 additions & 9 deletions apps/web/Dockerfile → Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
FROM node:lts-alpine as base

# Install PNPM the package manager we use
RUN npm install -g pnpm
RUN corepack enable

WORKDIR /frontend
WORKDIR /code

# Install required dependencies
COPY package.json svelte.config.js ./
RUN pnpm install
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./

# Copy over project files
COPY . .
COPY apps/ /code/apps/
COPY packages/ /code/packages/

RUN pnpm install

# Expose port we are running the frontend on
EXPOSE 5173

# In development we expose the Vite hot reload port and install all dependencies
FROM base as development
EXPOSE 24678
CMD [ "pnpm", "dev", "--host" ]
CMD [ "pnpm", "web", "dev", "--host" ]

# In production we run the build version of code without hot reload
FROM base as production
RUN pnpm build
CMD [ "pnpm", "preview", "--host", "--port=5173" ]
RUN pnpm web build
CMD [ "pnpm", "web", "preview", "--host", "--port=5173" ]
7 changes: 5 additions & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ services:
- 5173:5173
- 24678:24678
volumes:
- ./apps/web:/web
- /web/node_modules
- ./apps/web:/code/apps/web
- /code/apps/web/node_modules
- ./packages/client:/code/packages/client
- /code/packages/client/node_modules

2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
- 8080:8080
web:
build:
context: ./apps/web
context: .
target: production
image: youtubeaudiodownloader-web:latest
restart: always
Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"type": "module",
"scripts": {
"prepare": "husky install",
"generate": "pnpm --filter @yad/api generate:openapi && pnpm --filter @yad/client generate",
"dev": "pnpm --filter @yad/api --filter @yad/web dev",
"api": "pnpm --filter @yad/api -- ",
"web": "pnpm --filter @yad/web -- ",
"client": "pnpm --filter @yad/client -- ",
"build": "pnpm -r build",
"lint": "pnpm -r lint",
"format": "pnpm -r format",
Expand All @@ -20,6 +22,11 @@
"@commitlint/config-conventional": "^17.7.0",
"husky": "^8.0.3"
},
"pnpm": {
"overrides": {
"json-schema-ref-parser": "npm:@apidevtools/json-schema-ref-parser@10.1.0"
}
},
"packageManager": "pnpm@8.7.0",
"engines": {
"pnpm": ">=8",
Expand Down
16 changes: 16 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@yad/client",
"private": true,
"version": "1.0.0",
"license": "MIT",
"main": "./src/index.ts",
"scripts": {
"build": "tsc --project tsconfig.json",
"check": "tsc --project tsconfig.json --noEmit",
"generate": "openapi --input ../../apps/api/openapi.json --output ./src/generated --indent 2"
},
"devDependencies": {
"openapi-typescript-codegen": "^0.25.0",
"typescript": "^5.2.2"
}
}
36 changes: 36 additions & 0 deletions packages/client/src/DownloadsStatusService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { StatusUpdate } from "./generated/models/StatusUpdate";
import type { ApiRequestOptions } from "./generated/core/ApiRequestOptions";
import { resolve } from "./generated/core/request";
import { OpenAPI } from "./generated";

type EventSourceHandler<T> = (event: T) => void;

// Options used when getting information to setup the EventSource. This
// is not actually a GET request.
const options: ApiRequestOptions = {
method: 'GET',
url: '/downloads/status'
};

export class DownloadsStatusService {
public static async setup(
onMessage?: EventSourceHandler<StatusUpdate>,
onError?: EventSourceHandler<Event>,
onOpen?: EventSourceHandler<Event>
) : Promise<void> {
// Get token from OpenAPI set by user previously. This works in the
// same way as the generated code. This token is passed as a session_id
// query parameter to the endpoint as it cannot be passed in the header.
const sessionId = await resolve(options, OpenAPI.TOKEN);
const url = `${OpenAPI.BASE}${options.url}?session_id=${sessionId}`;
const source = new EventSource(url);
// Parse message data before callback.
source.onmessage = (event: MessageEvent) => {
const data = JSON.parse(event.data) as StatusUpdate
onMessage?.(data)
};
// Forward onerror and onopen messages incase user wants to use them.
source.onerror = (event) => onError?.(event);
source.onopen = (event) => onOpen?.(event);
}
}
25 changes: 25 additions & 0 deletions packages/client/src/generated/core/ApiError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';

export class ApiError extends Error {
public readonly url: string;
public readonly status: number;
public readonly statusText: string;
public readonly body: any;
public readonly request: ApiRequestOptions;

constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
super(message);

this.name = 'ApiError';
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.body = response.body;
this.request = request;
}
}
17 changes: 17 additions & 0 deletions packages/client/src/generated/core/ApiRequestOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ApiRequestOptions = {
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
readonly url: string;
readonly path?: Record<string, any>;
readonly cookies?: Record<string, any>;
readonly headers?: Record<string, any>;
readonly query?: Record<string, any>;
readonly formData?: Record<string, any>;
readonly body?: any;
readonly mediaType?: string;
readonly responseHeader?: string;
readonly errors?: Record<number, string>;
};
11 changes: 11 additions & 0 deletions packages/client/src/generated/core/ApiResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ApiResult = {
readonly url: string;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly body: any;
};
131 changes: 131 additions & 0 deletions packages/client/src/generated/core/CancelablePromise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export class CancelError extends Error {

constructor(message: string) {
super(message);
this.name = 'CancelError';
}

public get isCancelled(): boolean {
return true;
}
}

export interface OnCancel {
readonly isResolved: boolean;
readonly isRejected: boolean;
readonly isCancelled: boolean;

(cancelHandler: () => void): void;
}

export class CancelablePromise<T> implements Promise<T> {
#isResolved: boolean;
#isRejected: boolean;
#isCancelled: boolean;
readonly #cancelHandlers: (() => void)[];
readonly #promise: Promise<T>;
#resolve?: (value: T | PromiseLike<T>) => void;
#reject?: (reason?: any) => void;

constructor(
executor: (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: any) => void,
onCancel: OnCancel
) => void
) {
this.#isResolved = false;
this.#isRejected = false;
this.#isCancelled = false;
this.#cancelHandlers = [];
this.#promise = new Promise<T>((resolve, reject) => {
this.#resolve = resolve;
this.#reject = reject;

const onResolve = (value: T | PromiseLike<T>): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#isResolved = true;
this.#resolve?.(value);
};

const onReject = (reason?: any): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#isRejected = true;
this.#reject?.(reason);
};

const onCancel = (cancelHandler: () => void): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#cancelHandlers.push(cancelHandler);
};

Object.defineProperty(onCancel, 'isResolved', {
get: (): boolean => this.#isResolved,
});

Object.defineProperty(onCancel, 'isRejected', {
get: (): boolean => this.#isRejected,
});

Object.defineProperty(onCancel, 'isCancelled', {
get: (): boolean => this.#isCancelled,
});

return executor(onResolve, onReject, onCancel as OnCancel);
});
}

get [Symbol.toStringTag]() {
return "Cancellable Promise";
}

public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2> {
return this.#promise.then(onFulfilled, onRejected);
}

public catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> {
return this.#promise.catch(onRejected);
}

public finally(onFinally?: (() => void) | null): Promise<T> {
return this.#promise.finally(onFinally);
}

public cancel(): void {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#isCancelled = true;
if (this.#cancelHandlers.length) {
try {
for (const cancelHandler of this.#cancelHandlers) {
cancelHandler();
}
} catch (error) {
console.warn('Cancellation threw an error', error);
return;
}
}
this.#cancelHandlers.length = 0;
this.#reject?.(new CancelError('Request aborted'));
}

public get isCancelled(): boolean {
return this.#isCancelled;
}
}
32 changes: 32 additions & 0 deletions packages/client/src/generated/core/OpenAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ApiRequestOptions } from './ApiRequestOptions';

type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
type Headers = Record<string, string>;

export type OpenAPIConfig = {
BASE: string;
VERSION: string;
WITH_CREDENTIALS: boolean;
CREDENTIALS: 'include' | 'omit' | 'same-origin';
TOKEN?: string | Resolver<string> | undefined;
USERNAME?: string | Resolver<string> | undefined;
PASSWORD?: string | Resolver<string> | undefined;
HEADERS?: Headers | Resolver<Headers> | undefined;
ENCODE_PATH?: ((path: string) => string) | undefined;
};

export const OpenAPI: OpenAPIConfig = {
BASE: '',
VERSION: '1.0.0',
WITH_CREDENTIALS: false,
CREDENTIALS: 'include',
TOKEN: undefined,
USERNAME: undefined,
PASSWORD: undefined,
HEADERS: undefined,
ENCODE_PATH: undefined,
};
Loading

1 comment on commit b5d0c08

@vercel
Copy link

@vercel vercel bot commented on b5d0c08 Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.