@dooboostore/simple-boot-http-server 1.0.27 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +44 -9
  2. package/dist/cjs/SimpleBootHttpServer.js +15 -2
  3. package/dist/cjs/SimpleBootHttpServer.js.map +2 -2
  4. package/dist/cjs/decorators/MethodMapping.js.map +2 -2
  5. package/dist/cjs/models/RequestResponse.js +52 -0
  6. package/dist/cjs/models/RequestResponse.js.map +3 -3
  7. package/dist/cjs/proxy/RequestResponseInjectProxy.js +35 -0
  8. package/dist/cjs/proxy/RequestResponseInjectProxy.js.map +7 -0
  9. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +1 -1
  10. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
  11. package/dist/cjs/proxy/index.js +3 -1
  12. package/dist/cjs/proxy/index.js.map +2 -2
  13. package/dist/cjs/resolvers/Resolver.js.map +1 -1
  14. package/dist/esm/SimpleBootHttpServer.js +16 -3
  15. package/dist/esm/SimpleBootHttpServer.js.map +2 -2
  16. package/dist/esm/decorators/MethodMapping.js.map +2 -2
  17. package/dist/esm/models/RequestResponse.js +52 -0
  18. package/dist/esm/models/RequestResponse.js.map +2 -2
  19. package/dist/esm/proxy/RequestResponseInjectProxy.js +12 -0
  20. package/dist/esm/proxy/RequestResponseInjectProxy.js.map +7 -0
  21. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +1 -1
  22. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
  23. package/dist/esm/proxy/index.js +1 -0
  24. package/dist/esm/proxy/index.js.map +2 -2
  25. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +295 -252
  26. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +4 -4
  27. package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
  28. package/dist/types/decorators/MethodMapping.d.ts +13 -16
  29. package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
  30. package/dist/types/models/RequestResponse.d.ts +9 -0
  31. package/dist/types/models/RequestResponse.d.ts.map +1 -1
  32. package/dist/types/proxy/RequestResponseInjectProxy.d.ts +22 -0
  33. package/dist/types/proxy/RequestResponseInjectProxy.d.ts.map +1 -0
  34. package/dist/types/proxy/index.d.ts +1 -0
  35. package/dist/types/proxy/index.d.ts.map +1 -1
  36. package/dist/types/resolvers/Resolver.d.ts +2 -1
  37. package/dist/types/resolvers/Resolver.d.ts.map +1 -1
  38. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +295 -252
  39. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
  40. package/dist/umd-bundle/websocket-client.umd.js +106 -140
  41. package/dist/umd-bundle/websocket-client.umd.js.map +2 -2
  42. package/package.json +7 -7
  43. package/src/SimpleBootHttpServer.ts +19 -3
  44. package/src/decorators/MethodMapping.ts +151 -159
  45. package/src/models/RequestResponse.ts +50 -0
  46. package/src/proxy/RequestResponseInjectProxy.ts +30 -0
  47. package/src/proxy/SymbolIntentApiServiceProxy.ts +1 -1
  48. package/src/proxy/index.ts +1 -0
  49. package/src/resolvers/Resolver.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-boot-http-server",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -98,11 +98,11 @@
98
98
  "mime-types": "^2.1.34",
99
99
  "node-gzip": "^1.1.2",
100
100
  "ws": "^8.18.3",
101
- "@dooboostore/core": "1.0.32",
102
- "@dooboostore/core-node": "1.0.13",
103
- "@dooboostore/core-web": "1.0.13",
104
- "@dooboostore/simple-boot": "1.0.57",
105
- "@dooboostore/dom-render": "1.0.114"
101
+ "@dooboostore/core": "1.0.33",
102
+ "@dooboostore/core-node": "1.0.14",
103
+ "@dooboostore/dom-render": "1.0.115",
104
+ "@dooboostore/core-web": "1.0.14",
105
+ "@dooboostore/simple-boot": "1.0.58"
106
106
  },
