mirror of
https://github.com/zoriya/elysia.git
synced 2026-08-15 18:44:25 +00:00
🎉 feat: 0.25
This commit is contained in:
+5
-1
@@ -1,10 +1,14 @@
|
||||
# 0.0.0-experimental.25 - 28 Aug 2022
|
||||
New Feature:
|
||||
- `onRequest` now accept async function
|
||||
- Auto infer typed for `params`, `state`, `ref`
|
||||
- `onRequest` now accept async function
|
||||
- `refFn` syntax sugar for adding fn as reference instead of `() => () => value`
|
||||
|
||||
Improvement:
|
||||
- Switch from `@saltyaom/trek-router` to `@medley/router`
|
||||
- Using `clone` instead of flatten object
|
||||
- Refactor path fn for inline cache
|
||||
- Refactor `Context` to class
|
||||
|
||||
Bug fix:
|
||||
- `.ref()` throw error when accept function
|
||||
|
||||
+3
-2
@@ -3,8 +3,9 @@ import KingWorld from '../src'
|
||||
import cookie from '../src/index'
|
||||
|
||||
const app = new KingWorld()
|
||||
.get('/', ({ responseHeaders }) => {
|
||||
// responseHeaders.append('a', 'b')
|
||||
.get('/', ({ status, responseHeaders }) => {
|
||||
responseHeaders.append('a', 'b')
|
||||
status(400)
|
||||
|
||||
return new Response('Ok')
|
||||
})
|
||||
|
||||
+22
-14
@@ -9,8 +9,6 @@ const loggerPlugin: Plugin<
|
||||
{
|
||||
store: {
|
||||
fromPlugin: 'From Logger'
|
||||
}
|
||||
request: {
|
||||
log: () => void
|
||||
}
|
||||
}
|
||||
@@ -18,12 +16,12 @@ const loggerPlugin: Plugin<
|
||||
app
|
||||
.get('/hi', () => 'Hi')
|
||||
.state('fromPlugin', 'From Logger')
|
||||
.transform((request) => {
|
||||
request.log = () => {
|
||||
.transform(({ responseHeaders }, store) => {
|
||||
store.log = () => {
|
||||
console.log('From Logger')
|
||||
}
|
||||
|
||||
request.responseHeaders.append('X-POWERED-BY', 'KINGWORLD')
|
||||
responseHeaders.append('X-POWERED-BY', 'KINGWORLD')
|
||||
})
|
||||
.group(prefix, (app) => {
|
||||
app.get('/plugin', () => 'From Plugin')
|
||||
@@ -35,21 +33,27 @@ const app = new KingWorld<{
|
||||
date: number
|
||||
}
|
||||
}>()
|
||||
.get('/', () => 'KINGWORLD')
|
||||
.use(loggerPlugin)
|
||||
.get('/tako', () => Bun.file('./example/takodachi.png'))
|
||||
.state('build', Date.now())
|
||||
.ref('date', () => Date.now())
|
||||
.refFn('a', (a: string) => a)
|
||||
.get('/', () => 'KINGWORLD')
|
||||
.get('/tako', () => Bun.file('./example/takodachi.png'))
|
||||
.get('/json', () => ({
|
||||
hi: 'world'
|
||||
}))
|
||||
.get('/root/plugin/log', (_, { log, a }) => {
|
||||
log()
|
||||
|
||||
return a('B')
|
||||
})
|
||||
.get('/wildcard/*', () => 'Hi Wildcard')
|
||||
.get<{
|
||||
query: {
|
||||
name: string
|
||||
}
|
||||
}>('/kw', (ctx, store) => 'KINGWORLD', {
|
||||
preHandler: ({ query, log }, { fromPlugin }) => {
|
||||
preHandler: ({ query }, { fromPlugin, log }) => {
|
||||
console.log('Name:', query?.name)
|
||||
|
||||
if (query?.name === 'aom') return 'Hi saltyaom'
|
||||
@@ -79,8 +83,8 @@ const app = new KingWorld<{
|
||||
id: number
|
||||
}
|
||||
}>('/id/:id', (request, store) => request.params.id, {
|
||||
transform(request, store) {
|
||||
request.params.id = +request.params.id
|
||||
transform({ params }, store) {
|
||||
params.id = +params.id
|
||||
}
|
||||
})
|
||||
.post<{
|
||||
@@ -90,7 +94,7 @@ const app = new KingWorld<{
|
||||
params: {
|
||||
id: number
|
||||
}
|
||||
}>('/new/:id', async ({ request }) => request.json())
|
||||
}>('/new/:id', async ({ body }) => body)
|
||||
.group('/group', (app) => {
|
||||
app.preHandler<{
|
||||
query: {
|
||||
@@ -103,11 +107,15 @@ const app = new KingWorld<{
|
||||
.get('/kingworld', () => 'Welcome to KINGWORLD')
|
||||
.get('/fbk', () => 'FuBuKing')
|
||||
})
|
||||
.get('/response-header', (ctx) => {
|
||||
ctx.responseHeaders.append("a", "b")
|
||||
.get('/response-header', ({ status, responseHeaders }) => {
|
||||
status(404)
|
||||
responseHeaders.append('a', 'b')
|
||||
|
||||
return "A"
|
||||
// console.log(r.status)
|
||||
|
||||
return 'A'
|
||||
})
|
||||
.get('/this/is/my/deep/nested/root', () => 'Hi')
|
||||
.get('/build', (_, { build }) => build)
|
||||
.get('/ref', (_, { date }) => date)
|
||||
.get('/response', () => new Response('Hi'))
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
const handle = async (request: Request) => {
|
||||
const bodySize = request.headers.get('content-length')
|
||||
|
||||
// ? For some reason, this prevent segmentation fault (Bun 0.1.10)
|
||||
const body = !bodySize
|
||||
? undefined
|
||||
: request.headers.get('content-type') === 'application/json'
|
||||
? await request.json()
|
||||
: await request.text()
|
||||
|
||||
const a = {
|
||||
body
|
||||
}
|
||||
|
||||
return new Response('done')
|
||||
}
|
||||
|
||||
const body = JSON.stringify({
|
||||
tako: 'dachi'
|
||||
})
|
||||
|
||||
handle(
|
||||
new Request('http://localhost:3000/json', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'content-length': body.length.toString()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
for (let i = 0; i < 10_000_000; i++)
|
||||
handle(
|
||||
new Request('http://localhost:3000/json', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'content-length': body.length.toString()
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -34,6 +34,7 @@
|
||||
"release": "npm run build && npm run test && npm publish"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medley/router": "^0.2.1",
|
||||
"@saltyaom/trek-router": "0.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { TypedRoute } from './types'
|
||||
|
||||
export default class Context<Route extends TypedRoute = TypedRoute> {
|
||||
_status = 200
|
||||
_headers: Headers | undefined
|
||||
|
||||
request: Request
|
||||
query: Route['query'] extends Record<string, any>
|
||||
? Route['query']
|
||||
: Record<string, string>
|
||||
params: Route['params']
|
||||
body: Route['body']
|
||||
|
||||
constructor(a: {
|
||||
request: Request
|
||||
query: Route['query'] extends Record<string, any>
|
||||
? Route['query']
|
||||
: Record<string, string>
|
||||
params: Route['params']
|
||||
body: Route['body']
|
||||
}) {
|
||||
this.request = a.request
|
||||
this.params = a.params
|
||||
this.query = a.query
|
||||
this.body = a.body
|
||||
}
|
||||
|
||||
status = (code: number) => {
|
||||
this._status = code
|
||||
}
|
||||
|
||||
get responseHeaders(): Headers {
|
||||
if (!this._headers) this._headers = new Headers()
|
||||
|
||||
return this._headers
|
||||
}
|
||||
}
|
||||
+34
-45
@@ -1,4 +1,4 @@
|
||||
import type { Context } from './types'
|
||||
import type Context from './context'
|
||||
|
||||
const json = new Headers()
|
||||
json.append('Content-Type', 'application/json')
|
||||
@@ -7,28 +7,21 @@ export const jsonHeader = {
|
||||
}
|
||||
|
||||
// We don't want to assign new variable to be used only once here
|
||||
export const mapEarlyResponse = (
|
||||
headerAvailable: boolean,
|
||||
response: unknown,
|
||||
context: Context,
|
||||
status: number
|
||||
) => {
|
||||
if (headerAvailable)
|
||||
export const mapEarlyResponse = (response: unknown, context: Context) => {
|
||||
if (context._headers || context._status !== 200)
|
||||
switch (typeof response) {
|
||||
case 'string':
|
||||
return new Response(response, {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
case 'object':
|
||||
if (response instanceof Error)
|
||||
return errorToResponse(response, context.responseHeaders)
|
||||
return errorToResponse(response, context._headers)
|
||||
if (response instanceof Response) {
|
||||
for (const [
|
||||
key,
|
||||
value
|
||||
] of context.responseHeaders.entries())
|
||||
for (const [key, value] of context._headers?.entries() ??
|
||||
[])
|
||||
response.headers.append(key, value)
|
||||
|
||||
return response
|
||||
@@ -40,19 +33,19 @@ export const mapEarlyResponse = (
|
||||
)
|
||||
|
||||
return new Response(JSON.stringify(response), {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
// ? Maybe response or Blob
|
||||
case 'function':
|
||||
if (response instanceof Blob)
|
||||
return new Response(response, {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
for (const [key, value] of context.responseHeaders.entries())
|
||||
for (const [key, value] of context._headers?.entries() ?? [])
|
||||
(response as unknown as Response).headers.append(key, value)
|
||||
|
||||
return response as unknown as Response
|
||||
@@ -60,8 +53,8 @@ export const mapEarlyResponse = (
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
return new Response(response.toString(), {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
default:
|
||||
@@ -75,7 +68,7 @@ export const mapEarlyResponse = (
|
||||
case 'object':
|
||||
if (response instanceof Response) return response
|
||||
if (response instanceof Error)
|
||||
return errorToResponse(response, context.responseHeaders)
|
||||
return errorToResponse(response, context._headers)
|
||||
|
||||
return new Response(JSON.stringify(response), jsonHeader)
|
||||
|
||||
@@ -94,25 +87,21 @@ export const mapEarlyResponse = (
|
||||
}
|
||||
}
|
||||
|
||||
export const mapResponse = (
|
||||
headerAvailable: boolean,
|
||||
response: unknown,
|
||||
context: Context,
|
||||
status: number
|
||||
) => {
|
||||
if (headerAvailable)
|
||||
export const mapResponse = (response: unknown, context: Context) => {
|
||||
if (context._headers || context._status !== 200)
|
||||
switch (typeof response) {
|
||||
case 'string':
|
||||
return new Response(response, {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
case 'object':
|
||||
if (response instanceof Error)
|
||||
return errorToResponse(response, context.responseHeaders)
|
||||
return errorToResponse(response, context._headers)
|
||||
if (response instanceof Response) {
|
||||
for (const [key, value] of context.responseHeaders!.entries())
|
||||
for (const [key, value] of context._headers?.entries() ??
|
||||
[])
|
||||
response.headers.append(key, value)
|
||||
|
||||
return response
|
||||
@@ -124,16 +113,16 @@ export const mapResponse = (
|
||||
)
|
||||
|
||||
return new Response(JSON.stringify(response), {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
// ? Maybe response function or Blob
|
||||
case 'function':
|
||||
if (response instanceof Blob)
|
||||
return new Response(response, {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
return response()
|
||||
@@ -141,20 +130,20 @@ export const mapResponse = (
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
return new Response(response.toString(), {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
case 'undefined':
|
||||
return new Response('', {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
|
||||
default:
|
||||
return new Response(response as any, {
|
||||
status,
|
||||
headers: context.responseHeaders
|
||||
status: context._status,
|
||||
headers: context._headers
|
||||
})
|
||||
}
|
||||
else
|
||||
@@ -165,7 +154,7 @@ export const mapResponse = (
|
||||
case 'object':
|
||||
if (response instanceof Response) return response
|
||||
if (response instanceof Error)
|
||||
return errorToResponse(response, context.responseHeaders)
|
||||
return errorToResponse(response, context._headers)
|
||||
|
||||
return new Response(JSON.stringify(response), jsonHeader)
|
||||
|
||||
|
||||
+86
-83
@@ -1,7 +1,17 @@
|
||||
import { default as Router, type HTTPMethod } from '@saltyaom/trek-router'
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import MedleyRouter from '@medley/router'
|
||||
|
||||
import Context from './context'
|
||||
import { mapResponse, mapEarlyResponse } from './handler'
|
||||
import { mergeHook, isPromise, clone, mapArrayObject } from './utils'
|
||||
import {
|
||||
mergeHook,
|
||||
isPromise,
|
||||
clone,
|
||||
parseQuery,
|
||||
getQuery,
|
||||
getPath
|
||||
} from './utils'
|
||||
|
||||
import type {
|
||||
Handler,
|
||||
@@ -11,28 +21,40 @@ import type {
|
||||
PreRequestHandler,
|
||||
TypedRoute,
|
||||
Plugin,
|
||||
Context,
|
||||
KingWorldInstance,
|
||||
ComposedHandler,
|
||||
KingWorldConfig,
|
||||
KWKey,
|
||||
ExtractKWPath
|
||||
ExtractKWPath,
|
||||
HTTPMethod
|
||||
} from './types'
|
||||
import type { Serve } from 'bun'
|
||||
|
||||
export default class KingWorld<
|
||||
Instance extends KingWorldInstance = KingWorldInstance
|
||||
> {
|
||||
router: Router<ComposedHandler>
|
||||
store: Instance['store']
|
||||
hook: Hook<Instance>
|
||||
store: Instance['store'] = {}
|
||||
hook: Hook<Instance> = {
|
||||
onRequest: [],
|
||||
transform: [],
|
||||
preHandler: []
|
||||
}
|
||||
|
||||
config: KingWorldConfig = {
|
||||
bodyLimit: 1048576
|
||||
}
|
||||
|
||||
private _ref: [keyof Instance['store'], any][]
|
||||
private _default: Handler
|
||||
private router = new MedleyRouter()
|
||||
protected routes: [
|
||||
HTTPMethod,
|
||||
string,
|
||||
Handler<any, Instance>,
|
||||
RegisterHook<any, Instance>
|
||||
][] = []
|
||||
private _ref: [keyof Instance['store'], any][] = []
|
||||
private _default: Handler = () =>
|
||||
new Response('Not Found', {
|
||||
status: 404
|
||||
})
|
||||
|
||||
constructor(
|
||||
config: KingWorldConfig = {
|
||||
@@ -44,22 +66,7 @@ export default class KingWorld<
|
||||
bodyLimit: 1048576
|
||||
}
|
||||
) {
|
||||
this.router = new Router()
|
||||
|
||||
this.store = {} as Instance['store']
|
||||
this._ref = []
|
||||
this.hook = {
|
||||
onRequest: [],
|
||||
transform: [],
|
||||
preHandler: []
|
||||
}
|
||||
|
||||
this.config = config
|
||||
|
||||
this._default = () =>
|
||||
new Response('Not Found', {
|
||||
status: 404
|
||||
})
|
||||
}
|
||||
|
||||
private _addHandler<Route extends TypedRoute = TypedRoute>(
|
||||
@@ -68,10 +75,17 @@ export default class KingWorld<
|
||||
handler: Handler<Route, Instance>,
|
||||
hook?: RegisterHook<Route, Instance>
|
||||
) {
|
||||
this.router.add(method, path, [
|
||||
this.routes.push([
|
||||
method,
|
||||
path,
|
||||
handler,
|
||||
mergeHook(clone(this.hook) as Hook, hook as RegisterHook)
|
||||
])
|
||||
|
||||
this.router.register(path)[method] = {
|
||||
handle: handler,
|
||||
hooks: mergeHook(clone(this.hook) as Hook, hook as RegisterHook)
|
||||
}
|
||||
}
|
||||
|
||||
onRequest(handler: PreRequestHandler<Instance['store']>) {
|
||||
@@ -125,9 +139,9 @@ export default class KingWorld<
|
||||
|
||||
this.store = { ...this.store, ...instance.store }
|
||||
|
||||
Object.values(instance.router.routes).forEach(
|
||||
([method, path, handler]) => {
|
||||
this.router.add(method, `${prefix}${path}`, handler)
|
||||
Object.values(instance.routes).forEach(
|
||||
([method, path, handler, hook]) => {
|
||||
this._addHandler(method, `${prefix}${path}`, handler, hook)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -143,11 +157,9 @@ export default class KingWorld<
|
||||
|
||||
this.store = { ...this.store, ...instance.store }
|
||||
|
||||
Object.values(instance.router.routes).forEach(
|
||||
([method, path, handler]) => {
|
||||
this.router.add(method, path, handler)
|
||||
}
|
||||
)
|
||||
Object.values(instance.routes).forEach(([method, path, handler]) => {
|
||||
this._addHandler(method, path, handler)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
@@ -375,13 +387,31 @@ export default class KingWorld<
|
||||
return this as unknown as NewInstance
|
||||
}
|
||||
|
||||
// ? Need to be arrow function otherwise, `this` won't work for some reason
|
||||
async handle(request: Request): Promise<Response> {
|
||||
refFn<
|
||||
Key extends KWKey = keyof Instance['store'],
|
||||
Value = Instance['store'][keyof Instance['store']],
|
||||
ReturnValue = Value extends () => infer Returned
|
||||
? Returned extends Promise<infer AsyncReturned>
|
||||
? AsyncReturned
|
||||
: Returned
|
||||
: Value,
|
||||
NewInstance extends KingWorldInstance = KingWorld<
|
||||
KingWorldInstance<{
|
||||
store: Instance['store'] & { [key in Key]: ReturnValue }
|
||||
request: Instance['request'] extends Record<string, any>
|
||||
? Instance['request']
|
||||
: Record<string, any>
|
||||
}>
|
||||
>
|
||||
>(name: Key, value: Value): NewInstance {
|
||||
this._ref.push([name, () => value])
|
||||
|
||||
return this as unknown as NewInstance
|
||||
}
|
||||
|
||||
// ? Need to be arrow function otherwise `this` won't work for some reason
|
||||
handle = async (request: Request): Promise<Response> => {
|
||||
const store: Partial<Instance['store']> = clone(this.store)
|
||||
const [handler, _params, query] = this.router.find(
|
||||
request.method as HTTPMethod,
|
||||
request.url
|
||||
)
|
||||
|
||||
if (this._ref.length)
|
||||
for (const [key, value] of this._ref)
|
||||
@@ -401,56 +431,37 @@ export default class KingWorld<
|
||||
if (bodySize && +bodySize > this.config.bodyLimit)
|
||||
return new Response('Exceed body limit')
|
||||
|
||||
let headerAvailable = false
|
||||
let responseHeaders: Headers | undefined
|
||||
let status = 200
|
||||
|
||||
const body = !bodySize
|
||||
const body: string | Object | undefined = !bodySize
|
||||
? undefined
|
||||
: request.headers.get('content-type') === 'application/json'
|
||||
? await request.json()
|
||||
: await request.text()
|
||||
|
||||
// ? Might have additional field attach from plugin, so forced type cast here
|
||||
const context: Context = {
|
||||
request,
|
||||
params: _params.length ? mapArrayObject(_params) : {},
|
||||
query,
|
||||
status(value: number) {
|
||||
status = value
|
||||
headerAvailable = true
|
||||
},
|
||||
body,
|
||||
get responseHeaders() {
|
||||
if (!responseHeaders) {
|
||||
responseHeaders = new Headers()
|
||||
headerAvailable = true
|
||||
}
|
||||
const route = this.router.find(getPath(request.url))
|
||||
const handler = route?.store[request.method]
|
||||
|
||||
return responseHeaders
|
||||
}
|
||||
} as Context
|
||||
const context: Context = new Context({
|
||||
request,
|
||||
params: route?.params ?? {},
|
||||
query: parseQuery(getQuery(request.url)),
|
||||
body
|
||||
})
|
||||
|
||||
if (!handler) {
|
||||
let response = this._default(context, store)
|
||||
if (isPromise(response)) response = await response
|
||||
|
||||
return mapResponse(headerAvailable, response, context, status)
|
||||
return mapResponse(response, context)
|
||||
}
|
||||
|
||||
const [handle, hooks] = handler
|
||||
const { handle, hooks } = handler
|
||||
|
||||
if (hooks.transform.length)
|
||||
for (const transform of hooks.transform) {
|
||||
let response = transform(context, store)
|
||||
if (isPromise(response)) response = await response
|
||||
|
||||
const result = mapEarlyResponse(
|
||||
headerAvailable,
|
||||
response,
|
||||
context,
|
||||
status
|
||||
)
|
||||
const result = mapEarlyResponse(response, context)
|
||||
if (result) return result
|
||||
}
|
||||
|
||||
@@ -459,35 +470,28 @@ export default class KingWorld<
|
||||
let response = preHandler(context, store)
|
||||
if (isPromise(response)) response = await response
|
||||
|
||||
const result = mapEarlyResponse(
|
||||
headerAvailable,
|
||||
response,
|
||||
context,
|
||||
status
|
||||
)
|
||||
const result = mapEarlyResponse(response, context)
|
||||
if (result) return result
|
||||
}
|
||||
|
||||
let response = handle(context, store)
|
||||
if (isPromise(response)) response = await response
|
||||
|
||||
return mapResponse(headerAvailable, response, context, status)
|
||||
return mapResponse(response, context)
|
||||
}
|
||||
|
||||
listen(options: number | Omit<Serve, 'fetch'>) {
|
||||
if (!Bun) throw new Error('Bun to run')
|
||||
|
||||
const fetch = this.handle.bind(this)
|
||||
|
||||
Bun.serve(
|
||||
typeof options === 'number'
|
||||
? {
|
||||
port: options,
|
||||
fetch
|
||||
fetch: this.handle
|
||||
}
|
||||
: {
|
||||
...options,
|
||||
fetch
|
||||
fetch: this.handle
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -498,7 +502,6 @@ export type {
|
||||
Hook,
|
||||
HookEvent,
|
||||
RegisterHook,
|
||||
Context,
|
||||
PreRequestHandler,
|
||||
TypedRoute,
|
||||
Plugin
|
||||
|
||||
+47
-16
@@ -1,4 +1,5 @@
|
||||
import type KingWorld from './index'
|
||||
import type Context from './context'
|
||||
|
||||
export type KWKey = string | number | symbol
|
||||
|
||||
@@ -15,17 +16,6 @@ export interface KingWorldInstance<
|
||||
store: Instance['store']
|
||||
}
|
||||
|
||||
export type Context<Route extends TypedRoute = TypedRoute> = {
|
||||
request: Request
|
||||
query: Route['query'] extends Record<string, any>
|
||||
? Route['query']
|
||||
: Record<string, string>
|
||||
params: Route['params']
|
||||
body: Route['body']
|
||||
status(statusCode: number): void
|
||||
responseHeaders: Headers
|
||||
} & Omit<Route, 'body' | 'query' | 'header' | 'body'>
|
||||
|
||||
export type Handler<
|
||||
Route extends TypedRoute = TypedRoute,
|
||||
Instance extends KingWorldInstance = KingWorldInstance
|
||||
@@ -66,11 +56,14 @@ export interface TypedRoute {
|
||||
export type Plugin<
|
||||
T = Record<string, unknown>,
|
||||
PluginInstance extends KingWorldInstance = KingWorldInstance,
|
||||
BaseInstance extends KingWorldInstance = KingWorldInstance
|
||||
BaseInstance extends KingWorldInstance = KingWorldInstance,
|
||||
ReturnedInstance extends KingWorld<
|
||||
BaseInstance & PluginInstance
|
||||
> = KingWorld<BaseInstance & PluginInstance>
|
||||
> = (
|
||||
app: KingWorld<BaseInstance & PluginInstance>,
|
||||
config?: T
|
||||
) => KingWorld<BaseInstance & PluginInstance>
|
||||
) => ReturnedInstance
|
||||
|
||||
export type ComposedHandler = [handle: Handler<any, any>, hooks: Hook<any>]
|
||||
|
||||
@@ -78,7 +71,45 @@ export interface KingWorldConfig {
|
||||
bodyLimit: number
|
||||
}
|
||||
|
||||
export type IsKWPathParameter<Part> = Part extends `:${infer Parameter}` ? Parameter : never;
|
||||
export type IsKWPathParameter<Part> = Part extends `:${infer Parameter}`
|
||||
? Parameter
|
||||
: never
|
||||
export type ExtractKWPath<Path> = Path extends `${infer A}/${infer B}`
|
||||
? IsKWPathParameter<A> | ExtractKWPath<B>
|
||||
: IsKWPathParameter<Path>;
|
||||
? IsKWPathParameter<A> | ExtractKWPath<B>
|
||||
: IsKWPathParameter<Path>
|
||||
|
||||
export type HTTPMethod =
|
||||
| 'ACL'
|
||||
| 'BIND'
|
||||
| 'CHECKOUT'
|
||||
| 'CONNECT'
|
||||
| 'COPY'
|
||||
| 'DELETE'
|
||||
| 'GET'
|
||||
| 'HEAD'
|
||||
| 'LINK'
|
||||
| 'LOCK'
|
||||
| 'M-SEARCH'
|
||||
| 'MERGE'
|
||||
| 'MKACTIVITY'
|
||||
| 'MKCALENDAR'
|
||||
| 'MKCOL'
|
||||
| 'MOVE'
|
||||
| 'NOTIFY'
|
||||
| 'OPTIONS'
|
||||
| 'PATCH'
|
||||
| 'POST'
|
||||
| 'PROPFIND'
|
||||
| 'PROPPATCH'
|
||||
| 'PURGE'
|
||||
| 'PUT'
|
||||
| 'REBIND'
|
||||
| 'REPORT'
|
||||
| 'SEARCH'
|
||||
| 'SOURCE'
|
||||
| 'SUBSCRIBE'
|
||||
| 'TRACE'
|
||||
| 'UNBIND'
|
||||
| 'UNLINK'
|
||||
| 'UNLOCK'
|
||||
| 'UNSUBSCRIBE'
|
||||
|
||||
+31
-6
@@ -18,11 +18,13 @@ export const isPromise = <T>(
|
||||
export const clone = <T extends Object | any[] = Object | any[]>(
|
||||
aObject: T
|
||||
): T => {
|
||||
const bObject: Record<string, any> = Array.isArray(aObject) ? [] : {}
|
||||
const bObject: Record<string, any> = Array.isArray(aObject)
|
||||
? []
|
||||
: Object.create(null)
|
||||
|
||||
let value: Partial<T>
|
||||
for (const key in aObject) {
|
||||
value = aObject[key]
|
||||
value = aObject[key] as any
|
||||
|
||||
bObject[key as any] =
|
||||
typeof value === 'object' ? clone(value as T) : value
|
||||
@@ -31,10 +33,33 @@ export const clone = <T extends Object | any[] = Object | any[]>(
|
||||
return bObject as T
|
||||
}
|
||||
|
||||
export const mapArrayObject = (value: [string, string][]) => {
|
||||
const object: Record<string, string> = {}
|
||||
// export const splitOnce = (char: string, s: string) => {
|
||||
// const i = s.indexOf(char)
|
||||
|
||||
value.forEach(([key, value]) => (object[key] = value))
|
||||
// return i === -1 ? [s, ''] : [s.slice(0, i), s.slice(i + 1)]
|
||||
// }
|
||||
|
||||
return object
|
||||
export const getPath = (url: string): string => {
|
||||
const queryIndex = url.indexOf('?')
|
||||
|
||||
return url.substring(
|
||||
url.charCodeAt(0) === 47 ? 0 : url.indexOf('/', 11),
|
||||
queryIndex === -1 ? url.length : queryIndex
|
||||
)
|
||||
}
|
||||
|
||||
export const getQuery = (url: string): string => {
|
||||
const queryIndex = url.indexOf('?') + 1
|
||||
|
||||
return queryIndex ? url.substring(queryIndex) : ''
|
||||
}
|
||||
|
||||
export const parseQuery = (search: string) =>
|
||||
!search
|
||||
? {}
|
||||
: search.split('&').reduce((result, each) => {
|
||||
const i = each.indexOf('=')
|
||||
result[each.slice(0, i)] = each.slice(i + 1)
|
||||
|
||||
return result
|
||||
}, {} as Record<string, string>)
|
||||
|
||||
+16
-9
@@ -13,11 +13,10 @@ describe('Path', () => {
|
||||
})
|
||||
|
||||
it('Handle multiple level', async () => {
|
||||
const app = new KingWorld().get<{
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}>('/this/is/my/deep/nested/root', () => 'Ok')
|
||||
const app = new KingWorld().get(
|
||||
'/this/is/my/deep/nested/root',
|
||||
() => 'Ok'
|
||||
)
|
||||
const res = await app.handle(req('/this/is/my/deep/nested/root'))
|
||||
|
||||
expect(await res.text()).toBe('Ok')
|
||||
@@ -140,11 +139,18 @@ describe('Path', () => {
|
||||
it('Handle body', async () => {
|
||||
const app = new KingWorld().post<{
|
||||
body: string
|
||||
}>('/', ({ request }) => request.text())
|
||||
}>('/', ({ body }) => body)
|
||||
|
||||
const body = 'Botan'
|
||||
|
||||
const res = await app.handle(
|
||||
new Request('/', {
|
||||
method: 'POST',
|
||||
body: 'Botan'
|
||||
body,
|
||||
headers: {
|
||||
'content-type': 'text/plain',
|
||||
'content-length': body.length.toString()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
@@ -160,13 +166,14 @@ describe('Path', () => {
|
||||
body: {
|
||||
name: string
|
||||
}
|
||||
}>('/', ({ request }) => request.json())
|
||||
}>('/', ({ body }) => body)
|
||||
const res = await app.handle(
|
||||
new Request('/', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
'content-type': 'application/json',
|
||||
'content-length': body.length.toString()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user