diff --git a/CHANGELOG.md b/CHANGELOG.md index 0381e232..9e210444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ -<<<<<<< HEAD # 0.8.0 Feature: - `header` initialization function @@ -27,7 +26,7 @@ Change: Bug fix: - remove `headers`, `path` from `PreContext` - remove `derive` from `PreContext` -======= + # 0.7.31 - 9 Dec 2023 Improvement: - [#345](https://github.com/elysiajs/elysia/pull/345) add font to `SchemaOptions` @@ -38,7 +37,6 @@ Bug fix: - [#330](https://github.com/elysiajs/elysia/pull/330) preserve query params for mounted handler - [#332](https://github.com/elysiajs/elysia/pull/332) reexport TSchema from typebox - [#319](https://github.com/elysiajs/elysia/pull/319) TypeBox Ref error when using Elysia.group() ->>>>>>> main # 0.7.30 - 5 Dec 2023 Bug fix: diff --git a/example/a.ts b/example/a.ts index 01a8246c..36bb8bd7 100644 --- a/example/a.ts +++ b/example/a.ts @@ -2,21 +2,43 @@ import { Elysia, t } from '../src' const a = new Elysia({ name: 'a', seed: 'awdawd' }).extends( ({ onBeforeHandle }) => ({ - a(fn: () => void) { - onBeforeHandle(fn) + a(fn: string) { + onBeforeHandle(() => { + console.log(fn) + }) } }) ) const b = new Elysia({ name: 'b', seed: 'add' }).use(a).decorate('b', 'b') +// const app = new Elysia() +// .use(a) +// .use(b) +// .get('/', () => 'Hello World', { +// a: 'a' +// }) +// .listen(3000) + +const orders = [] + const app = new Elysia() - .use(a) - .use(b) + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle(fn) + } + })) + .onBeforeHandle(() => { + orders.push(1) + }) .get('/', () => 'Hello World', { - a() { - console.log('a') + beforeHandle() { + orders.push(2) + }, + hi: () => { + orders.push(3) } }) - .listen(3000) -console.dir({ main: app.dependencies }, { depth: 10 }) +console.log(app.routes[0]) +app.handle(new Request('http://localhost/')) +// console.dir({ main: app.dependencies }, { depth: 10 }) diff --git a/src/index.ts b/src/index.ts index 47b7fd24..91c0d872 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,8 @@ import { filterGlobalHook, asGlobal, traceBackExtension, - replaceUrlPath + replaceUrlPath, + primitiveHooks } from './utils' import { @@ -340,6 +341,8 @@ export default class Elysia< ) => { if (typeof type === 'function' || Array.isArray(type)) { if (!localHook[stackName]) localHook[stackName] = [] + if (typeof localHook[stackName] === 'function') + localHook[stackName] = [localHook[stackName]] if (Array.isArray(type)) localHook[stackName] = ( @@ -350,7 +353,7 @@ export default class Elysia< return } - const { insert = 'before', stack = 'local' } = type + const { insert = 'after', stack = 'local' } = type if (stack === 'global') { if (!Array.isArray(fn)) { @@ -376,6 +379,8 @@ export default class Elysia< return } else { if (!localHook[stackName]) localHook[stackName] = [] + if (typeof localHook[stackName] === 'function') + localHook[stackName] = [localHook[stackName]] if (!Array.isArray(fn)) { if (insert === 'before') { @@ -411,14 +416,31 @@ export default class Elysia< onError: createManager('error') } - for (const extension of this.extensions) - traceBackExtension( - extension(manager), - localHook as any, - checksum( - this.config.name + JSON.stringify(this.config.seed) - ) + for (const extension of this.extensions) { + const customHookValues: Record = {} + for (const [key, value] of Object.entries(localHook)) { + if (primitiveHooks.includes(key as any)) continue + + customHookValues[key] = value + } + + // @ts-ignore + if (!extension.$elysiaChecksum) + // @ts-ignore + extension.$elysiaChecksum = [] + + const hash = checksum(JSON.stringify(customHookValues)) + + // @ts-ignore + if (extension.$elysiaChecksum.includes(hash)) continue + + // @ts-ignore + extension.$elysiaChecksum.push( + checksum(JSON.stringify(customHookValues)) ) + + traceBackExtension(extension(manager), localHook as any) + } } const hooks = mergeHook(globalHook, localHook) diff --git a/src/utils.ts b/src/utils.ts index 571bec4e..72c6b90e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -562,21 +562,14 @@ export const unsignCookie = async (input: string, secret: string | null) => { export const traceBackExtension = ( extension: BaseExtension, property: Record, - checksum: number | undefined, hooks = property ) => { for (const [key, value] of Object.entries(property)) { if (primitiveHooks.includes(key as any) || !(key in extension)) continue if (typeof extension[key] === 'function') { - // Property is reference to the original object - // @ts-ignore - if (!property[key]?.$elysiaChecksum) { - // @ts-ignore - property[key].$elysiaChecksum = checksum - extension[key](value) - } + extension[key](value) } else if (typeof extension[key] === 'object') - traceBackExtension(extension[key], value as any, checksum, hooks) + traceBackExtension(extension[key], value as any, hooks) } } diff --git a/test/extension/extension.test.ts b/test/extension/extension.test.ts new file mode 100644 index 00000000..75325f1d --- /dev/null +++ b/test/extension/extension.test.ts @@ -0,0 +1,273 @@ +import { describe, it, expect } from 'bun:test' +import Elysia from '../../src' +import { req } from '../utils' + +describe('Extension API', () => { + it('work', async () => { + let answer: string | undefined + + const app = new Elysia() + .extends(() => ({ + hi(config: string) { + answer = config + } + })) + .get('/', () => 'Hello World', { + hi: 'Hello World' + }) + + await app.handle(req('/')) + + expect(answer).toBe('Hello World') + }) + + it('accept function', async () => { + let answer: string | undefined + + const app = new Elysia() + .extends(() => ({ + hi(fn: () => any) { + fn() + } + })) + .get('/', () => 'Hello World', { + hi() { + answer = 'Hello World' + } + }) + + await app.handle(req('/')) + + expect(answer).toBe('Hello World') + }) + + it('create custom life-cycle', async () => { + const app = new Elysia() + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle(fn) + } + })) + .get('/', () => 'Hello World', { + hi: () => 'Hello World' + }) + + const response = await app.handle(req('/')).then((x) => x.text()) + + expect(response).toBe('Hello World') + }) + + it('insert after on local stack by default', async () => { + const orders: number[] = [] + + const app = new Elysia() + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle(fn) + } + })) + .onBeforeHandle(() => { + orders.push(1) + }) + .get('/', () => 'Hello World', { + beforeHandle() { + orders.push(2) + }, + hi: () => { + orders.push(3) + } + }) + + await app.handle(req('/')) + + expect(orders).toEqual([1, 2, 3]) + }) + + it('insert after on local stack', async () => { + const orders: number[] = [] + + const app = new Elysia() + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle({ insert: 'after', stack: 'local' }, fn) + } + })) + .onBeforeHandle(() => { + orders.push(1) + }) + .get('/', () => 'Hello World', { + beforeHandle() { + orders.push(2) + }, + hi: () => { + orders.push(3) + } + }) + + await app.handle(req('/')) + + expect(orders).toEqual([1, 2, 3]) + }) + + it('insert before on local stack', async () => { + const orders: number[] = [] + + const app = new Elysia() + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle({ insert: 'before', stack: 'local' }, fn) + } + })) + .onBeforeHandle(() => { + orders.push(1) + }) + .get('/', () => 'Hello World', { + beforeHandle() { + orders.push(3) + }, + hi: () => { + orders.push(2) + } + }) + + await app.handle(req('/')) + + expect(orders).toEqual([1, 2, 3]) + }) + + it('insert after on global stack', async () => { + const orders: number[] = [] + + const app = new Elysia() + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle({ insert: 'after', stack: 'global' }, fn) + } + })) + .onBeforeHandle(() => { + orders.push(1) + }) + .get('/', () => 'Hello World', { + beforeHandle() { + orders.push(3) + }, + hi: () => { + orders.push(2) + } + }) + + await app.handle(req('/')) + + expect(orders).toEqual([1, 2, 3]) + }) + + it('insert before on global stack', async () => { + const orders: number[] = [] + + const app = new Elysia() + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle({ insert: 'before', stack: 'global' }, fn) + } + })) + .onBeforeHandle(() => { + orders.push(2) + }) + .get('/', () => 'Hello World', { + beforeHandle() { + orders.push(3) + }, + hi: () => { + orders.push(1) + } + }) + + await app.handle(req('/')) + + expect(orders).toEqual([1, 2, 3]) + }) + + it('appends onParse', async () => { + const app = new Elysia() + .extends(({ onParse }) => ({ + hi(fn: () => any) { + onParse(fn) + } + })) + .get('/', () => 'Hello World', { + hi: () => {} + }) + + expect(app.routes[0].hooks.parse?.length).toEqual(1) + }) + + it('appends onTransform', async () => { + const app = new Elysia() + .extends(({ onTransform }) => ({ + hi(fn: () => any) { + onTransform(fn) + } + })) + .get('/', () => 'Hello World', { + hi: () => {} + }) + + expect(app.routes[0].hooks.transform?.length).toEqual(1) + }) + + it('appends onBeforeHandle', async () => { + const app = new Elysia() + .extends(({ onBeforeHandle }) => ({ + hi(fn: () => any) { + onBeforeHandle(fn) + } + })) + .get('/', () => 'Hello World', { + hi: () => {} + }) + + expect(app.routes[0].hooks.beforeHandle?.length).toEqual(1) + }) + + it('appends onAfterHandle', async () => { + const app = new Elysia() + .extends(({ onAfterHandle }) => ({ + hi(fn: () => any) { + onAfterHandle(fn) + } + })) + .get('/', () => 'Hello World', { + hi: () => {} + }) + + expect(app.routes[0].hooks.afterHandle?.length).toEqual(1) + }) + + it('appends onError', async () => { + const app = new Elysia() + .extends(({ onError }) => ({ + hi(fn: () => any) { + onError(fn) + } + })) + .get('/', () => 'Hello World', { + hi: () => {} + }) + + expect(app.routes[0].hooks.error?.length).toEqual(1) + }) + + it('appends onResponse', async () => { + const app = new Elysia() + .extends(({ onResponse }) => ({ + hi(fn: () => any) { + onResponse(fn) + } + })) + .get('/', () => 'Hello World', { + hi: () => {} + }) + + expect(app.routes[0].hooks.onResponse?.length).toEqual(1) + }) +})