Use Map instead of Set for easy referencing of endpoints and operations.refactor/new-arch
@@ -1,5 +1,6 @@ | |||||
import {Endpoint, EndpointOperations} from './endpoint'; | import {Endpoint, EndpointOperations} from './endpoint'; | ||||
import {Operation} from './operation'; | import {Operation} from './operation'; | ||||
import {NamedSet, PredicateMap} from './common'; | |||||
export interface BaseAppState { | export interface BaseAppState { | ||||
endpoints: unknown; | endpoints: unknown; | ||||
@@ -20,8 +21,8 @@ export type AppOperations<T extends App> = ( | |||||
export interface App<AppName extends string = string, AppState extends BaseAppState = BaseAppState> { | export interface App<AppName extends string = string, AppState extends BaseAppState = BaseAppState> { | ||||
name: AppName; | name: AppName; | ||||
operations: Set<Operation>; | |||||
endpoints: Set<Endpoint>; | |||||
endpoints: NamedSet<Endpoint>; | |||||
operations: NamedSet<Operation>; | |||||
operation<NewOperation extends Operation>(newOperation: NewOperation): App< | operation<NewOperation extends Operation>(newOperation: NewOperation): App< | ||||
AppName, | AppName, | ||||
{ | { | ||||
@@ -48,39 +49,28 @@ interface AppParams<Name extends string = string> { | |||||
class AppInstance<Params extends AppParams, State extends BaseAppState> implements App<Params['name'], State> { | class AppInstance<Params extends AppParams, State extends BaseAppState> implements App<Params['name'], State> { | ||||
readonly name: Params['name']; | readonly name: Params['name']; | ||||
readonly endpoints: Set<Endpoint>; | |||||
readonly operations: Set<Operation>; | |||||
readonly endpoints: NamedSet<Endpoint>; | |||||
readonly operations: NamedSet<Operation>; | |||||
constructor(params: Params) { | constructor(params: Params) { | ||||
this.name = params.name; | this.name = params.name; | ||||
this.endpoints = new Set<Endpoint>(); | |||||
this.operations = new Set<Operation>(); | |||||
this.endpoints = new Map<Endpoint['name'], Endpoint>(); | |||||
this.operations = new PredicateMap<Operation['name'], Operation>((newOperation, s) => ( | |||||
s.method === newOperation.method | |||||
)); | |||||
} | } | ||||
operation<NewOperation extends Operation>(newOperation: NewOperation) { | operation<NewOperation extends Operation>(newOperation: NewOperation) { | ||||
const existingOperations = Array.from(this.operations); | |||||
if (!existingOperations.some((s) => ( | |||||
s.name === newOperation.name | |||||
&& s.method === newOperation.method | |||||
))) { | |||||
this.operations.add(newOperation); | |||||
} | |||||
this.operations.set(newOperation.name, newOperation); | |||||
return this; | return this; | ||||
} | } | ||||
endpoint<NewEndpoint extends Endpoint = Endpoint>(newEndpoint: NewEndpoint) { | endpoint<NewEndpoint extends Endpoint = Endpoint>(newEndpoint: NewEndpoint) { | ||||
const existingEndpoints = Array.from(this.endpoints); | |||||
if (existingEndpoints.some((s) => ( | |||||
s.name === newEndpoint.name | |||||
))) { | |||||
if (this.endpoints.has(newEndpoint.name)) { | |||||
throw new Error(`Cannot add duplicate endpoint with name: ${newEndpoint.name}`); | throw new Error(`Cannot add duplicate endpoint with name: ${newEndpoint.name}`); | ||||
} | } | ||||
this.endpoints.add(newEndpoint); | |||||
this.endpoints.set(newEndpoint.name, newEndpoint); | |||||
return this; | return this; | ||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,26 @@ | |||||
type ObjectWithName<Name extends string = string> = { | |||||
name: Name; | |||||
}; | |||||
export type NamedSet<T extends ObjectWithName> = Map<T['name'], T>; | |||||
export class PredicateMap<K, V> extends Map<K, V> { | |||||
static get [Symbol.species]() { | |||||
return Map; | |||||
} | |||||
constructor(private readonly predicate: (newItem: V, existingItem: V) => boolean, arg0?: ConstructorParameters<typeof Map<K, V>>[0]) { | |||||
super(arg0); | |||||
} | |||||
set(key: K, value: V) { | |||||
for (const a of this.values()) { | |||||
if (this.predicate(value, a)) { | |||||
return this; | |||||
} | |||||
} | |||||
super.set(key, value); | |||||
return this; | |||||
} | |||||
} |
@@ -1,5 +1,6 @@ | |||||
import {DataSource} from '../backend/data-source'; | import {DataSource} from '../backend/data-source'; | ||||
import {validation as v} from '.'; | import {validation as v} from '.'; | ||||
import {NamedSet} from './common'; | |||||
export type EndpointQueue = [Endpoint, Record<string, unknown> | undefined][]; | export type EndpointQueue = [Endpoint, Record<string, unknown> | undefined][]; | ||||
@@ -20,16 +21,15 @@ export const serializeEndpointQueue = (endpointQueue: EndpointQueue) => { | |||||
.join('') | .join('') | ||||
}; | }; | ||||
export const parseToEndpointQueue = (urlWithoutBase: string, endpoints: Set<Endpoint>) => { | |||||
export const parseToEndpointQueue = (urlWithoutBase: string, endpoints: NamedSet<Endpoint>) => { | |||||
const [urlWithoutQueryParams] = urlWithoutBase.split('?'); | const [urlWithoutQueryParams] = urlWithoutBase.split('?'); | ||||
const fragments = urlWithoutQueryParams.split('/').filter((s) => s.trim().length > 0); | const fragments = urlWithoutQueryParams.split('/').filter((s) => s.trim().length > 0); | ||||
const endpointsArray = Array.from(endpoints); | |||||
return fragments.reduce( | return fragments.reduce( | ||||
(theEndpointQueueRaw, s) => { | (theEndpointQueueRaw, s) => { | ||||
const theEndpointQueue = theEndpointQueueRaw as EndpointQueue; | const theEndpointQueue = theEndpointQueueRaw as EndpointQueue; | ||||
const [lastEndpoint, lastEndpointParams] = theEndpointQueue.at(-1) ?? []; | const [lastEndpoint, lastEndpointParams] = theEndpointQueue.at(-1) ?? []; | ||||
const endpoint = endpointsArray.find((e) => e.name === s); | |||||
const endpoint = endpoints.get(s); | |||||
if (typeof endpoint !== 'undefined') { | if (typeof endpoint !== 'undefined') { | ||||
if (typeof lastEndpoint === 'undefined') { | if (typeof lastEndpoint === 'undefined') { | ||||
return [ | return [ | ||||
@@ -39,7 +39,7 @@ class ServerInstance<Backend extends BaseBackend> implements Server<Backend> { | |||||
const endpoints = parseToEndpointQueue(req.url, this.backend.app.endpoints); | const endpoints = parseToEndpointQueue(req.url, this.backend.app.endpoints); | ||||
const [endpoint, endpointParams] = endpoints.at(-1) ?? []; | const [endpoint, endpointParams] = endpoints.at(-1) ?? []; | ||||
const appOperations = Array.from(this.backend.app.operations) | |||||
const appOperations = Array.from(this.backend.app.operations.values()) | |||||
const foundAppOperation = appOperations | const foundAppOperation = appOperations | ||||
.find((op) => op.method === req.method?.toUpperCase()); | .find((op) => op.method === req.method?.toUpperCase()); | ||||
@@ -95,7 +95,6 @@ class ServerInstance<Backend extends BaseBackend> implements Server<Backend> { | |||||
} | } | ||||
const bodyToSerialize = responseSpec.body; | const bodyToSerialize = responseSpec.body; | ||||
console.log(bodyToSerialize); | |||||
res.statusMessage = responseSpec.statusMessage; // TODO add default status message per status code | res.statusMessage = responseSpec.statusMessage; // TODO add default status message per status code | ||||
res.writeHead(responseSpec.statusCode, {}); | res.writeHead(responseSpec.statusCode, {}); | ||||
@@ -61,7 +61,7 @@ describe('default', () => { | |||||
app: theRawApp, | app: theRawApp, | ||||
}); | }); | ||||
theRawEndpoint = Array.from(theApp.endpoints).find((e) => e.name === 'users'); | |||||
theRawEndpoint = theApp.endpoints.get('users'); | |||||
theOperation = operations.fetch; | theOperation = operations.fetch; | ||||
theServer = server({ | theServer = server({ | ||||