107
107
  "scripts": {
108
108
  "clean": "rm -rf dist",
@@ -121,6 +121,6 @@
121
121
  "publish:npm": "pnpm run build && cp package.json package.json.back && pnpm pack && TARBALL_NAME=$(ls dooboostore-simple-boot-http-server-*.tgz | head -n 1) && tar -xzf \"$TARBALL_NAME\" -C . --strip-components=1 package/package.json && rm \"$TARBALL_NAME\" && (pnpm publish --no-git-checks || true) && cp package.json.back package.json && rm -rf package.json.back ",
122
122
  "ts:clean": "find . -name '*.js' -o -name '*.map' -o -name '*.d.ts' | xargs rm -f",
123
123
  "typecheck": "tsc --noEmit",
124
- "test": "echo test"
124
+ "test": "node --import ./test/register-loader.mjs --test --test-force-exit test/index.test.ts"
125
125
  }
126
126
  }
@@ -13,7 +13,7 @@ import {Filter} from './filters/Filter';
13
13
  import {ExceptionHandlerSituationType, targetExceptionHandler} from '@dooboostore/simple-boot';
14
14
  import {getInject, SituationTypeContainer, SituationTypeContainers} from '@dooboostore/simple-boot';
15
15
  import {EndPoint} from './endpoints/EndPoint';
16
- import {ReflectUtils} from '@dooboostore/core';
16
+ import {ReflectUtils, ValidUtils} from '@dooboostore/core';
17
17
  import {ReqFormUrlBody} from './models/datas/body/ReqFormUrlBody';
18
18
  import {ReqJsonBody} from './models/datas/body/ReqJsonBody';
19
19
  import {ReqHeader} from './models/datas/ReqHeader';
