@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.
- package/README.md +44 -9
- package/dist/cjs/SimpleBootHttpServer.js +15 -2
- package/dist/cjs/SimpleBootHttpServer.js.map +2 -2
- package/dist/cjs/decorators/MethodMapping.js.map +2 -2
- package/dist/cjs/models/RequestResponse.js +52 -0
- package/dist/cjs/models/RequestResponse.js.map +3 -3
- package/dist/cjs/proxy/RequestResponseInjectProxy.js +35 -0
- package/dist/cjs/proxy/RequestResponseInjectProxy.js.map +7 -0
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +1 -1
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
- package/dist/cjs/proxy/index.js +3 -1
- package/dist/cjs/proxy/index.js.map +2 -2
- package/dist/cjs/resolvers/Resolver.js.map +1 -1
- package/dist/esm/SimpleBootHttpServer.js +16 -3
- package/dist/esm/SimpleBootHttpServer.js.map +2 -2
- package/dist/esm/decorators/MethodMapping.js.map +2 -2
- package/dist/esm/models/RequestResponse.js +52 -0
- package/dist/esm/models/RequestResponse.js.map +2 -2
- package/dist/esm/proxy/RequestResponseInjectProxy.js +12 -0
- package/dist/esm/proxy/RequestResponseInjectProxy.js.map +7 -0
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +1 -1
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
- package/dist/esm/proxy/index.js +1 -0
- package/dist/esm/proxy/index.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +295 -252
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +4 -4
- package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
- package/dist/types/decorators/MethodMapping.d.ts +13 -16
- package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
- package/dist/types/models/RequestResponse.d.ts +9 -0
- package/dist/types/models/RequestResponse.d.ts.map +1 -1
- package/dist/types/proxy/RequestResponseInjectProxy.d.ts +22 -0
- package/dist/types/proxy/RequestResponseInjectProxy.d.ts.map +1 -0
- package/dist/types/proxy/index.d.ts +1 -0
- package/dist/types/proxy/index.d.ts.map +1 -1
- package/dist/types/resolvers/Resolver.d.ts +2 -1
- package/dist/types/resolvers/Resolver.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +295 -252
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
- package/dist/umd-bundle/websocket-client.umd.js +106 -140
- package/dist/umd-bundle/websocket-client.umd.js.map +2 -2
- package/package.json +7 -7
- package/src/SimpleBootHttpServer.ts +19 -3
- package/src/decorators/MethodMapping.ts +151 -159
- package/src/models/RequestResponse.ts +50 -0
- package/src/proxy/RequestResponseInjectProxy.ts +30 -0
- package/src/proxy/SymbolIntentApiServiceProxy.ts +1 -1
- package/src/proxy/index.ts +1 -0
- 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.
|
|
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.
|
|
102
|
-
"@dooboostore/core-node": "1.0.
|
|
103
|
-
"@dooboostore/
|
|
104
|
-
"@dooboostore/
|
|
105
|
-
"@dooboostore/
|
|
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": "
|
|
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
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
export type
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
65
|
-
}
|
|
56
|
+
return ReflectUtils.getMetadata(MappingMetadataKey, target, propertyKey);
|
|
57
|
+
};
|
|
66
58
|
|
|
67
59
|
export const getUrlMappings = (target: any): SaveMappingConfig[] => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
88
|
-
}
|
|
79
|
+
return getUrlMapping(target, propertyKey);
|
|
80
|
+
};
|
|
89
81
|
export const getGETS = (target: any): SaveMappingConfig[] => {
|
|
90
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
107
|
-
}
|
|
98
|
+
return getUrlMapping(target, propertyKey);
|
|
99
|
+
};
|
|
108
100
|
export const getPOSTS = (target: any): SaveMappingConfig[] => {
|
|
109
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
126
|
-
}
|
|
117
|
+
return getUrlMapping(target, propertyKey);
|
|
118
|
+
};
|
|
127
119
|
export const getDELETES = (target: any): SaveMappingConfig[] => {
|
|
128
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
145
|
-
}
|
|
136
|
+
return getUrlMapping(target, propertyKey);
|
|
137
|
+
};
|
|
146
138
|
export const getPUTS = (target: any): SaveMappingConfig[] => {
|
|
147
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
164
|
-
}
|
|
155
|
+
return getUrlMapping(target, propertyKey);
|
|
156
|
+
};
|
|
165
157
|
export const getPATCHS = (target: any): SaveMappingConfig[] => {
|
|
166
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
183
|
-
}
|
|
174
|
+
return getUrlMapping(target, propertyKey);
|
|
175
|
+
};
|
|
184
176
|
export const getOPTIONSS = (target: any): SaveMappingConfig[] => {
|
|
185
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
202
|
-
}
|
|
193
|
+
return getUrlMapping(target, propertyKey);
|
|
194
|
+
};
|
|
203
195
|
export const getHEADS = (target: any): SaveMappingConfig[] => {
|
|
204
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
221
|
-
}
|
|
212
|
+
return getUrlMapping(target, propertyKey);
|
|
213
|
+
};
|
|
222
214
|
export const getTRACES = (target: any): SaveMappingConfig[] => {
|
|
223
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
240
|
-
}
|
|
231
|
+
return getUrlMapping(target, propertyKey);
|
|
232
|
+
};
|
|
241
233
|
export const getCONNECTS = (target: any): SaveMappingConfig[] => {
|
|
242
|
-
|
|
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
|
|
43
|
+
body: userConfig?.body ? ConvertUtils.toFormData(userConfig.body) : undefined
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
});
|
package/src/proxy/index.ts
CHANGED
|
@@ -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
|
-
|
|
5
|
+
resolve(data: T, rr: RequestResponse, responseMappingConfig: ResponseMappingConfig): Promise<void>;
|
|
5
6
|
}
|