Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
uasan committed May 13, 2024
1 parent a58476a commit 2516a8f
Show file tree
Hide file tree
Showing 15 changed files with 156 additions and 28 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './src/interfaces/server/app.ts';
export * from './src/interfaces/server/context.ts';
export * from './src/interfaces/migration/context.js';
export * from './src/interfaces/server/cache.ts';
export * from './src/interfaces/security/WebAuthn.js';
export * from './src/interfaces/security/Challenge.ts';
export * from './src/interfaces/security/decorators.ts';
Expand Down
1 change: 1 addition & 0 deletions src/compiler/entities/api/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const methods = new Map()
.set('get', 'get')
.set('head', 'head')
.set('put', 'put')
.set('post', 'post')
.set('patch', 'patch')
Expand Down
65 changes: 47 additions & 18 deletions src/compiler/entities/api/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import {
isNotThisParameter,
isStringType,
isBigIntType,
typeToString,
hasUndefinedType,
getNonUndefinedType,
hasAsyncModifier,
} from '../../helpers/checker.js';
import { methods } from './constants.js';
import { makePayloadValidator } from '../../helpers/validator.js';
Expand All @@ -39,6 +39,7 @@ import { lookup } from '../../makers/declaration.js';

import { File } from '../../makers/types/validators/File.js';
import { BinaryData } from '../../makers/types/validators/BinaryData.js';
import { makeCache } from '#compiler/makers/decorators/cache.js';