@@ -140,6 +140,13 @@ export class SimpleBootHttpServer extends SimpleApplication {
140
140
  otherStorage.set(RequestResponse, rr);
141
141
  otherStorage.set(IncomingMessage, req);
142
142
  otherStorage.set(ServerResponse, res);
143
+ // 클라이언트가 응답 완료 전에 끊으면 abort. 핸들러는 파라미터 타입 AbortSignal 로 주입받아
144
+ // fetch/timers/events.once 등에 넘기면 대기 중이어도 바로 풀린다 (제너레이터 스트림의 이벤트 대기 누수 방지)
145
+ const abortController = new AbortController();
146
+ res.once('close', () => {
147
+ if (!res.writableFinished) abortController.abort();
148
+ });
149
+ otherStorage.set(AbortSignal, abortController.signal);
143
150
  try {
144
151
  transactionManager?.try();
145
152
  if (this.option.requestEndPoints) {
@@ -327,12 +334,21 @@ export class SimpleBootHttpServer extends SimpleApplication {
327
334
  ...
328
335
  */
329
336
  const resConfig = it.config?.res;
330
- if (resConfig?.manual === true) {
337
+ if (resConfig === 'manual') {
331
338
  // 핸들러가 ServerResponse를 직접 주입받아 스스로 write/end를 제어한다 (예: SSE 스트리밍)
339
+ } else if (ValidUtils.isGenerator(data) || ValidUtils.isAsyncGenerator(data)) {
340
+ // 제너레이터 리턴: 헤더 먼저 보내고, yield 된 값을 하나씩 바로 body 로 흘려보낸다 (예: SSE)
341
+ const headers = resConfig?.header ?? {};
342
+ if (resConfig?.contentType) {
343
+ headers[HttpHeaders.ContentType] = resConfig.contentType;
344
+ }
345
+ rr.resSetHeaders(headers);
346
+ rr.resSetStatusCode(resConfig?.status ?? HttpStatus.Ok);
347
+ await rr.resStream(data);
332
348
  } else {
333
349
  if (it.config?.resolver) {
334
350
  const execute = typeof it.config.resolver === 'function' ? this.simstanceManager.getOrNewSim({target: it.config.resolver}) : it.config.resolver;
335
- data = await execute?.resolve?.(data, rr);
351
+ data = await execute?.resolve?.(data, rr, resConfig);
336
352
  }
337
353
  const status = resConfig?.status ?? HttpStatus.Ok;
338
354
  const headers = resConfig?.header ?? {};
@@ -6,241 +6,233 @@ import { Mimes } from '../codes/Mimes';
6
6
  import { HttpMethod } from '../codes/HttpMethod';
7
7
 
8
8
  export enum UrlMappingSituationType {
9
- REQ_JSON_BODY = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_JSON_BODY',
10
- REQ_PATH_DATA = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_PATH_DATA',
11
- // REQ_URL_SEARCH_PARAMS = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_URL_SEARCH_PARAMS',
12
- REQ_URL_SEARCH_PARAMS_OBJ = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_URL_SEARCH_PARAMS_OBJ',
13
- REQ_FORM_URL_BODY = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_FORM_URL_BODY'
14
- }
15
- export type MappingConfig = {
16
- method: HttpMethod | string,
17
- description?: {
18
- name?: string;
19
- detail?: string;
20
- };
9
+ REQ_JSON_BODY = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_JSON_BODY',
10
+ REQ_PATH_DATA = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_PATH_DATA',
11
+ // REQ_URL_SEARCH_PARAMS = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_URL_SEARCH_PARAMS',
12
+ REQ_URL_SEARCH_PARAMS_OBJ = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_URL_SEARCH_PARAMS_OBJ',
13
+ REQ_FORM_URL_BODY = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_FORM_URL_BODY'
14
+ }
15
+ export type RequestMappingConfig = {
16
+ method: HttpMethod | string;
17
+ description?: {
18
+ name?: string;
19
+ detail?: string;
20
+ };
21
21
 
22
- req?: {
23
- contentType?: (Mimes|string)[];
24
- accept?: (Mimes|string)[];
25
- };
26
-
27
- res?:
28
- | {
29
- status?: number;
30
- header?: {[key: string]: string};
31
- contentType?: Mimes | string;
32
- manual?: false;
33
- }
34
- /**
35
- * manual: true — 리턴값을 이용한 자동 status/header/body 처리(및 resolver 호출)를 건너뛴다.
36
- * SSE 등 핸들러가 ServerResponse를 직접 주입받아 writeHead/write/end를 스스로 제어하는 경우 사용.
37
- */
38
- | { manual: true };
39
- resolver?: Resolver|ConstructorType<Resolver>;
40
- }
41
- export type SaveMappingConfig = {propertyKey: string | symbol; config: MappingConfig}
22
+ req?: {
23
+ contentType?: (Mimes | string)[];
24
+ accept?: (Mimes | string)[];
25
+ };
26
+ };
27
+ export type ResponseMappingConfig = {
28
+ status?: number;
29
+ header?: { [key: string]: string };
30
+ contentType?: Mimes | string;
31
+ };
32
+ export type MappingConfig = (RequestMappingConfig & { res?: ResponseMappingConfig; resolver?: Resolver | ConstructorType<Resolver> }) | (RequestMappingConfig & { res: 'manual' });
33
+ export type SaveMappingConfig = { propertyKey: string | symbol; config: MappingConfig };
42
34
  const MappingMetadataKey = Symbol.for('simple-boot-http-server:mapping-metadata');
43
35
  // const GETSMappingMetadataKey = Symbol('GET_METHODS');
44
36
  // const GETMappingMetadataKey = Symbol('GET_METHOD');
45
37
 
46
38
  const process = (config: MappingConfig, target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
47
- const saveMappingConfigs = (ReflectUtils.getMetadata(MappingMetadataKey, target.constructor) ?? []) as SaveMappingConfig[];
48
- saveMappingConfigs.push({propertyKey, config});
49
- ReflectUtils.defineMetadata(MappingMetadataKey, saveMappingConfigs, target.constructor);
50
- ReflectUtils.defineMetadata(MappingMetadataKey, config, target, propertyKey);
51
- }
39
+ const saveMappingConfigs = (ReflectUtils.getMetadata(MappingMetadataKey, target.constructor) ?? []) as SaveMappingConfig[];
40
+ saveMappingConfigs.push({ propertyKey, config });
41
+ ReflectUtils.defineMetadata(MappingMetadataKey, saveMappingConfigs, target.constructor);
42
+ ReflectUtils.defineMetadata(MappingMetadataKey, config, target, propertyKey);
43
+ };
52
44
  export function UrlMapping(config: MappingConfig): ReflectMethod;
53
45
  export function UrlMapping(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
54
46
  export function UrlMapping(configOrtarget?: MappingConfig | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
55
- if (propertyKey && descriptor) {
56
- process({method: HttpMethod.GET}, configOrtarget, propertyKey, descriptor);
57
- } else {
58
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
59
- process(configOrtarget, target, propertyKey, descriptor);
60
- }
61
- }
47
+ if (propertyKey && descriptor) {
48
+ process({ method: HttpMethod.GET }, configOrtarget, propertyKey, descriptor);
49
+ } else {
50
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
51
+ process(configOrtarget, target, propertyKey, descriptor);
52
+ };
53
+ }
62
54
  }
63
55
  export const getUrlMapping = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
64
- return ReflectUtils.getMetadata(MappingMetadataKey, target, propertyKey);
65
- }
56
+ return ReflectUtils.getMetadata(MappingMetadataKey, target, propertyKey);
57
+ };
66
58
 
67
59
  export const getUrlMappings = (target: any): SaveMappingConfig[] => {
68
- if (target !== null && undefined !== target && typeof target === 'object') {
69
- target = target.constructor;
70
- }
71
- return ReflectUtils.getMetadata(MappingMetadataKey, target);
72
- }
60
+ if (target !== null && undefined !== target && typeof target === 'object') {
61
+ target = target.constructor;
62
+ }
63
+ return ReflectUtils.getMetadata(MappingMetadataKey, target);
64
+ };
73
65
  export function GET(config: Omit<MappingConfig, 'method'>): ReflectMethod;
