@dooboostore/simple-boot-http-server 1.0.26 → 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 (71) hide show
  1. package/README.md +138 -17
  2. package/dist/cjs/SimpleBootHttpServer.js +33 -16
  3. package/dist/cjs/SimpleBootHttpServer.js.map +2 -2
  4. package/dist/cjs/codes/Mimes.js +2 -0
  5. package/dist/cjs/codes/Mimes.js.map +2 -2
  6. package/dist/cjs/decorators/MethodMapping.js +2 -2
  7. package/dist/cjs/decorators/MethodMapping.js.map +2 -2
  8. package/dist/cjs/filters/ResourceFilter.js +0 -1
  9. package/dist/cjs/filters/ResourceFilter.js.map +2 -2
  10. package/dist/cjs/models/RequestResponse.js +52 -0
  11. package/dist/cjs/models/RequestResponse.js.map +3 -3
  12. package/dist/cjs/proxy/RequestResponseInjectProxy.js +35 -0
  13. package/dist/cjs/proxy/RequestResponseInjectProxy.js.map +7 -0
  14. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +1 -1
  15. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
  16. package/dist/cjs/proxy/index.js +3 -1
  17. package/dist/cjs/proxy/index.js.map +2 -2
  18. package/dist/cjs/resolvers/Resolver.js.map +1 -1
  19. package/dist/cjs/websocket/WebSocketManager.js +68 -40
  20. package/dist/cjs/websocket/WebSocketManager.js.map +2 -2
  21. package/dist/esm/SimpleBootHttpServer.js +34 -17
  22. package/dist/esm/SimpleBootHttpServer.js.map +2 -2
  23. package/dist/esm/codes/Mimes.js +2 -0
  24. package/dist/esm/codes/Mimes.js.map +2 -2
  25. package/dist/esm/decorators/MethodMapping.js +2 -2
  26. package/dist/esm/decorators/MethodMapping.js.map +2 -2
  27. package/dist/esm/filters/ResourceFilter.js +0 -1
  28. package/dist/esm/filters/ResourceFilter.js.map +2 -2
  29. package/dist/esm/models/RequestResponse.js +52 -0
  30. package/dist/esm/models/RequestResponse.js.map +2 -2
  31. package/dist/esm/proxy/RequestResponseInjectProxy.js +12 -0
  32. package/dist/esm/proxy/RequestResponseInjectProxy.js.map +7 -0
  33. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +1 -1
  34. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
  35. package/dist/esm/proxy/index.js +1 -0
  36. package/dist/esm/proxy/index.js.map +2 -2
  37. package/dist/esm/websocket/WebSocketManager.js +68 -40
  38. package/dist/esm/websocket/WebSocketManager.js.map +2 -2
  39. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +572 -387
  40. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +4 -4
  41. package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
  42. package/dist/types/codes/Mimes.d.ts +2 -0
  43. package/dist/types/codes/Mimes.d.ts.map +1 -1
  44. package/dist/types/decorators/MethodMapping.d.ts +13 -8
  45. package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
  46. package/dist/types/filters/ResourceFilter.d.ts.map +1 -1
  47. package/dist/types/models/RequestResponse.d.ts +9 -0
  48. package/dist/types/models/RequestResponse.d.ts.map +1 -1
  49. package/dist/types/proxy/RequestResponseInjectProxy.d.ts +22 -0
  50. package/dist/types/proxy/RequestResponseInjectProxy.d.ts.map +1 -0
  51. package/dist/types/proxy/index.d.ts +1 -0
  52. package/dist/types/proxy/index.d.ts.map +1 -1
  53. package/dist/types/resolvers/Resolver.d.ts +2 -1
  54. package/dist/types/resolvers/Resolver.d.ts.map +1 -1
  55. package/dist/types/websocket/WebSocketManager.d.ts +3 -1
  56. package/dist/types/websocket/WebSocketManager.d.ts.map +1 -1
  57. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +572 -387
  58. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
  59. package/dist/umd-bundle/websocket-client.umd.js +315 -149
  60. package/dist/umd-bundle/websocket-client.umd.js.map +4 -4
  61. package/package.json +9 -9
  62. package/src/SimpleBootHttpServer.ts +38 -17
  63. package/src/codes/Mimes.ts +2 -0
  64. package/src/decorators/MethodMapping.ts +151 -152
  65. package/src/filters/ResourceFilter.ts +0 -1
  66. package/src/models/RequestResponse.ts +50 -0
  67. package/src/proxy/RequestResponseInjectProxy.ts +30 -0
  68. package/src/proxy/SymbolIntentApiServiceProxy.ts +1 -1
  69. package/src/proxy/index.ts +1 -0
  70. package/src/resolvers/Resolver.ts +2 -1
  71. package/src/websocket/WebSocketManager.ts +69 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-boot-http-server",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -67,12 +67,7 @@
