Skip to content

Commit

Permalink
refactor(exposition): remove express (#525)
Browse files Browse the repository at this point in the history
* refactor(exposition): remove `express`

* refactor(exposition): replace `IncomingMessage` extension with `Context` wrapper

* refactor(exposition): replace `Promex` with `once` for `server.listen`

* refactor(exposition): replace `Promex` with `once` for `server.close`

* style(exposition): fix warnings
  • Loading branch information
temich authored Feb 26, 2024
1 parent d8490ce commit 98b7cdf
Show file tree
Hide file tree
Showing 46 changed files with 440 additions and 1,055 deletions.
6 changes: 3 additions & 3 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ overrides:
- always
- null: always
'@typescript-eslint/no-unsafe-argument': warn
'@typescript-eslint/no-non-null-assertion': warn
'@typescript-eslint/no-non-null-assertion': off
'@typescript-eslint/non-nullable-type-assertion-style': warn
'@typescript-eslint/unbound-method': warn

Expand All @@ -80,8 +80,8 @@ overrides:
- files: ['**/features/**/*.ts']
extends: 'plugin:@typescript-eslint/disable-type-checked'
rules:
'@typescript-eslint/prefer-nullish-coalescing': 'off'
'@typescript-eslint/strict-boolean-expressions': 'off'
'@typescript-eslint/prefer-nullish-coalescing': 'off'
'@typescript-eslint/strict-boolean-expressions': 'off'
parserOptions:
project: true
env:
Expand Down
9 changes: 6 additions & 3 deletions connectors/bindings.amqp/source/deployment/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { resolveRecord, naming } from '@toa.io/pointer'
import { type Locator } from '@toa.io/core'
import { type AnnotationRecord } from '@toa.io/pointer/transpiled/Deployment'
import { type Annotation } from './annotation'
import type { URIMap } from '@toa.io/pointer'

export function createDependency (context: Context): Dependency {
const global: Variable[] = []
Expand All @@ -25,7 +26,7 @@ export async function resolveURIs (locator: Locator): Promise<string[]> {
if (value === undefined)
throw new Error(`Environment variable ${VARIABLE} is not specified`)

const map = decode(value)
const map = decode<URIMap>(value)
const record = resolveRecord(map, locator.id)

return await parseRecord(record)
Expand Down Expand Up @@ -102,8 +103,10 @@ function readEnv (key: string, name: string): string {
const variable = naming.nameVariable(ID, key, name)
const value = process.env[variable]

if (value === undefined) throw new Error(variable + ' is not set')
else return value
if (value === undefined)
throw new Error(variable + ' is not set')
else
return value
}

const ID = 'amqp-context'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

function store (input, context) {
const { storage, request, accept, meta } = input
const path = request.path
const path = request.url
const claim = request.headers['content-type']

return context.storages[storage].put(path, request, { claim, accept, meta })
Expand Down
2 changes: 1 addition & 1 deletion extensions/exposition/features/octets.entries.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Feature: Accessing entires
Feature: Accessing entries

Scenario: Entries are not accessible by default
Given the annotation:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: echo
version: 0.0.0

operations:
compute:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace: octets
name: tester
version: 0.0.0

storages: octets

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: sequences
version: 0.0.0

operations:
numbers:
Expand Down
3 changes: 0 additions & 3 deletions extensions/exposition/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
"@toa.io/core": "1.0.0-alpha.3",
"@toa.io/generic": "1.0.0-alpha.3",
"@toa.io/schemas": "1.0.0-alpha.3",
"@toa.io/streams": "1.0.0-alpha.3",
"bcryptjs": "2.4.3",
"error-value": "0.3.0",
"express": "4.18.2",
"js-yaml": "4.1.0",
"matchacho": "0.3.5",
"msgpackr": "1.10.1",
Expand All @@ -49,7 +47,6 @@
"@toa.io/extensions.storages": "1.0.0-alpha.3",
"@types/bcryptjs": "2.4.3",
"@types/cors": "2.8.13",
"@types/express": "4.17.17",
"@types/negotiator": "0.6.1"
},
"gitHead": "24d68d70a56717f2f4441cc9884a60f9fee0863e"
Expand Down
6 changes: 3 additions & 3 deletions extensions/exposition/source/Directive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import assert from 'node:assert'
import { generate } from 'randomstring'
import { DirectivesFactory, type Family } from './Directive'
import { type syntax } from './RTD'
import { type IncomingMessage } from './HTTP'
import { type Remotes } from './Remotes'
import type { Context } from './HTTP'

const families: Array<jest.MockedObjectDeep<Family>> = [
{
Expand Down Expand Up @@ -76,7 +76,7 @@ it('should apply directive', async () => {
}

const directives = factory.create([declaration])
const request = generate() as unknown as IncomingMessage
const request = generate() as unknown as Context
const directive = families[0].create.mock.results[0].value

await directives.preflight(request, [])
Expand All @@ -89,7 +89,7 @@ it('should apply directive', async () => {

it('should apply mandatory families', async () => {
const directives = factory.create([])
const request = generate() as unknown as IncomingMessage
const request = generate() as unknown as Context

await directives.preflight(request, [])

Expand Down
22 changes: 11 additions & 11 deletions extensions/exposition/source/Directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IncomingMessage, OutgoingMessage } from './HTTP'
import type { Context, OutgoingMessage } from './HTTP'
import type { Remotes } from './Remotes'
import type { Output } from './io'
import type * as RTD from './RTD'
Expand All @@ -10,15 +10,15 @@ export class Directives implements RTD.Directives<Directives> {
this.sets = sets
}

public async preflight (request: IncomingMessage, parameters: RTD.Parameter[]): Promise<Output> {
public async preflight (context: Context, parameters: RTD.Parameter[]): Promise<Output> {
for (const set of this.sets) {
if (set.family.preflight === undefined)
continue

const output = await set.family.preflight(set.directives, request, parameters)
const output = await set.family.preflight(set.directives, context, parameters)

if (output !== null) {
await this.settle(request, output)
await this.settle(context, output)

return output
}
Expand All @@ -27,10 +27,10 @@ export class Directives implements RTD.Directives<Directives> {
return null
}

public async settle (request: IncomingMessage, response: OutgoingMessage): Promise<void> {
public async settle (context: Context, response: OutgoingMessage): Promise<void> {
for (const set of this.sets)
if (set.family.settle !== undefined)
await set.family.settle(set.directives, request, response)
await set.family.settle(set.directives, context, response)
}

public merge (directives: Directives): void {
Expand All @@ -39,7 +39,7 @@ export class Directives implements RTD.Directives<Directives> {
}

export class DirectivesFactory implements RTD.DirectivesFactory<Directives> {
private readonly remtoes: Remotes
private readonly remotes: Remotes
private readonly families: Record<string, Family> = {}
private readonly mandatory: string[] = []

Expand All @@ -51,7 +51,7 @@ export class DirectivesFactory implements RTD.DirectivesFactory<Directives> {
this.mandatory.push(family.name)
}

this.remtoes = remotes
this.remotes = remotes
}

public create (declarations: RTD.syntax.Directive[]): Directives {
Expand All @@ -67,7 +67,7 @@ export class DirectivesFactory implements RTD.DirectivesFactory<Directives> {
if (family === undefined)
throw new Error(`Directive family '${declaration.family}' is not found.`)

const directive = family.create(declaration.name, declaration.value, this.remtoes)
const directive = family.create(declaration.name, declaration.value, this.remotes)

groups[family.name] ??= []
groups[family.name].push(directive)
Expand Down Expand Up @@ -109,11 +109,11 @@ export interface Family<TDirective = any, TExtension = any> {
create: (name: string, value: any, remotes: Remotes) => TDirective

preflight?: (directives: TDirective[],
request: IncomingMessage & TExtension,
request: Context & TExtension,
parameters: RTD.Parameter[]) => Output | Promise<Output>

settle?: (directives: TDirective[],
request: IncomingMessage & TExtension,
request: Context & TExtension,
response: OutgoingMessage) => void | Promise<void>
}

Expand Down
9 changes: 6 additions & 3 deletions extensions/exposition/source/Factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type Directives, DirectivesFactory } from './Directive'
import { Composition } from './Composition'
import * as root from './root'
import { Interception } from './Interception'
import type { Broadcast } from './Gateway'
import type { Connector, Locator, extensions } from '@toa.io/core'

export class Factory implements extensions.Factory {
Expand All @@ -19,15 +20,15 @@ export class Factory implements extensions.Factory {
}

public tenant (locator: Locator, node: syntax.Node): Connector {
const broadcast = this.boot.bindings.broadcast(CHANNEL, locator.id)
const broadcast: Broadcast = this.boot.bindings.broadcast(CHANNEL, locator.id)

return new Tenant(broadcast, locator, node)
}

public service (): Connector | null {
const debug = process.env.TOA_EXPOSITION_DEBUG === '1'
const trace = process.env.TOA_EXPOSITION_TRACE === '1'
const broadcast = this.boot.bindings.broadcast(CHANNEL)
const broadcast: Broadcast = this.boot.bindings.broadcast(CHANNEL)
const server = Server.create({ methods: syntax.verbs, debug, trace })
const remotes = new Remotes(this.boot)
const node = root.resolve()
Expand All @@ -37,10 +38,12 @@ export class Factory implements extensions.Factory {
const tree = new Tree<Endpoint, Directives>(node, methods, directives)

const composition = new Composition(this.boot)
const gateway = new Gateway(broadcast, server, tree, interception)
const gateway = new Gateway(broadcast, tree, interception)

gateway.depends(remotes)
gateway.depends(composition)

server.attach(gateway.process.bind(gateway))
server.depends(gateway)

return server
Expand Down
62 changes: 29 additions & 33 deletions extensions/exposition/source/Gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,79 +13,75 @@ export class Gateway extends Connector {
private readonly broadcast: Broadcast
private readonly tree: Tree<Endpoint, Directives>
private readonly interceptor: Interception
private readonly server: Connector

// eslint-disable-next-line max-params, max-len
public constructor (broadcast: Broadcast, server: http.Server, tree: Tree<Endpoint, Directives>, interception: Interception) {
// eslint-disable-next-line max-len
public constructor (broadcast: Broadcast, tree: Tree<Endpoint, Directives>, interception: Interception) {
super()

this.broadcast = broadcast
this.tree = tree
this.interceptor = interception
this.server = server

this.depends(broadcast)
// this.depends(server)

server.attach(this.process.bind(this))
}

protected override async open (): Promise<void> {
await this.discover()

console.info('Gateway has started and is awaiting resource branches.')
}

protected override dispose (): void {
console.info('Gateway is closed.')
}

private async process (request: http.IncomingMessage): Promise<http.OutgoingMessage> {
const interception = await request.timing.capture('gate:intercept',
this.interceptor.intercept(request))
public async process (context: http.Context): Promise<http.OutgoingMessage> {
const interception = await context.timing.capture('gate:intercept',
this.interceptor.intercept(context))

if (interception !== null)
return interception

const match = this.tree.match(request.path)
const match = this.tree.match(context.url.pathname)

if (match === null)
throw new http.NotFound()

const { node, parameters } = match

if (!(request.method in node.methods))
if (!(context.request.method in node.methods))
throw new http.MethodNotAllowed()

const method = node.methods[request.method]
const method = node.methods[context.request.method]

const interruption = await request.timing.capture('gate:preflight',
method.directives.preflight(request, parameters))
const interruption = await context.timing.capture('gate:preflight',
method.directives.preflight(context, parameters))

const response = interruption ??
await request.timing.capture('gate:call', this.call(method, request, parameters))
await context.timing.capture('gate:call', this.call(method, context, parameters))

await request.timing.capture('gate:settle', method.directives.settle(request, response))
await context.timing.capture('gate:settle', method.directives.settle(context, response))

return response
}

protected override async open (): Promise<void> {
await this.discover()

console.info('Gateway has started and is awaiting resource branches.')
}

protected override dispose (): void {
console.info('Gateway is closed.')
}

private async call
(method: Method<Endpoint, Directives>, request: http.IncomingMessage, parameters: Parameter[]):
(method: Method<Endpoint, Directives>, context: http.Context, parameters: Parameter[]):
Promise<http.OutgoingMessage> {
if (request.path[request.path.length - 1] !== '/')
if (context.url.pathname[context.url.pathname.length - 1] !== '/')
throw new http.NotFound('Trailing slash is required.')

if (request.encoder === null)
if (context.encoder === null)
throw new http.NotAcceptable()

if (method.endpoint === null)
throw new http.MethodNotAllowed()

const body = await request.parse()
const body = await context.body()
const query = Object.fromEntries(context.url.searchParams)

const reply = await method.endpoint
.call(body, request.query, parameters)
.call(body, query, parameters)
.catch(rethrow) as Maybe<unknown>

if (reply instanceof Error)
Expand All @@ -111,4 +107,4 @@ export class Gateway extends Connector {
}
}

type Broadcast = bindings.Broadcast<Label>
export type Broadcast = bindings.Broadcast<Label>
Loading

0 comments on commit 98b7cdf

Please sign in to comment.