74
66
  export function GET(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
75
67
  export function GET(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
76
- if (propertyKey && descriptor) {
77
- process({method: HttpMethod.GET}, configOrTarget, propertyKey, descriptor);
78
- } else {
79
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
80
- configOrTarget.method = HttpMethod.GET;
81
- process(configOrTarget, target, propertyKey, descriptor);
82
- }
83
- }
68
+ if (propertyKey && descriptor) {
69
+ process({ method: HttpMethod.GET }, configOrTarget, propertyKey, descriptor);
70
+ } else {
71
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
72
+ configOrTarget.method = HttpMethod.GET;
73
+ process(configOrTarget, target, propertyKey, descriptor);
74
+ };
75
+ }
84
76
  }
85
77
 
86
78
  export const getGET = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
87
- return getUrlMapping(target, propertyKey);
88
- }
79
+ return getUrlMapping(target, propertyKey);
80
+ };
89
81
  export const getGETS = (target: any): SaveMappingConfig[] => {
90
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.GET);
91
- }
82
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.GET);
83
+ };
92
84
  export function POST(config: Omit<MappingConfig, 'method'>): ReflectMethod;
93
85
  export function POST(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
94
86
  export function POST(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
95
- if (propertyKey && descriptor) {
96
- process({method: HttpMethod.POST}, configOrTarget, propertyKey, descriptor);
97
- } else {
98
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
99
- configOrTarget.method = HttpMethod.POST;
100
- process(configOrTarget, target, propertyKey, descriptor);
101
- }
102
- }
87
+ if (propertyKey && descriptor) {
88
+ process({ method: HttpMethod.POST }, configOrTarget, propertyKey, descriptor);
89
+ } else {
90
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
91
+ configOrTarget.method = HttpMethod.POST;
92
+ process(configOrTarget, target, propertyKey, descriptor);
93
+ };
94
+ }
103
95
  }
104
96
 
105
97
  export const getPOST = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