67
67
  "url": "https://github.com/dooboostore-develop/packages"
68
68
  },
69
69
  "peerDependencies": {
70
- "reflect-metadata": "^0.2.2",
71
- "@dooboostore/core": "1.0.32",
72
- "@dooboostore/core-node": "1.0.13",
73
- "@dooboostore/core-web": "1.0.13",
74
- "@dooboostore/dom-render": "1.0.114",
75
- "@dooboostore/simple-boot": "1.0.57"
70
+ "reflect-metadata": "^0.2.2"
76
71
  },
77
72
  "devDependencies": {
78
73
  "@babel/core": "^7.24.0",
@@ -102,7 +97,12 @@
102
97
  "fast-json-patch": "^3.1.0",
103
98
  "mime-types": "^2.1.34",
104
99
  "node-gzip": "^1.1.2",
105
- "ws": "^8.18.3"
100
+ "ws": "^8.18.3",
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) {
@@ -326,23 +333,37 @@ export class SimpleBootHttpServer extends SimpleApplication {
326
333
  여기에서 정해진 타입 리턴에 따른 조치
327
334
  ...
328
335
  */
329
- if (it.config?.resolver) {
330
- const execute = typeof it.config.resolver === 'function' ? this.simstanceManager.getOrNewSim({target: it.config.resolver}) : it.config.resolver;
331
- data = await execute?.resolve?.(data, rr);
332
- }
333
- const status = it.config?.res?.status ?? HttpStatus.Ok;
334
- const headers = it.config?.res?.header ?? {};
335
- if (it.config?.res?.contentType) {
336
- headers[HttpHeaders.ContentType] = it.config?.res?.contentType;
337
- }
338
- if ((it.config?.res?.contentType?.toLowerCase().indexOf(Mimes.ApplicationJson.toLowerCase()) ?? -1) > -1) {
339
- data = JSON.stringify(data);
340
- } else if (data && typeof data === 'object') {
341
- data = JSON.stringify(data);
336
+ const resConfig = it.config?.res;
337
+ if (resConfig === 'manual') {
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);
348
+ } else {
349
+ if (it.config?.resolver) {
350
+ const execute = typeof it.config.resolver === 'function' ? this.simstanceManager.getOrNewSim({target: it.config.resolver}) : it.config.resolver;
351
+ data = await execute?.resolve?.(data, rr, resConfig);
352
+ }
353
+ const status = resConfig?.status ?? HttpStatus.Ok;
354
+ const headers = resConfig?.header ?? {};
355
+ if (resConfig?.contentType) {
356
+ headers[HttpHeaders.ContentType] = resConfig.contentType;
357
+ }
358
+ if ((resConfig?.contentType?.toLowerCase().indexOf(Mimes.ApplicationJson.toLowerCase()) ?? -1) > -1) {
359
+ data = JSON.stringify(data);
360
+ } else if (data && typeof data === 'object') {
361
+ data = JSON.stringify(data);
362
+ }
363
+ rr.resSetHeaders(headers)
364
+ rr.resSetStatusCode(status);
365
+ rr.resWrite(data);
342
366
  }
343
- rr.resSetHeaders(headers)
344
- rr.resSetStatusCode(status);
345
- rr.resWrite(data);
346
367
  }
347
368
  }
