diff --git a/.hooks/pre-commit b/.hooks/pre-commit index 9fdc237..b2382a1 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -15,8 +15,8 @@ pnpm --filter jin-frame run build echo -e "\n ${yellow}* code build end.${no_color}" echo -e "\n ${yellow}* code test start.${no_color}" -pnpm --filter @jin-frame/generator-core run test -pnpm --filter jin-frame run test +pnpm --filter @jin-frame/generator-core run test:silent +pnpm --filter jin-frame run test:silent echo -e "\n ${yellow}* code test end.${no_color}" echo -e "\n ${green}* pre-commit hook was SUCCESSFUL${no_color} \n" \ No newline at end of file diff --git a/packages/jin-frame/package.json b/packages/jin-frame/package.json index 6a7a52f..4f044c2 100644 --- a/packages/jin-frame/package.json +++ b/packages/jin-frame/package.json @@ -1,6 +1,6 @@ { "name": "jin-frame", - "version": "5.0.3", + "version": "5.0.4", "description": "Reusable HTTP API request definition library", "packageManager": "pnpm@10.16.0", "scripts": { @@ -20,9 +20,10 @@ "pub:prod": "pnpm run bundle && cross-env RELEASE_MODE=true pnpm publish --access=public", "clean": "rimraf dist", "lint": "eslint --cache .", + "lint:silent": "eslint --cache --quiet .", "lint-staged": "lint-staged", "test:only": "vitest run --coverage", - "test:silent": "vitest run --coverage", + "test:silent": "vitest run --coverage --silent", "test": "vitest run --coverage", "ctix": "ctix build --config .configs/.ctirc", "ctix-rm": "ctix remove --force-yes --remove-backup", diff --git a/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.test.ts b/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.test.ts index db6fbde..31929b3 100644 --- a/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.test.ts +++ b/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.test.ts @@ -3,6 +3,7 @@ import { getFieldMetadata } from '#decorators/fields/handlers/getFieldMetadata'; import { Param } from '#decorators/fields/Param'; import { Query } from '#decorators/fields/Query'; import { Post } from '#decorators/methods/Post'; +import { Get } from '#decorators/methods/Get'; import { describe, expect, it } from 'vitest'; import { Body } from '#decorators/fields/Body'; import { ObjectBody } from '#decorators/fields/ObjectBody'; @@ -105,3 +106,70 @@ describe('getFieldMetadata', () => { }); }); }); + +@Post({ host: 'https://api.somesite.com', path: 'base/path' }) +class BaseRequest extends JinFrame { + @Query() + readonly baseField!: string; + + @Header({ replaceAt: 'X-Base-Header' }) + readonly baseHeader!: string; +} + +@Get({ host: 'https://api.somesite.com', path: 'child/path' }) +class ChildRequest extends BaseRequest { + @Param() + readonly childParam!: string; + + @Body() + readonly childBody!: string; +} + +@Post({ host: 'https://api.somesite.com', path: 'override/base' }) +class OverrideBaseRequest extends JinFrame { + @Query() + readonly sharedField!: string; +} + +@Get({ host: 'https://api.somesite.com', path: 'override/child' }) +class OverrideChildRequest extends OverrideBaseRequest { + @Body() + declare readonly sharedField: string; +} + +describe('getFieldMetadata with inheritance', () => { + it('should include parent class field metadata in child class', () => { + const r = ChildRequest.of({ + baseField: 'base-value', + baseHeader: 'base-header-value', + childParam: 'child-param-value', + childBody: 'child-body-value', + }); + + const metas = getFieldMetadata( + r.constructor.prototype, + Object.entries(r).map(([key, value]) => ({ key, value })), + ); + + expect(metas.query).toEqual(expect.arrayContaining([expect.objectContaining({ key: 'baseField', type: 'query' })])); + expect(metas.header).toEqual( + expect.arrayContaining([expect.objectContaining({ key: 'baseHeader', replaceAt: 'X-Base-Header' })]), + ); + expect(metas.param).toEqual( + expect.arrayContaining([expect.objectContaining({ key: 'childParam', type: 'param' })]), + ); + expect(metas.body).toEqual(expect.arrayContaining([expect.objectContaining({ key: 'childBody', type: 'body' })])); + }); + + it('should use child decorator when child overrides a field from parent', () => { + const r = OverrideChildRequest.of({ sharedField: 'value' }); + + const metas = getFieldMetadata( + r.constructor.prototype, + Object.entries(r).map(([key, value]) => ({ key, value })), + ); + + expect(metas.body).toEqual(expect.arrayContaining([expect.objectContaining({ key: 'sharedField', type: 'body' })])); + expect(metas.query).toHaveLength(0); + }); +}); diff --git a/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.ts b/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.ts index 5f35b20..13cc397 100644 --- a/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.ts +++ b/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.ts @@ -28,7 +28,7 @@ export function getFieldMetadata(type: object, keys: { key: string; value: unkno const fields: { key: string; meta: FieldEntry }[] = []; for (const key of keys) { - const raw = Reflect.getOwnMetadata(REQUEST_FIELD_DECORATOR, type, key.key) as FieldEntry[] | undefined; + const raw = Reflect.getMetadata(REQUEST_FIELD_DECORATOR, type, key.key) as FieldEntry[] | undefined; if (raw != null) { for (const entry of raw) {