106
- return getUrlMapping(target, propertyKey);
107
- }
98
+ return getUrlMapping(target, propertyKey);
99
+ };
108
100
  export const getPOSTS = (target: any): SaveMappingConfig[] => {
109
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.POST);
110
- }
101
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.POST);
102
+ };
111
103
  export function DELETE(config: Omit<MappingConfig, 'method'>): ReflectMethod;
112
104
  export function DELETE(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
113
105
  export function DELETE(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
114
- if (propertyKey && descriptor) {
115
- process({method: HttpMethod.DELETE}, configOrTarget, propertyKey, descriptor);
116
- } else {
117
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
118
- configOrTarget.method = HttpMethod.DELETE;
119
- process(configOrTarget, target, propertyKey, descriptor);
120
- }
121
- }
106
+ if (propertyKey && descriptor) {
107
+ process({ method: HttpMethod.DELETE }, configOrTarget, propertyKey, descriptor);
108
+ } else {
109
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
110
+ configOrTarget.method = HttpMethod.DELETE;
111
+ process(configOrTarget, target, propertyKey, descriptor);
112
+ };
113
+ }
122
114
  }
123
115
 
124
116
  export const getDELETE = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
125
- return getUrlMapping(target, propertyKey);
126
- }
117
+ return getUrlMapping(target, propertyKey);
118
+ };
127
119
  export const getDELETES = (target: any): SaveMappingConfig[] => {
128
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.DELETE);
129
- }
120
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.DELETE);
121
+ };
130
122
  export function PUT(config: Omit<MappingConfig, 'method'>): ReflectMethod;
131
123
  export function PUT(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
132
124
  export function PUT(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
133
- if (propertyKey && descriptor) {
134
- process({method: HttpMethod.PUT}, configOrTarget, propertyKey, descriptor);
135
- } else {
136
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
137
- configOrTarget.method = HttpMethod.PUT;
138
- process(configOrTarget, target, propertyKey, descriptor);
139
- }
140
- }
125
+ if (propertyKey && descriptor) {
126
+ process({ method: HttpMethod.PUT }, configOrTarget, propertyKey, descriptor);
127
+ } else {
128
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
129
+ configOrTarget.method = HttpMethod.PUT;
130
+ process(configOrTarget, target, propertyKey, descriptor);
131
+ };
132
+ }
141
133
  }
142
134
 
143
135
  export const getPUT = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
144
- return getUrlMapping(target, propertyKey);
145
- }
136
+ return getUrlMapping(target, propertyKey);
137
+ };
146
138
  export const getPUTS = (target: any): SaveMappingConfig[] => {
147
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PUT);
148
- }
139
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PUT);
140
+ };
149
141
  export function PATCH(config: Omit<MappingConfig, 'method'>): ReflectMethod;
150
142
  export function PATCH(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
151
143
  export function PATCH(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
152
- if (propertyKey && descriptor) {
153
- process({method: HttpMethod.PATCH}, configOrTarget, propertyKey, descriptor);
154
- } else {
155
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
156
- configOrTarget.method = HttpMethod.PATCH;
157
- process(configOrTarget, target, propertyKey, descriptor);
158
- }
159
- }
144
+ if (propertyKey && descriptor) {
145
+ process({ method: HttpMethod.PATCH }, configOrTarget, propertyKey, descriptor);
146
+ } else {
147
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
148
+ configOrTarget.method = HttpMethod.PATCH;
149
+ process(configOrTarget, target, propertyKey, descriptor);
150
+ };
151
+ }
160
152
  }
161
153
 
162
154
  export const getPATCH = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
163
- return getUrlMapping(target, propertyKey);
164
- }
155
+ return getUrlMapping(target, propertyKey);
156
+ };
165
157
  export const getPATCHS = (target: any): SaveMappingConfig[] => {
166
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PATCH);
167
- }
158
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PATCH);
159
+ };
168
160
  export function OPTIONS(config: Omit<MappingConfig, 'method'>): ReflectMethod;