348
369
 
@@ -24,6 +24,8 @@ export enum Mimes {
24
24
  ImageXpixmap = 'image/x-xpixmap',
25
25
  ImageXxbm = 'image/x-xbm',
26
26
  MultipartFormData = 'multipart/form-data',
27
+ TextEventStream = 'text/event-stream', // SSE 실시간 텍스트 스트리밍
28
+ ApplicationXndjson = 'application/x-ndjson',
27
29
  ApplicationJsonPostSimpleBootSsrIntentScheme = 'application/json-post+simple-boot-ssr-intent-scheme',
28
30
  Intent = 'intent'
29
31
  }
@@ -6,234 +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
- status?: number;
29
- header?: {[key: string]: string};
30
- contentType?: Mimes | string;
31
- }
32
- resolver?: Resolver|ConstructorType<Resolver>;
33
- }
34
- 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 };
35
34
  const MappingMetadataKey = Symbol.for('simple-boot-http-server:mapping-metadata');
36
35
  // const GETSMappingMetadataKey = Symbol('GET_METHODS');
37
36
  // const GETMappingMetadataKey = Symbol('GET_METHOD');
38
37
 
39
38
  const process = (config: MappingConfig, target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
40
- const saveMappingConfigs = (ReflectUtils.getMetadata(MappingMetadataKey, target.constructor) ?? []) as SaveMappingConfig[];
41
- saveMappingConfigs.push({propertyKey, config});
42
- ReflectUtils.defineMetadata(MappingMetadataKey, saveMappingConfigs, target.constructor);
43
- ReflectUtils.defineMetadata(MappingMetadataKey, config, target, propertyKey);
44
- }
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
+ };
45
44
  export function UrlMapping(config: MappingConfig): ReflectMethod;
46
45
  export function UrlMapping(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
47
46
  export function UrlMapping(configOrtarget?: MappingConfig | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
48
- if (propertyKey && descriptor) {
49
- process({method: HttpMethod.GET}, configOrtarget, propertyKey, descriptor);
50
- } else {
51
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
52
- process(configOrtarget, target, propertyKey, descriptor);
53
- }
54
- }
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
+ }
55
54
  }
56
55
  export const getUrlMapping = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
57
- return ReflectUtils.getMetadata(MappingMetadataKey, target, propertyKey);
58
- }
56
+ return ReflectUtils.getMetadata(MappingMetadataKey, target, propertyKey);
57
+ };
59
58
 
60
59
  export const getUrlMappings = (target: any): SaveMappingConfig[] => {
61
- if (target !== null && undefined !== target && typeof target === 'object') {
62
- target = target.constructor;
63
- }
64
- return ReflectUtils.getMetadata(MappingMetadataKey, target);
65
- }
60
+ if (target !== null && undefined !== target && typeof target === 'object') {
61
+ target = target.constructor;
62
+ }
63
+ return ReflectUtils.getMetadata(MappingMetadataKey, target);
64
+ };
66
65
  export function GET(config: Omit<MappingConfig, 'method'>): ReflectMethod;
67
66
  export function GET(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
68
67
  export function GET(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
69
- if (propertyKey && descriptor) {
70
- process({method: HttpMethod.GET}, configOrTarget, propertyKey, descriptor);
71
- } else {
72
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
73
- configOrTarget.method = HttpMethod.GET;
74
- process(configOrTarget, target, propertyKey, descriptor);
75
- }
76
- }
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
+ }
77
76
  }
78
77
 
79
78
  export const getGET = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
