Skip to content

Commit

Permalink
update context to be class instead of prototype for better development
Browse files Browse the repository at this point in the history
  • Loading branch information
ragokan committed Sep 15, 2024
1 parent 320a8f6 commit 867083e
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 221 deletions.
11 changes: 5 additions & 6 deletions packages/server/src/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { __BunicornContext } from "../context/base.ts";
import type { BunicornContext } from "../context/types.ts";
import { BunicornContext } from "../context/base.ts";
import { __checkPathIsRegex } from "../helpers/checkIsRegex.ts";
import { __createDependencyStore } from "../helpers/di.ts";
import { __getPath } from "../helpers/pathRegexps.ts";
Expand Down Expand Up @@ -127,13 +126,13 @@ export class BunicornApp<
}

try {
const context = new __BunicornContext(
const context = new BunicornContext(
request,
url as TBasePath,
route,
match,
BunicornApp.getFromStore,
) as BunicornContext<BasePath, never>;
match,
route,
);

const { middlewares, handler } = route;
const middlewaresLength = middlewares.length;
Expand Down
281 changes: 126 additions & 155 deletions packages/server/src/context/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,170 +7,141 @@ import { __getParams } from "../helpers/pathUtils.ts";
import type { BasePath, __BuiltRoute } from "../router/types.ts";
import type { BunicornSchema } from "../validation/types.ts";
import { __validate } from "../validation/validate.ts";
import type {
BuniResponseInit,
BunicornContextConstructor,
__PrivateBunicornContext,
} from "./types.ts";

const __BunicornContext = function <TPath extends BasePath = BasePath>(
this: __PrivateBunicornContext<any, any>,
request: Request,
url: TPath,
route: __BuiltRoute<TPath>,
match: string[] | boolean,
get: GetDependencyFn,
) {
// Base
this.request = request;
this.url = url;
this.route = route;
this.match = match;
this.get = get;
this.params = __getParams(route.path, match);
} as any as BunicornContextConstructor;

// Getters
__BunicornContext.prototype.getText = async function (
this: __PrivateBunicornContext & { __text: string },
) {
return (this.__text ??= await this.request.text());
};

__BunicornContext.prototype.getBody = async function (
this: __PrivateBunicornContext & { __body: any },
) {
// Cache body
if (this.__body !== undefined) {
return this.__body;
import type { BuniResponseInit, __PrivateBunicornContext } from "./types.ts";

export class BunicornContext<
TPath extends BasePath = BasePath,
InputSchema = never,
> {
private __text: string | undefined;
private __body: object | undefined;
private __searchParams: object | undefined;
private resultHeaders: Record<string, string> | undefined;
private __params: Record<string, string> | undefined;

constructor(
public request: Request,
public url: TPath,
public get: GetDependencyFn,
protected match: string[] | boolean,
protected route: __BuiltRoute<TPath>,
) {}

public get params() {
return (this.__params ??= __getParams(this.route.path, this.match));
}

const { route, request } = this;
const contentType = request.headers.get("Content-Type") || "";
public async getText() {
return (this.__text ??= await this.request.text());
}

public async getBody(): Promise<InputSchema> {
// Cache body
if (this.__body !== undefined) {
return this.__body as InputSchema;
}

let _body: any;
const { route, request } = this;
const contentType = request.headers.get("Content-Type") || "";

let _body: any;

if (contentType.startsWith("application/json")) {
_body = await request.json();
} else if (contentType.startsWith("multipart/form-data")) {
_body = await request
.formData()
.then((data) => formDataToObject(data, route.input));
} else {
_body = await request.text();
}

if (contentType.startsWith("application/json")) {
_body = await request.json();
} else if (contentType.startsWith("multipart/form-data")) {
_body = await request
.formData()
.then((data) => formDataToObject(data, route.input));
} else {
_body = await request.text();
return (this.__body = route.input ? __validate(route.input, _body) : _body);
}

return (this.__body = route.input ? __validate(route.input, _body) : _body);
};

__BunicornContext.prototype.getSearchParams = function (
this: __PrivateBunicornContext & { __searchParams: any },
schema?: BunicornSchema,
) {
const result = (this.__searchParams ??= __getSearchParams(this.url));
return schema ? __validate(schema, result) : result;
};

__BunicornContext.prototype.getHeader = function (
this: __PrivateBunicornContext,
name: string,
) {
return this.request.headers.get(name);
};

// Setters
__BunicornContext.prototype.setHeader = function (
this: __PrivateBunicornContext,
name: string,
value: string,
) {
(this.resultHeaders ??= {})[name] = value;
};

// Responses
__BunicornContext.prototype.ok = function (this: __PrivateBunicornContext) {
return new Response(undefined, {
status: 200,
headers: this.resultHeaders,
}) as any;
};

__BunicornContext.prototype.raw = function <T extends BodyInit | null>(
this: __PrivateBunicornContext,
body: T,
init: BuniResponseInit = {},
) {
this.applyHeaders(init);
return new Response(body, init) as unknown as T;
};

__BunicornContext.prototype.text = function (
this: __PrivateBunicornContext,
body: string,
init: any = {},
) {
(init.headers ??= {})["Content-Type"] = "text/plain";
this.applyHeaders(init);
return new Response(body, init) as any;
};

__BunicornContext.prototype.json = function <T extends Record<any, any>>(
this: __PrivateBunicornContext,
body: T,
init: BuniResponseInit = {},
) {
const headers = (init.headers ??= {});
Object.assign(headers, this.resultHeaders);

const { route } = this;

if (route.output) {
try {
const parseResult = __validate(route.output, body, route.__outputOptions);
return Response.json(parseResult, init) as any as T;
} catch (error) {
BunicornApp.onGlobalError(
new BunicornError(
`Failed to parse output for the method '${route.method}' to path '${route.path}'.`,
{
data: {
path: route.path,
output: body,
schema: route.output,
issues: error,
public getSearchParams(schema?: BunicornSchema) {
const result = (this.__searchParams ??= __getSearchParams(this.url));
return schema ? __validate(schema, result) : result;
}

public getHeader(name: string) {
return this.request.headers.get(name);
}

public setHeader(name: string, value: string) {
(this.resultHeaders ??= {})[name] = value;
return this;
}

public ok() {
return new Response(undefined, {
status: 200,
headers: this.resultHeaders,
}) as any;
}

public raw<T extends BodyInit | null>(body: T, init: BuniResponseInit = {}) {
this.applyHeaders(init);
return new Response(body, init) as unknown as T;
}

public text(body: string, init: BuniResponseInit = {}) {
(init.headers ??= {})["Content-Type"] = "text/plain";
this.applyHeaders(init);
return new Response(body, init) as any;
}

public json<T extends Record<any, any>>(
body: T,
init: BuniResponseInit = {},
) {
const headers = (init.headers ??= {});
Object.assign(headers, this.resultHeaders);

const { route } = this;

if (route.output) {
try {
const parseResult = __validate(
route.output,
body,
route.__outputOptions,
);
return Response.json(parseResult, init) as any as T;
} catch (error) {
BunicornApp.onGlobalError(
new BunicornError(
`Failed to parse output for the method '${route.method}' to path '${route.path}'.`,
{
data: {
path: route.path,
output: body,
schema: route.output,
issues: error,
},
},
},
),
);
throw new BunicornError(
"Failed to parse output. This should be handled internally.",
);
),
);
throw new BunicornError(
"Failed to parse output. This should be handled internally.",
);
}
}

return Response.json(body, init) as unknown as T;
}

return Response.json(body, init) as unknown as T;
};

__BunicornContext.prototype.stream = function <T>(
this: __PrivateBunicornContext,
body: ReadableStream<T>,
init: BuniResponseInit = {},
) {
init.headers ??= {};
init.headers["Content-Type"] = "text/event-stream";
this.applyHeaders(init);
return new Response(body, init) as unknown as ReadableStream<T>;
};

// Helpers
__BunicornContext.prototype.applyHeaders = function (
this: __PrivateBunicornContext,
init: any,
) {
const resultHeaders = this.resultHeaders;
if (resultHeaders) {
Object.assign((init.headers ??= {}), resultHeaders);
public stream<T>(body: ReadableStream<T>, init: BuniResponseInit = {}) {
init.headers ??= {};
init.headers["Content-Type"] = "text/event-stream";
this.applyHeaders(init);
return new Response(body, init) as unknown as ReadableStream<T>;
}
};

export { __BunicornContext };
private applyHeaders(init: BuniResponseInit) {
const resultHeaders = this.resultHeaders;
if (resultHeaders !== undefined) {
Object.assign((init.headers ??= {}), resultHeaders);
}
}
}
60 changes: 5 additions & 55 deletions packages/server/src/context/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import type {
__ExtractParams,
} from "../router/types.ts";

import type {
BunicornSchema,
__InferBunicornOutput,
} from "../validation/types.ts";
import type { __InferBunicornOutput } from "../validation/types.ts";
import type { BunicornContext } from "./base.ts";

export interface __CreateContextArgs<TPath extends BasePath> {
route: __BuiltRoute<TPath>;
Expand All @@ -27,58 +25,10 @@ export interface BuniResponseInit {
statusText?: string;
}

export interface BunicornContext<
export type __PrivateBunicornContext<
TPath extends BasePath = BasePath,
InputSchema = never,
> {
// Base
request: Request;
url: TPath;
route: __BuiltRoute<TPath>;
match: string[] | boolean;
get: GetDependencyFn;
params: __ExtractParams<TPath>;

// Getters
getText(): Promise<string>;
getBody(): Promise<InputSchema>;
getSearchParams<Body extends Record<string, string>>(): Body;
getSearchParams<TSchema extends BunicornSchema>(
schema: TSchema,
): __InferBunicornOutput<TSchema>;
getHeader(name: string): string | null;

// Setters
setHeader(name: string, value: string): void;

// Responses
ok(): any;
raw<T>(body: T, init?: BuniResponseInit): T;
text(body: string, init?: BuniResponseInit): string;
json<T>(body: T, init?: BuniResponseInit): T;
stream<T>(
body: ReadableStream<T>,
init?: BuniResponseInit,
): ReadableStream<T>;
}

export interface __PrivateBunicornContext<
TPath extends BasePath = BasePath,
InputSchema = never,
> extends BunicornContext<TPath, InputSchema> {
> = BunicornContext<TPath, InputSchema> & {
resultHeaders?: Record<string, string>;
applyHeaders(init: any): void;
}

export interface BunicornContextConstructor<
TPath extends BasePath = BasePath,
InputSchema = never,
> {
new (
request: Request,
url: TPath,
route: __BuiltRoute<TPath>,
match: string[] | boolean,
get: GetDependencyFn,
): BunicornContext<TPath, InputSchema>;
}
};
Loading

0 comments on commit 867083e

Please sign in to comment.