169
161
  export function OPTIONS(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
170
162
  export function OPTIONS(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
171
- if (propertyKey && descriptor) {
172
- process({method: HttpMethod.OPTIONS}, configOrTarget, propertyKey, descriptor);
173
- } else {
174
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
175
- configOrTarget.method = HttpMethod.OPTIONS;
176
- process(configOrTarget, target, propertyKey, descriptor);
177
- }
178
- }
163
+ if (propertyKey && descriptor) {
164
+ process({ method: HttpMethod.OPTIONS }, configOrTarget, propertyKey, descriptor);
165
+ } else {
166
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
167
+ configOrTarget.method = HttpMethod.OPTIONS;
168
+ process(configOrTarget, target, propertyKey, descriptor);
169
+ };
170
+ }
179
171
  }
180
172
 
181
173
  export const getOPTIONS = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
182
- return getUrlMapping(target, propertyKey);
183
- }
174
+ return getUrlMapping(target, propertyKey);
175
+ };
184
176
  export const getOPTIONSS = (target: any): SaveMappingConfig[] => {
185
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.OPTIONS);
186
- }
177
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.OPTIONS);
178
+ };
187
179
  export function HEAD(config: Omit<MappingConfig, 'method'>): ReflectMethod;
188
180
  export function HEAD(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
189
181
  export function HEAD(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
190
- if (propertyKey && descriptor) {
191
- process({method: HttpMethod.HEAD}, configOrTarget, propertyKey, descriptor);
192
- } else {
193
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
194
- configOrTarget.method = HttpMethod.HEAD;
195
- process(configOrTarget, target, propertyKey, descriptor);
196
- }
197
- }
182
+ if (propertyKey && descriptor) {
183
+ process({ method: HttpMethod.HEAD }, configOrTarget, propertyKey, descriptor);
184
+ } else {
185
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
186
+ configOrTarget.method = HttpMethod.HEAD;
187
+ process(configOrTarget, target, propertyKey, descriptor);
188
+ };
189
+ }
198
190
  }
199
191
 
200
192
  export const getHEAD = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
201
- return getUrlMapping(target, propertyKey);
202
- }
193
+ return getUrlMapping(target, propertyKey);
194
+ };
203
195
  export const getHEADS = (target: any): SaveMappingConfig[] => {
204
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.HEAD);
205
- }
196
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.HEAD);
197
+ };
206
198
  export function TRACE(config: Omit<MappingConfig, 'method'>): ReflectMethod;
207
199
  export function TRACE(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
208
200
  export function TRACE(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
209
- if (propertyKey && descriptor) {
210
- process({method: HttpMethod.TRACE}, configOrTarget, propertyKey, descriptor);
211
- } else {
212
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
213
- configOrTarget.method = HttpMethod.TRACE;
214
- process(configOrTarget, target, propertyKey, descriptor);
215
- }
216
- }
201
+ if (propertyKey && descriptor) {
202
+ process({ method: HttpMethod.TRACE }, configOrTarget, propertyKey, descriptor);
203
+ } else {
204
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
205
+ configOrTarget.method = HttpMethod.TRACE;
206
+ process(configOrTarget, target, propertyKey, descriptor);
207
+ };
208
+ }
217
209
  }
218
210
 
219
211
  export const getTRACE = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
220
- return getUrlMapping(target, propertyKey);
221
- }
212
+ return getUrlMapping(target, propertyKey);
213
+ };
222
214
  export const getTRACES = (target: any): SaveMappingConfig[] => {
223
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.TRACE);
224
- }
215
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.TRACE);
216
+ };
225
217
  export function CONNECT(config: Omit<MappingConfig, 'method'>): ReflectMethod;
226
218
  export function CONNECT(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
227
219
  export function CONNECT(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
228
- if (propertyKey && descriptor) {
229
- process({method: HttpMethod.CONNECT}, configOrTarget, propertyKey, descriptor);
230
- } else {
231
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
232
- configOrTarget.method = HttpMethod.CONNECT;
233
- process(configOrTarget, target, propertyKey, descriptor);
234
- }
235
- }
220
+ if (propertyKey && descriptor) {
221
+ process({ method: HttpMethod.CONNECT }, configOrTarget, propertyKey, descriptor);
222
+ } else {
223
+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
224
+ configOrTarget.method = HttpMethod.CONNECT;
225
+ process(configOrTarget, target, propertyKey, descriptor);
226
+ };
227
+ }
236
228
  }