80
- return getUrlMapping(target, propertyKey);
81
- }
79
+ return getUrlMapping(target, propertyKey);
80
+ };
82
81
  export const getGETS = (target: any): SaveMappingConfig[] => {
83
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.GET);
84
- }
82
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.GET);
83
+ };
85
84
  export function POST(config: Omit<MappingConfig, 'method'>): ReflectMethod;
86
85
  export function POST(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
87
86
  export function POST(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
88
- if (propertyKey && descriptor) {
89
- process({method: HttpMethod.POST}, configOrTarget, propertyKey, descriptor);
90
- } else {
91
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
92
- configOrTarget.method = HttpMethod.POST;
93
- process(configOrTarget, target, propertyKey, descriptor);
94
- }
95
- }
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
+ }
96
95
  }
97
96
 
98
97
  export const getPOST = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
99
- return getUrlMapping(target, propertyKey);
100
- }
98
+ return getUrlMapping(target, propertyKey);
99
+ };
101
100
  export const getPOSTS = (target: any): SaveMappingConfig[] => {
102
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.POST);
103
- }
101
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.POST);
102
+ };
104
103
  export function DELETE(config: Omit<MappingConfig, 'method'>): ReflectMethod;
105
104
  export function DELETE(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
106
105
  export function DELETE(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
107
- if (propertyKey && descriptor) {
108
- process({method: HttpMethod.DELETE}, configOrTarget, propertyKey, descriptor);
109
- } else {
110
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
111
- configOrTarget.method = HttpMethod.DELETE;
112
- process(configOrTarget, target, propertyKey, descriptor);
113
- }
114
- }
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
+ }
115
114
  }
116
115
 
117
116
  export const getDELETE = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
118
- return getUrlMapping(target, propertyKey);
119
- }
117
+ return getUrlMapping(target, propertyKey);
118
+ };
120
119
  export const getDELETES = (target: any): SaveMappingConfig[] => {
121
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.DELETE);
122
- }
120
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.DELETE);
121
+ };
123
122
  export function PUT(config: Omit<MappingConfig, 'method'>): ReflectMethod;
124
123
  export function PUT(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
125
124
  export function PUT(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
126
- if (propertyKey && descriptor) {
127
- process({method: HttpMethod.PUT}, configOrTarget, propertyKey, descriptor);
128
- } else {
129
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
130
- configOrTarget.method = HttpMethod.PUT;
131
- process(configOrTarget, target, propertyKey, descriptor);
132
- }
133
- }
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
+ }
134
133
  }
135
134
 
136
135
  export const getPUT = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
137
- return getUrlMapping(target, propertyKey);
138
- }
136
+ return getUrlMapping(target, propertyKey);
137
+ };
139
138
  export const getPUTS = (target: any): SaveMappingConfig[] => {
140
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PUT);
141
- }
139
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PUT);
140
+ };
142
141
  export function PATCH(config: Omit<MappingConfig, 'method'>): ReflectMethod;
143
142
  export function PATCH(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
144
143
  export function PATCH(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
145
- if (propertyKey && descriptor) {
146
- process({method: HttpMethod.PATCH}, configOrTarget, propertyKey, descriptor);
147
- } else {
148
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
149
- configOrTarget.method = HttpMethod.PATCH;
150
- process(configOrTarget, target, propertyKey, descriptor);
151
- }
152
- }
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
+ }
153
152
  }
154
153
 
155
154
  export const getPATCH = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
156
- return getUrlMapping(target, propertyKey);
157
- }
155
+ return getUrlMapping(target, propertyKey);
156
+ };
158
157
  export const getPATCHS = (target: any): SaveMappingConfig[] => {
159
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.GET);
160
- }
158
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PATCH);
159
+ };
161
160
  export function OPTIONS(config: Omit<MappingConfig, 'method'>): ReflectMethod;
