From 9f6ead59470735c3caa97616aeba9eb315165d40 Mon Sep 17 00:00:00 2001 From: ByungJoon Lee Date: Wed, 29 Apr 2026 02:05:42 +0900 Subject: [PATCH 1/4] chore(jin-frame): add silent mode scripts and apply to pre-commit hook Co-Authored-By: Claude Sonnet 4.6 --- .hooks/pre-commit | 4 ++-- packages/jin-frame/package.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) 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..dc73635 100644 --- a/packages/jin-frame/package.json +++ b/packages/jin-frame/package.json @@ -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", From 1ab023b805137f26af87eed4c8f7dcc93b65ad8d Mon Sep 17 00:00:00 2001 From: ByungJoon Lee Date: Wed, 29 Apr 2026 02:09:18 +0900 Subject: [PATCH 2/4] fix(jin-frame): traverse prototype chain in getFieldMetadata for inheritance support Replace Reflect.getOwnMetadata with Reflect.getMetadata so that field decorators defined on parent classes are correctly inherited by child classes. Co-Authored-By: Claude Sonnet 4.6 --- .../fields/handlers/getFieldMetadata.test.ts | 44 +++++++++++++++++++ .../fields/handlers/getFieldMetadata.ts | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) 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..1716106 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,46 @@ 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; +} + +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' })])); + }); +}); 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) { From 777ce25bb35e2203c5c0da3d4c482665b3d50ebe Mon Sep 17 00:00:00 2001 From: ByungJoon Lee Date: Wed, 29 Apr 2026 02:11:31 +0900 Subject: [PATCH 3/4] chore(jin-frame): bump version to 5.0.4 Co-Authored-By: Claude Sonnet 4.6 --- packages/jin-frame/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jin-frame/package.json b/packages/jin-frame/package.json index dc73635..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": { From 8afa2b85e56046e82a82d80add619e65f2d70476 Mon Sep 17 00:00:00 2001 From: ByungJoon Lee Date: Wed, 29 Apr 2026 02:12:45 +0900 Subject: [PATCH 4/4] test(jin-frame): add override case to getFieldMetadata inheritance tests Co-Authored-By: Claude Sonnet 4.6 --- .../fields/handlers/getFieldMetadata.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 1716106..31929b3 100644 --- a/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.test.ts +++ b/packages/jin-frame/src/decorators/fields/handlers/getFieldMetadata.test.ts @@ -125,6 +125,18 @@ class ChildRequest extends BaseRequest { 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({ @@ -148,4 +160,16 @@ describe('getFieldMetadata with inheritance', () => { ); 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); + }); });