237
229
 
238
230
  export const getCONNECT = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
239
- return getUrlMapping(target, propertyKey);
240
- }
231
+ return getUrlMapping(target, propertyKey);
232
+ };
241
233
  export const getCONNECTS = (target: any): SaveMappingConfig[] => {
242
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.CONNECT);
243
- }
234
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.CONNECT);
235
+ };
244
236
 
245
237
  // Aliases for lowercase
246
238
  export const urlMapping = UrlMapping;
@@ -11,6 +11,7 @@ import { ReqHeader } from './datas/ReqHeader';
11
11
  import { ReqMultipartFormBody } from './datas/body/ReqMultipartFormBody';
12
12
  import { HttpStatus } from '../codes/HttpStatus';
13
13
  import { gzip } from 'node-gzip';
14
+ import { createGzip, constants as zlibConstants } from 'zlib';
14
15
  import { SessionManager } from '../session/SessionManager';
15
16
  import { HttpServerOption } from '../option/HttpServerOption';
16
17
  import { Blob } from 'node:buffer';
@@ -557,6 +558,55 @@ export class RequestResponse {
557
558
  this.res.end();
558
559
  }
559
560
 
561
+ /**
562
+ * 헤더를 즉시 보내고, source 가 내놓는 값을 하나씩 바로 body 에 쓴 뒤 end.
563
+ * (SSE / 스트리밍 응답용. resWrite 는 버퍼링 후 resEnd 에서 한 번에 보내므로 스트리밍 불가)
564
+ * - 문자열/Buffer 는 그대로, 그 외는 JSON.stringify
565
+ * - Accept-Encoding 에 gzip 이 있으면 gzip 스트림으로 압축하고, 청크마다 Z_SYNC_FLUSH 로 바로 내보낸다
566
+ * (flush 없이 쓰면 zlib 내부 버퍼에 모였다가 늦게 나가서 SSE 실시간성이 깨짐)
567
+ * - 클라이언트가 끊으면 source.return() 으로 제너레이터를 종료시켜 finally 가 돌게 한다
568
+ */
569
+ async resStream(source: Iterable<any> | AsyncIterable<any>) {
570
+ const res = this.res;
571
+ const gz = this.reqHeaderFirst(HttpHeaders.AcceptEncoding)?.includes('gzip') ? createGzip() : undefined;
572
+ if (gz) {
573
+ res.setHeader(HttpHeaders.ContentEncoding, 'gzip');
574
+ res.removeHeader(HttpHeaders.ContentLength);
575
+ gz.pipe(res);
576
+ }
577
+ const out = gz ?? res;
578
+ res.flushHeaders();
579
+ let closed = false;
580
+ const onClose = () => {
581
+ closed = true;
582
+ (source as any).return?.();
583
+ };
584
+ res.once('close', onClose);
585
+ try {
586
+ for await (const chunk of source) {
587
+ if (closed) break;
588
+ const data = typeof chunk === 'string' || Buffer.isBuffer(chunk) ? chunk : JSON.stringify(chunk);
589
+ const ok = out.write(data);
590
+ gz?.flush(zlibConstants.Z_SYNC_FLUSH);
591
+ // backpressure: 버퍼가 차면 drain(또는 연결 종료)까지 대기
592
+ if (!ok) await new Promise(resolve => { out.once('drain', resolve); res.once('close', resolve); });
593
+ }
594
+ } catch (e) {
595
+ // 클라이언트가 끊긴 뒤의 에러(AbortSignal 로 대기를 푼 AbortError 등)는 받을 상대가 없으니 삼킨다
596
+ if (!closed) throw e;
597
+ } finally {
598
+ res.off('close', onClose);
599
+ if (closed) {
600
+ gz?.destroy();
601
+ } else if (!res.writableEnded) {
602
+ // gzip 이면 gz.end() → pipe 가 res.end() 까지 해준다. 끝까지 흘려보낸 뒤 리턴
603
+ const finished = new Promise(resolve => { res.once('finish', resolve); res.once('close', resolve); });
604
+ out.end();
605
+ await finished;
606
+ }
607
+ }
608
+ }
609
+
560
610
  resIsDone() {
561
611
  return this.res.finished || this.res.writableEnded || this.res.headersSent;
562
612
  }