162
161
  export function OPTIONS(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
163
162
  export function OPTIONS(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
164
- if (propertyKey && descriptor) {
165
- process({method: HttpMethod.OPTIONS}, configOrTarget, propertyKey, descriptor);
166
- } else {
167
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
168
- configOrTarget.method = HttpMethod.OPTIONS;
169
- process(configOrTarget, target, propertyKey, descriptor);
170
- }
171
- }
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
+ }
172
171
  }
173
172
 
174
173
  export const getOPTIONS = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
175
- return getUrlMapping(target, propertyKey);
176
- }
174
+ return getUrlMapping(target, propertyKey);
175
+ };
177
176
  export const getOPTIONSS = (target: any): SaveMappingConfig[] => {
178
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.OPTIONS);
179
- }
177
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.OPTIONS);
178
+ };
180
179
  export function HEAD(config: Omit<MappingConfig, 'method'>): ReflectMethod;
181
180
  export function HEAD(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
182
181
  export function HEAD(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
183
- if (propertyKey && descriptor) {
184
- process({method: HttpMethod.HEAD}, configOrTarget, propertyKey, descriptor);
185
- } else {
186
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
187
- configOrTarget.method = HttpMethod.HEAD;
188
- process(configOrTarget, target, propertyKey, descriptor);
189
- }
190
- }
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
+ }
191
190
  }
192
191
 
193
192
  export const getHEAD = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
194
- return getUrlMapping(target, propertyKey);
195
- }
193
+ return getUrlMapping(target, propertyKey);
194
+ };
196
195
  export const getHEADS = (target: any): SaveMappingConfig[] => {
197
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.GET);
198
- }
196
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.HEAD);
197
+ };
199
198
  export function TRACE(config: Omit<MappingConfig, 'method'>): ReflectMethod;
200
199
  export function TRACE(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
201
200
  export function TRACE(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
202
- if (propertyKey && descriptor) {
203
- process({method: HttpMethod.TRACE}, configOrTarget, propertyKey, descriptor);
204
- } else {
205
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
206
- configOrTarget.method = HttpMethod.TRACE;
207
- process(configOrTarget, target, propertyKey, descriptor);
208
- }
209
- }
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
+ }
210
209
  }
211
210
 
212
211
  export const getTRACE = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
213
- return getUrlMapping(target, propertyKey);
214
- }
212
+ return getUrlMapping(target, propertyKey);
213
+ };
215
214
  export const getTRACES = (target: any): SaveMappingConfig[] => {
216
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.TRACE);
217
- }
215
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.TRACE);
216
+ };
218
217
  export function CONNECT(config: Omit<MappingConfig, 'method'>): ReflectMethod;
219
218
  export function CONNECT(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
220
219
  export function CONNECT(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
221
- if (propertyKey && descriptor) {
222
- process({method: HttpMethod.CONNECT}, configOrTarget, propertyKey, descriptor);
223
- } else {
224
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
225
- configOrTarget.method = HttpMethod.CONNECT;
226
- process(configOrTarget, target, propertyKey, descriptor);
227
- }
228
- }
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
+ }
229
228
  }
230
229
 
231
230
  export const getCONNECT = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
232
- return getUrlMapping(target, propertyKey);
233
- }
231
+ return getUrlMapping(target, propertyKey);
232
+ };
234
233
  export const getCONNECTS = (target: any): SaveMappingConfig[] => {
235
- return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.CONNECT);
236
- }
234
+ return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.CONNECT);
235
+ };
237
236
 
238
237
  // Aliases for lowercase
239
238
  export const urlMapping = UrlMapping;
@@ -54,7 +54,6 @@ export class ResourceFilter implements Filter {
54
54
  }
55
55
 
56
56
  if (typeof it === 'string') {
57
- console.log('url!!!!!!!', url)
58
57
  return it === url;
59
58
  // return RegExp(it).test(url);
60
59
  }
@@ -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';