export function makeRouteMethod(name, node) {
const statements = [];
Expand All @@ -51,6 +52,22 @@ export function makeRouteMethod(name, node) {
let payloadNode = node.parameters.find(isNotThisParameter);
let payloadType = payloadNode && getTypeOfNode(payloadNode);

let isAsyncAction = hasAsyncModifier(node);
let isAsyncHandler = isAsyncAction;

const isPrivate = node.modifiers?.some(
hasDecorator,
lookup.decorators.Permission
);

const cache = makeCache(
node.modifiers?.find(hasDecorator, lookup.decorators.Cache),
req,
res,
ctx,
isPrivate
);

const hasUndefinedReturnType = hasUndefinedType(returnType);

if (hasUndefinedReturnType) {
Expand All @@ -61,62 +78,74 @@ export function makeRouteMethod(name, node) {
let payload;
let pathParameters = '';

if (cache?.check) {
statements.push(cache.check);
}

statements.push(
factoryConstant(ctx, factoryCallThisMethod('create', [req, res]))
);

if (payloadType) {
const metaType = makePayloadValidator(node, payloadType);

if (name === 'get') {
if (name === 'get' || name === 'head') {
payload = factoryIdentifier('data');
const query = makePayloadFromQuery(payloadType);

pathParameters = query.path;
statements.push(factoryConstant(payload, query.data));
} else {
const body = makePayloadFromBody(metaType);

payload = body.data;
statements.push(factoryConstant(factoryIdentifier('data'), body.init));
}
}

if (node.modifiers?.some(hasDecorator, lookup.decorators.Permission)) {
if (isPrivate) {
isAsyncHandler = true;
statements.push(factoryAwaitStatement(factoryCallMethod(ctx, 'auth')));
}

ast = factoryAwait(factoryCallMethod(ast, name, payload && [payload]));
ast = factoryCallMethod(ast, name, payload && [payload]);

if (cache?.preset) {
statements.push(cache.preset);
}

if (isAsyncAction) {
ast = factoryAwait(ast);
}

if (isVoidLikeType(returnType)) {
statements.push(factoryStatement(ast), internals.respondNoContent(res));
(ast = factoryStatement(ast)), internals.respondNoContent(res);
} else if (File.isAssignable(returnType)) {
statements.push(internals.respondFile(res, ast));
ast = internals.respondFile(res, ast);
} else if (BinaryData.isAssignable(returnType) || isStringType(returnType)) {
statements.push(internals.respondBinary(res, ast));
ast = internals.respondBinary(res, ast);
} else if (isBigIntType(returnType)) {
statements.push(
internals.respondBinary(
res,
factoryPropertyParenthesized(
ast,
factoryCall(factoryIdentifier('toString')),
hasUndefinedReturnType
)
ast = internals.respondBinary(
res,
factoryPropertyParenthesized(
ast,
factoryCall(factoryIdentifier('toString')),
hasUndefinedReturnType
)
);
} else {
statements.push(internals.respondJson(res, ast));
ast = internals.respondJson(res, ast);
}

statements.push(ast);

host.entity.route.methods.push({
name: methods.get(name),
params: pathParameters,
});

return factoryStaticProperty(
factoryIdentifier(methods.get(name)),
factoryRouteFunction([
factoryRouteFunction(isAsyncHandler, [
factoryTryStatement(statements, factoryIdentifier('e'), [
internals.respondError(res, factoryIdentifier('e')),
]),
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/entities/api/maker.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function ServerContext(node) {
if (
member.kind === MethodDeclaration &&
methods.has(member.name.escapedText) &&
!member.modifiers.some(isStaticKeyword)
!member.modifiers?.some(isStaticKeyword)
)
members.push(makeRouteMethod(member.name.escapedText, member));
}
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/helpers/checker.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const {
export const {
Decorator,
TrueKeyword,
AsyncKeyword,
FalseKeyword,
StaticKeyword,
ExportKeyword,
Expand All @@ -58,6 +59,7 @@ export const isTrueKeyword = ({ kind }) => kind === TrueKeyword;
export const isFalseKeyword = ({ kind }) => kind === FalseKeyword;
export const isStaticKeyword = ({ kind }) => kind === StaticKeyword;
export const isDeclareKeyword = ({ kind }) => kind === DeclareKeyword;
export const isAsyncKeyword = ({ kind }) => kind === AsyncKeyword;

export const isAnyType = ({ flags }) => (flags & flagAny) !== 0;
export const isNullType = ({ flags }) => (flags & flagNull) !== 0;
Expand Down Expand Up @@ -154,6 +156,7 @@ export const isExtendsToken = ({ token }) => token === ExtendsKeyword;
export const isNotThisIdentifier = node => node.escapedText !== 'this';
export const isNotThisParameter = node => isNotThisIdentifier(node.name);

export const hasAsyncModifier = node => some(node.modifiers, isAsyncKeyword);
export const hasDeclareModifier = node =>
some(node.modifiers, isDeclareKeyword);

Expand Down
4 changes: 2 additions & 2 deletions src/compiler/helpers/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ export const factoryBodyFunction = statements =>
host.factory.createBlock(statements, false)
);

export const factoryRouteFunction = statements =>
export const factoryRouteFunction = (isAsync, statements) =>
host.factory.createArrowFunction(
[factoryToken(AsyncKeyword)],
isAsync ? [factoryToken(AsyncKeyword)] : undefined,
undefined,
[
factoryParameter(factoryIdentifier('res')),
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/makers/declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { TypedArray } from './types/validators/TypedArray.js';
import { BinaryData } from './types/validators/BinaryData.js';
import { BigIntSerial } from './types/validators/BigIntSerial.js';

import { Cache } from './decorators/cache.js';
import { Table } from './decorators/table.js';
import { Postgres } from './decorators/postgres.js';
import { Permission } from './decorators/permission.js';
Expand Down Expand Up @@ -66,6 +67,7 @@ export const lookup = {
},

decorators: {
Cache,
Table,
Postgres,
Permission,
Expand Down
38 changes: 38 additions & 0 deletions src/compiler/makers/decorators/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { factoryCallMethod } from '#compiler/helpers/call.js';
import {
getTypeOfNode,
isFalseKeyword,
isNumberType,
isTrueKeyword,
} from '#compiler/helpers/checker.js';
import { factoryIfReturn } from '#compiler/helpers/statement.js';

export function makeCache(decor, req, res, ctx) {
if (!decor?.expression) return;

const arg = decor.expression.arguments[0];
const name = decor.expression.expression;
const type = getTypeOfNode(arg);

if (isTrueKeyword(arg)) {
return {
check: factoryIfReturn(
factoryCallMethod(name, 'checkImmutable', [req, res])
),
preset: factoryCallMethod(name, 'setImmutable', [ctx]),
};
} else if (isFalseKeyword(arg)) {
return {
preset: factoryCallMethod(name, 'setNoStore', [ctx]),
};
} else if (isNumberType(type)) {
return {
check: factoryIfReturn(factoryCallMethod(name, 'checkAge', [req, res])),
preset: factoryCallMethod(name, 'setAge', [ctx, arg]),
};
}
}

export function Cache(node) {
return node;
}
12 changes: 12 additions & 0 deletions src/interfaces/server/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface CacheOptions {
maxAge?: number;
immutable?: boolean;
revalidate?: () => Promise<boolean>;
}

export declare function Cache(
options: boolean | number | CacheOptions
): (
target: (payload?: any) => any,
context: ClassMethodDecoratorContext
) => void;
2 changes: 2 additions & 0 deletions src/interfaces/types/File.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { BlobOptions } from './Blob.ts';



export declare class File<
T extends BlobOptions = undefined,
> extends globalThis.Blob {
Expand Down
41 changes: 41 additions & 0 deletions src/runtime/server/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { respondNotModified } from './response/respondCache.js';

export class Cache {
static checkImmutable(req, res) {
if (req.getHeader('if-none-match')) {
respondNotModified(res);
return true;
} else return false;
}

static setImmutable(context) {
context.response.headers.push(
'cache-control',
'max-age=31536000, immutable',
'etag',
'1'
);
}

static setNoStore(context) {
context.response.headers.push('cache-control', 'no-store');
}

static checkAge(req, res) {
const eTag = req.getHeader('if-none-match');

if (eTag && +eTag > Date.now()) {
respondNotModified(res);
return true;
} else return false;
}

static setAge(context, age) {
context.response.headers.push(
'cache-control',
'max-age=' + age,
'etag',
(Date.now() + age * 1000).toString()
);
}
}
2 changes: 0 additions & 2 deletions src/runtime/server/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export class ServerContext extends Context {
request = {
cookies: new Map(),
headers: {
etag: '',
range: '',
},
};
Expand All @@ -33,7 +32,6 @@ export class ServerContext extends Context {

parseCookies(context.request.cookies, req.getHeader('cookie'));

context.request.headers.etag = req.getHeader('etag') ?? '';
context.request.headers.range = req.getHeader('range') ?? '';

return context;
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/server/response/respondCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function respondNotModified(res) {
res.cork(() => {
res.writeStatus('304').end();
});
}
2 changes: 1 addition & 1 deletion src/runtime/server/response/respondError.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export function respondError(res, error) {

let status = error.status || 500;

if (res.context.connected) {
if (res.context?.connected) {
const type = error.constructor?.name || 'Error';

res.cork(() => {
Expand Down
4 changes: 0 additions & 4 deletions src/runtime/server/response/respondFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@ class FileSender {
response.writeHeader('content-type', file.type);
}

if (file.hash) {
response.writeHeader('etag', file.hash);
}

if (file.lastModified) {
response.writeHeader(
'last-modified',
Expand Down

0 comments on commit 2516a8f

Please sign in to comment.