@@ -0,0 +1,30 @@
1
+ import { RequestResponse } from '../models/RequestResponse';
2
+
3
+ /**
4
+ * SSR 인프로세스 렌더용 sim 래퍼.
5
+ *
6
+ * 클라이언트는 SymbolIntentApiServiceProxy 가 HTTP 로 호출하며 IntentSchemeFilter 가
7
+ * intent.data 마지막에 rr(RequestResponse) 을 붙여 백서비스의 data 인자로 전달한다.
8
+ * 반면 SSR 은 페이지가 sim 을 인프로세스로 직접 호출(`svc.method(request)`)하므로 rr 이 없다.
9
+ *
10
+ * 이 프록시는 그 공백을 메운다 — 메서드 호출 시 넘어온 인자 **뒤에 rr 을 붙여준다**.
11
+ * IntentSchemeFilter 규약과 동일하게:
12
+ * - 인자 0개 → (rr) // 본문·쿼리 없는 요청과 동일
13
+ * - 인자 1개 → (request, rr) // 일반 서비스 호출
14
+ * - 인자 N개 → (...args, rr) // 이미 data 를 넘겼으면 그건 그대로 쓰이고 rr 은 뒤에 무시됨
15
+ *
16
+ * 백서비스 메서드가 `data?: RequestResponse` 옵셔널이라 fn.length 로 arity 를 못 재므로
17
+ * (옵셔널은 length 에서 제외) "메서드 파라미터 수"가 아니라 "호출 인자 뒤 append" 방식을 쓴다.
18
+ *
19
+ * constructor / 비함수 프로퍼티는 감싸지 않는다 — Sim 이 it.constructor 로 타입·메타데이터를 찾기 때문.
20
+ * rr 이 없으면(비SSR) 인자를 그대로 흘려보낸다.
21
+ */
22
+ export const injectRequestResponse = <T extends object>(sim: T, rr?: RequestResponse): T =>
23
+ new Proxy(sim, {
24
+ get(target: any, prop: string | symbol) {
25
+ const value = target[prop];
26
+ if (prop === 'constructor' || typeof value !== 'function') return value;
27
+ if (!rr) return value.bind(target);
28
+ return (...args: any[]) => value.apply(target, [...args, rr]);
29
+ }
30
+ });
@@ -40,7 +40,7 @@ export const createHandler = <T extends object>(apiService: ApiService): Proxy
40
40
  fetch: {
41
41
  credentials: 'include',
42
42
  headers: headers,
43
- body: ConvertUtils.toFormData(userConfig?.body)
43
+ body: userConfig?.body ? ConvertUtils.toFormData(userConfig.body) : undefined
44
44
  }
45
45
  }
46
46
  });
@@ -1,3 +1,4 @@
1
1
  export * from './SymbolIntentApiServiceProxy';
2
2
  export * from './SymbolIntentApiServiceProxyBase';
3
3
  export * from './TopicProtocolApiServiceProxy';
4
+ export * from './RequestResponseInjectProxy';
@@ -1,5 +1,6 @@
1
1
  import {RequestResponse} from '../models/RequestResponse';
2
+ import { ResponseMappingConfig } from '../decorators';
2
3
 
3
4
  export interface Resolver<T = any> {
4
- resolve(data: T, rr: RequestResponse): Promise<void>;
5
+ resolve(data: T, rr: RequestResponse, responseMappingConfig: ResponseMappingConfig): Promise<void>;
5
6
  }