Browse Source

Fix stream errors

Trying to customize request objects trigger typescript errors.
master
TheoryOfNekomata 6 months ago
parent
commit
a0e5c45db3
6 changed files with 37 additions and 69 deletions
  1. +5
    -3
      examples/basic/data-source.ts
  2. +2
    -0
      examples/basic/server.ts
  3. +1
    -1
      src/backend/extenders/method.ts
  4. +1
    -1
      src/backend/extenders/url.ts
  5. +6
    -6
      src/backend/handlers.ts
  6. +22
    -58
      src/backend/server.ts

+ 5
- 3
examples/basic/data-source.ts View File

@@ -1,7 +1,9 @@
import {DataSource, dataSources, Resource} from '../../src';
import {dataSources, Resource} from '../../src';
import {DataSource} from '../../src/backend/data-source';
import {BaseDataSource} from '../../src/common/data-source';


export const autoIncrement = async (dataSource: DataSource) => {
const data = await dataSource.getMultiple() as Record<string, string>[];
export const autoIncrement = async (dataSource: BaseDataSource) => {
const data = await (dataSource as DataSource).getMultiple() as Record<string, string>[];


const highestId = data.reduce<number>( const highestId = data.reduce<number>(
(highestId, d) => (Number(d.id) > highestId ? Number(d.id) : highestId), (highestId, d) => (Number(d.id) > highestId ? Number(d.id) : highestId),


+ 2
- 0
examples/basic/server.ts View File

@@ -15,6 +15,7 @@ const Piano = resource(v.object(
v.never() v.never()
)) ))
.name('Piano') .name('Piano')
.route('pianos')
.id('id', { .id('id', {
generationStrategy: autoIncrement, generationStrategy: autoIncrement,
serialize: (id) => id?.toString() ?? '0', serialize: (id) => id?.toString() ?? '0',
@@ -45,6 +46,7 @@ const User = resource(v.object(
schema: v.number(), schema: v.number(),
}) })
.name('User') .name('User')
.route('users')
.fullText('bio'); .fullText('bio');


const app = application({ const app = application({


+ 1
- 1
src/backend/extenders/method.ts View File

@@ -3,7 +3,7 @@ import http from 'http';
import {HttpMiddlewareError} from '../server'; import {HttpMiddlewareError} from '../server';


interface RequestContext extends http.IncomingMessage { interface RequestContext extends http.IncomingMessage {
method: string;
method?: string;
} }


export const adjustMethod = (req: RequestContext) => { export const adjustMethod = (req: RequestContext) => {


+ 1
- 1
src/backend/extenders/url.ts View File

@@ -7,7 +7,7 @@ interface RequestContext extends http.IncomingMessage {


query?: URLSearchParams; query?: URLSearchParams;


rawUrl: string;
rawUrl?: string;
} }


export const adjustUrl = (req: RequestContext) => { export const adjustUrl = (req: RequestContext) => {


+ 6
- 6
src/backend/handlers.ts View File

@@ -6,10 +6,10 @@ export const handleGetRoot: Middleware = (req) => {
const { backend, basePath } = req; const { backend, basePath } = req;


const data = { const data = {
name: backend.app.name
name: backend!.app.name
}; };


const registeredResources = Array.from(backend.app.resources);
const registeredResources = Array.from(backend!.app.resources);
const availableResources = registeredResources.filter((r) => ( const availableResources = registeredResources.filter((r) => (
r.state.canFetchCollection r.state.canFetchCollection
|| r.state.canCreate || r.state.canCreate
@@ -43,7 +43,7 @@ export const handleGetCollection: Middleware = async (req) => {
try { try {
// TODO querying mechanism // TODO querying mechanism
data = await resource.dataSource.getMultiple(query); // TODO paginated responses per resource data = await resource.dataSource.getMultiple(query); // TODO paginated responses per resource
if (backend.showTotalItemCountOnGetCollection && typeof resource.dataSource.getTotalCount === 'function') {
if (backend!.showTotalItemCountOnGetCollection && typeof resource.dataSource.getTotalCount === 'function') {
totalItemCount = await resource.dataSource.getTotalCount(query); totalItemCount = await resource.dataSource.getTotalCount(query);
} }
} catch (cause) { } catch (cause) {
@@ -133,7 +133,7 @@ export const handleDeleteItem: Middleware = async (req) => {
}); });
} }


if (!existing && backend.throws404OnDeletingNotFound) {
if (!existing && backend!.throws404OnDeletingNotFound) {
throw new HttpMiddlewareError('deleteNonExistingResource', { throw new HttpMiddlewareError('deleteNonExistingResource', {
statusCode: constants.HTTP_STATUS_NOT_FOUND statusCode: constants.HTTP_STATUS_NOT_FOUND
}); });
@@ -214,7 +214,7 @@ export const handleCreateItem: Middleware = async (req) => {
let totalItemCount: number | undefined; let totalItemCount: number | undefined;
try { try {
newObject = await resource.dataSource.create(params); newObject = await resource.dataSource.create(params);
if (backend.showTotalItemCountOnCreateItem && typeof resource.dataSource.getTotalCount === 'function') {
if (backend!.showTotalItemCountOnCreateItem && typeof resource.dataSource.getTotalCount === 'function') {
totalItemCount = await resource.dataSource.getTotalCount(); totalItemCount = await resource.dataSource.getTotalCount();
} }
} catch (cause) { } catch (cause) {
@@ -266,7 +266,7 @@ export const handleEmplaceItem: Middleware = async (req) => {


const headers: Record<string, string> = {}; const headers: Record<string, string> = {};
let totalItemCount: number | undefined; let totalItemCount: number | undefined;
if (backend.showTotalItemCountOnCreateItem && typeof resource.dataSource.getTotalCount === 'function') {
if (backend!.showTotalItemCountOnCreateItem && typeof resource.dataSource.getTotalCount === 'function') {
totalItemCount = await resource.dataSource.getTotalCount(); totalItemCount = await resource.dataSource.getTotalCount();
} }
if (isCreated) { if (isCreated) {


+ 22
- 58
src/backend/server.ts View File

@@ -1,9 +1,6 @@
import http from 'http'; import http from 'http';
import {BackendState} from './common'; import {BackendState} from './common';
import {Language, Resource, Charset, MediaType, LanguageStatusMessageMap} from '../common'; import {Language, Resource, Charset, MediaType, LanguageStatusMessageMap} from '../common';
import * as applicationJson from '../common/media-types/application/json';
import * as utf8 from '../common/charsets/utf-8';
import * as en from '../common/languages/en';
import https from 'https'; import https from 'https';
import Negotiator from 'negotiator'; import Negotiator from 'negotiator';
import {constants} from 'http2'; import {constants} from 'http2';
@@ -89,19 +86,19 @@ export interface CreateServerParams {
} }


export interface RequestContext extends http.IncomingMessage { export interface RequestContext extends http.IncomingMessage {
backend: BackendState;
backend?: BackendState;


host: string;
host?: string;


scheme: string;
scheme?: string;


basePath: string;
basePath?: string;


method: string;
method?: string;


url: string;
url?: string;


rawUrl: string;
rawUrl?: string;


cn: { cn: {
language: Language; language: Language;
@@ -122,39 +119,6 @@ export interface Middleware<Req extends RequestContext = RequestContext> {
(req: Req): undefined | Response | Promise<undefined | Response>; (req: Req): undefined | Response | Promise<undefined | Response>;
} }


class ServerYasumiRequest extends http.IncomingMessage implements RequestContext {
host = 'localhost';

scheme = 'http';

basePath = '';

backend = {} as BackendState;

resource = undefined as unknown as BackendResource;

resourceId?: string;

query = new URLSearchParams();

body?: unknown;

method = '';

url = '';

rawUrl = '';

readonly cn: {
language: Language;
mediaType: MediaType;
charset: Charset;
} = {
language: en,
mediaType: applicationJson,
charset: utf8,
};
}
const getAllowedMiddlewares = <T extends v.BaseSchema>(resource: Resource<T>, mainResourceId: string) => { const getAllowedMiddlewares = <T extends v.BaseSchema>(resource: Resource<T>, mainResourceId: string) => {
const middlewares = [] as [string, Middleware, v.BaseSchema?][]; const middlewares = [] as [string, Middleware, v.BaseSchema?][];
if (mainResourceId === '') { if (mainResourceId === '') {
@@ -210,13 +174,13 @@ const getAllowedMiddlewares = <T extends v.BaseSchema>(resource: Resource<T>, ma


const adjustRequestForContentNegotiation = (req: RequestContext, res: http.ServerResponse<RequestContext>) => { const adjustRequestForContentNegotiation = (req: RequestContext, res: http.ServerResponse<RequestContext>) => {
const negotiator = new Negotiator(req); const negotiator = new Negotiator(req);
const availableLanguages = Array.from(req.backend.app.languages);
const availableCharsets = Array.from(req.backend.app.charsets);
const availableMediaTypes = Array.from(req.backend.app.mediaTypes);
const availableLanguages = Array.from(req.backend!.app.languages);
const availableCharsets = Array.from(req.backend!.app.charsets);
const availableMediaTypes = Array.from(req.backend!.app.mediaTypes);


const languageCandidate = negotiator.language(availableLanguages.map((l) => l.name)) ?? req.backend.cn.language.name;
const charsetCandidate = negotiator.charset(availableCharsets.map((l) => l.name)) ?? req.backend.cn.charset.name;
const mediaTypeCandidate = negotiator.mediaType(availableMediaTypes.map((l) => l.name)) ?? req.backend.cn.mediaType.name;
const languageCandidate = negotiator.language(availableLanguages.map((l) => l.name)) ?? req.backend!.cn.language.name;
const charsetCandidate = negotiator.charset(availableCharsets.map((l) => l.name)) ?? req.backend!.cn.charset.name;
const mediaTypeCandidate = negotiator.mediaType(availableMediaTypes.map((l) => l.name)) ?? req.backend!.cn.mediaType.name;


// TODO refactor // TODO refactor
const currentLanguage = availableLanguages.find((l) => l.name === languageCandidate); const currentLanguage = availableLanguages.find((l) => l.name === languageCandidate);
@@ -255,9 +219,9 @@ const adjustRequestForContentNegotiation = (req: RequestContext, res: http.Serve


const responseBodyCharset = availableCharsets.find((l) => l.name === charsetCandidate); const responseBodyCharset = availableCharsets.find((l) => l.name === charsetCandidate);
if (typeof responseBodyCharset === 'undefined') { if (typeof responseBodyCharset === 'undefined') {
const data = req.backend?.cn.language.bodies.encodingNotAcceptable();
const responseRaw = req.backend?.cn.mediaType.serialize(data);
const response = typeof responseRaw !== 'undefined' ? req.backend?.cn.charset.encode(responseRaw) : undefined;
const data = req.backend!.cn.language.bodies.encodingNotAcceptable();
const responseRaw = req.backend!.cn.mediaType.serialize(data);
const response = typeof responseRaw !== 'undefined' ? req.backend!.cn.charset.encode(responseRaw) : undefined;
res.writeHead(constants.HTTP_STATUS_NOT_ACCEPTABLE, { res.writeHead(constants.HTTP_STATUS_NOT_ACCEPTABLE, {
'Content-Language': req.backend?.cn.language.name, 'Content-Language': req.backend?.cn.language.name,
'Content-Type': [ 'Content-Type': [
@@ -283,18 +247,18 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr
key: serverParams.key, key: serverParams.key,
cert: serverParams.cert, cert: serverParams.cert,
requestTimeout: serverParams.requestTimeout, requestTimeout: serverParams.requestTimeout,
IncomingMessage: ServerYasumiRequest,
}) })
: http.createServer({ : http.createServer({
requestTimeout: serverParams.requestTimeout, requestTimeout: serverParams.requestTimeout,
IncomingMessage: ServerYasumiRequest,
}); });


server.on('request', async (req: RequestContext, res) => {
server.on('request', async (req: RequestContext, res: http.ServerResponse<RequestContext>) => {
req.backend = backendState; req.backend = backendState;
req.basePath = serverParams.basePath ?? ''; req.basePath = serverParams.basePath ?? '';
req.host = serverParams.host ?? 'localhost'; req.host = serverParams.host ?? 'localhost';
req.scheme = isHttps ? 'https' : 'http'; req.scheme = isHttps ? 'https' : 'http';
req.cn = req.backend.cn;

adjustRequestForContentNegotiation(req, res); adjustRequestForContentNegotiation(req, res);


try { try {
@@ -373,7 +337,7 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr
return; return;
} }


const [, resourceRouteName, resourceId = ''] = req.url.split('/') ?? [];
const [, resourceRouteName, resourceId = ''] = req.url?.split('/') ?? [];
const resource = Array.from(req.backend.app.resources).find((r) => r.state!.routeName === resourceRouteName); const resource = Array.from(req.backend.app.resources).find((r) => r.state!.routeName === resourceRouteName);
if (typeof resource === 'undefined') { if (typeof resource === 'undefined') {
res.statusCode = constants.HTTP_STATUS_NOT_FOUND; res.statusCode = constants.HTTP_STATUS_NOT_FOUND;
@@ -414,8 +378,8 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr
} }


if (schema) { if (schema) {
const availableSerializers = Array.from(req.backend.app.mediaTypes);
const availableCharsets = Array.from(req.backend.app.charsets);
const availableSerializers = Array.from(req.backend!.app.mediaTypes);
const availableCharsets = Array.from(req.backend!.app.charsets);
const contentTypeHeader = req.headers['content-type'] ?? 'application/octet-stream'; const contentTypeHeader = req.headers['content-type'] ?? 'application/octet-stream';
const fragments = contentTypeHeader.split(';'); const fragments = contentTypeHeader.split(';');
const mediaType = fragments[0]; const mediaType = fragments[0];


Loading…
Cancel
Save