@dooboostore/simple-boot-http-server 1.0.24 → 1.0.26

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 (190) hide show
  1. package/README.md +113 -0
  2. package/dist/cjs/SimpleBootHttpServer.js +16 -4
  3. package/dist/cjs/SimpleBootHttpServer.js.map +2 -2
  4. package/dist/cjs/bundle-entry.js +34 -24
  5. package/dist/cjs/bundle-entry.js.map +2 -2
  6. package/dist/cjs/decorators/MethodMapping.js +33 -3
  7. package/dist/cjs/decorators/MethodMapping.js.map +2 -2
  8. package/dist/cjs/errors/BadRequestError.js +2 -1
  9. package/dist/cjs/errors/BadRequestError.js.map +2 -2
  10. package/dist/cjs/errors/ForbiddenError.js +2 -1
  11. package/dist/cjs/errors/ForbiddenError.js.map +2 -2
  12. package/dist/cjs/errors/InternalServerError.js +2 -1
  13. package/dist/cjs/errors/InternalServerError.js.map +2 -2
  14. package/dist/cjs/errors/NotFoundError.js +2 -1
  15. package/dist/cjs/errors/NotFoundError.js.map +2 -2
  16. package/dist/cjs/errors/UnauthorizedError.js +2 -1
  17. package/dist/cjs/errors/UnauthorizedError.js.map +2 -2
  18. package/dist/cjs/filters/IntentSchemeFilter.js +1 -2
  19. package/dist/cjs/filters/IntentSchemeFilter.js.map +2 -2
  20. package/dist/cjs/filters/ResourceFilter.js +3 -2
  21. package/dist/cjs/filters/ResourceFilter.js.map +2 -2
  22. package/dist/cjs/filters/index.js +3 -1
  23. package/dist/cjs/filters/index.js.map +2 -2
  24. package/dist/cjs/index.js +2 -0
  25. package/dist/cjs/index.js.map +2 -2
  26. package/dist/cjs/inject/InjectSituationType.js +1 -1
  27. package/dist/cjs/inject/InjectSituationType.js.map +2 -2
  28. package/dist/cjs/models/RequestResponse.js +19 -2
  29. package/dist/cjs/models/RequestResponse.js.map +2 -2
  30. package/dist/cjs/models/datas/body/index.js +28 -0
  31. package/dist/cjs/models/datas/body/index.js.map +7 -0
  32. package/dist/cjs/models/datas/index.js +32 -0
  33. package/dist/cjs/models/datas/index.js.map +7 -0
  34. package/dist/cjs/models/datas/{body → path}/ReqPathData.js.map +1 -1
  35. package/dist/cjs/models/datas/{search → path}/ReqSearchParamsObj.js.map +1 -1
  36. package/dist/cjs/models/datas/path/index.js +24 -0
  37. package/dist/cjs/models/datas/path/index.js.map +7 -0
  38. package/dist/cjs/models/index.js +4 -16
  39. package/dist/cjs/models/index.js.map +2 -2
  40. package/dist/cjs/models/method/index.js +22 -0
  41. package/dist/cjs/models/method/index.js.map +7 -0
  42. package/dist/cjs/models/method/patch/index.js +22 -0
  43. package/dist/cjs/models/method/patch/index.js.map +7 -0
  44. package/dist/cjs/option/HttpServerOption.js.map +2 -2
  45. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +98 -95
  46. package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +3 -3
  47. package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js +1 -1
  48. package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js.map +2 -2
  49. package/dist/cjs/proxy/TopicProtocolApiServiceProxy.js +244 -266
  50. package/dist/cjs/proxy/TopicProtocolApiServiceProxy.js.map +3 -3
  51. package/dist/cjs/websocket/WebSocketClient.js +2 -1
  52. package/dist/cjs/websocket/WebSocketClient.js.map +2 -2
  53. package/dist/esm/SimpleBootHttpServer.js +16 -4
  54. package/dist/esm/SimpleBootHttpServer.js.map +2 -2
  55. package/dist/esm/bundle-entry.js +14 -12
  56. package/dist/esm/bundle-entry.js.map +2 -2
  57. package/dist/esm/decorators/MethodMapping.js +22 -2
  58. package/dist/esm/decorators/MethodMapping.js.map +2 -2
  59. package/dist/esm/errors/BadRequestError.js +2 -1
  60. package/dist/esm/errors/BadRequestError.js.map +2 -2
  61. package/dist/esm/errors/ForbiddenError.js +2 -1
  62. package/dist/esm/errors/ForbiddenError.js.map +2 -2
  63. package/dist/esm/errors/InternalServerError.js +2 -1
  64. package/dist/esm/errors/InternalServerError.js.map +2 -2
  65. package/dist/esm/errors/NotFoundError.js +2 -1
  66. package/dist/esm/errors/NotFoundError.js.map +2 -2
  67. package/dist/esm/errors/UnauthorizedError.js +2 -1
  68. package/dist/esm/errors/UnauthorizedError.js.map +2 -2
  69. package/dist/esm/filters/IntentSchemeFilter.js +2 -3
  70. package/dist/esm/filters/IntentSchemeFilter.js.map +2 -2
  71. package/dist/esm/filters/ResourceFilter.js +3 -2
  72. package/dist/esm/filters/ResourceFilter.js.map +2 -2
  73. package/dist/esm/filters/index.js +1 -0
  74. package/dist/esm/filters/index.js.map +2 -2
  75. package/dist/esm/index.js +1 -0
  76. package/dist/esm/index.js.map +2 -2
  77. package/dist/esm/inject/InjectSituationType.js +1 -1
  78. package/dist/esm/inject/InjectSituationType.js.map +2 -2
  79. package/dist/esm/models/RequestResponse.js +19 -2
  80. package/dist/esm/models/RequestResponse.js.map +2 -2
  81. package/dist/esm/models/datas/body/index.js +5 -0
  82. package/dist/esm/models/datas/body/index.js.map +7 -0
  83. package/dist/esm/models/datas/index.js +7 -0
  84. package/dist/esm/models/datas/index.js.map +7 -0
  85. package/dist/esm/models/datas/{body → path}/ReqPathData.js.map +1 -1
  86. package/dist/esm/models/datas/{search → path}/ReqSearchParamsObj.js.map +1 -1
  87. package/dist/esm/models/datas/path/index.js +3 -0
  88. package/dist/esm/models/datas/path/index.js.map +7 -0
  89. package/dist/esm/models/index.js +2 -8
  90. package/dist/esm/models/index.js.map +2 -2
  91. package/dist/esm/models/method/index.js +2 -0
  92. package/dist/esm/models/method/index.js.map +7 -0
  93. package/dist/esm/models/method/patch/index.js +2 -0
  94. package/dist/esm/models/method/patch/index.js.map +7 -0
  95. package/dist/esm/option/HttpServerOption.js.map +2 -2
  96. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +98 -95
  97. package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +3 -3
  98. package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js +2 -2
  99. package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js.map +2 -2
  100. package/dist/esm/proxy/TopicProtocolApiServiceProxy.js +244 -266
  101. package/dist/esm/proxy/TopicProtocolApiServiceProxy.js.map +3 -3
  102. package/dist/esm/websocket/WebSocketClient.js +3 -2
  103. package/dist/esm/websocket/WebSocketClient.js.map +2 -2
  104. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +19374 -4956
  105. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +4 -4
  106. package/dist/types/SimpleBootHttpServer.d.ts +1 -1
  107. package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
  108. package/dist/types/bundle-entry.d.ts +7 -12
  109. package/dist/types/bundle-entry.d.ts.map +1 -1
  110. package/dist/types/decorators/MethodMapping.d.ts +10 -0
  111. package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
  112. package/dist/types/errors/BadRequestError.d.ts +2 -2
  113. package/dist/types/errors/BadRequestError.d.ts.map +1 -1
  114. package/dist/types/errors/ForbiddenError.d.ts +2 -2
  115. package/dist/types/errors/ForbiddenError.d.ts.map +1 -1
  116. package/dist/types/errors/InternalServerError.d.ts +2 -2
  117. package/dist/types/errors/InternalServerError.d.ts.map +1 -1
  118. package/dist/types/errors/NotFoundError.d.ts +2 -2
  119. package/dist/types/errors/NotFoundError.d.ts.map +1 -1
  120. package/dist/types/errors/UnauthorizedError.d.ts +2 -2
  121. package/dist/types/errors/UnauthorizedError.d.ts.map +1 -1
  122. package/dist/types/filters/ResourceFilter.d.ts.map +1 -1
  123. package/dist/types/filters/index.d.ts +1 -0
  124. package/dist/types/filters/index.d.ts.map +1 -1
  125. package/dist/types/index.d.ts +1 -0
  126. package/dist/types/index.d.ts.map +1 -1
  127. package/dist/types/inject/InjectSituationType.d.ts.map +1 -1
  128. package/dist/types/models/RequestResponse.d.ts +3 -1
  129. package/dist/types/models/RequestResponse.d.ts.map +1 -1
  130. package/dist/types/models/datas/body/index.d.ts +5 -0
  131. package/dist/types/models/datas/body/index.d.ts.map +1 -0
  132. package/dist/types/models/datas/index.d.ts +7 -0
  133. package/dist/types/models/datas/index.d.ts.map +1 -0
  134. package/dist/types/models/datas/{body → path}/ReqPathData.d.ts.map +1 -1
  135. package/dist/types/models/datas/path/ReqSearchParamsObj.d.ts.map +1 -0
  136. package/dist/types/models/datas/path/index.d.ts +3 -0
  137. package/dist/types/models/datas/path/index.d.ts.map +1 -0
  138. package/dist/types/models/index.d.ts +2 -8
  139. package/dist/types/models/index.d.ts.map +1 -1
  140. package/dist/types/models/method/index.d.ts +2 -0
  141. package/dist/types/models/method/index.d.ts.map +1 -0
  142. package/dist/types/models/method/patch/index.d.ts +2 -0
  143. package/dist/types/models/method/patch/index.d.ts.map +1 -0
  144. package/dist/types/option/HttpServerOption.d.ts +6 -2
  145. package/dist/types/option/HttpServerOption.d.ts.map +1 -1
  146. package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts +6 -8
  147. package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts.map +1 -1
  148. package/dist/types/proxy/TopicProtocolApiServiceProxy.d.ts +5 -15
  149. package/dist/types/proxy/TopicProtocolApiServiceProxy.d.ts.map +1 -1
  150. package/dist/types/websocket/WebSocketClient.d.ts.map +1 -1
  151. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +19340 -4928
  152. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
  153. package/dist/umd-bundle/websocket-client.umd.js +378 -1468
  154. package/dist/umd-bundle/websocket-client.umd.js.map +4 -4
  155. package/package.json +8 -8
  156. package/src/SimpleBootHttpServer.ts +20 -6
  157. package/src/bundle-entry.ts +12 -12
  158. package/src/decorators/MethodMapping.ts +13 -1
  159. package/src/errors/BadRequestError.ts +7 -3
  160. package/src/errors/ForbiddenError.ts +7 -3
  161. package/src/errors/InternalServerError.ts +7 -3
  162. package/src/errors/NotFoundError.ts +7 -3
  163. package/src/errors/UnauthorizedError.ts +7 -3
  164. package/src/filters/IntentSchemeFilter.ts +2 -2
  165. package/src/filters/ResourceFilter.ts +5 -2
  166. package/src/filters/index.ts +1 -0
  167. package/src/index.ts +1 -0
  168. package/src/inject/InjectSituationType.ts +1 -1
  169. package/src/models/RequestResponse.ts +28 -2
  170. package/src/models/datas/body/index.ts +4 -0
  171. package/src/models/datas/index.ts +6 -0
  172. package/src/models/datas/path/index.ts +2 -0
  173. package/src/models/index.ts +2 -8
  174. package/src/models/method/index.ts +1 -0
  175. package/src/models/method/patch/index.ts +1 -0
  176. package/src/option/HttpServerOption.ts +4 -2
  177. package/src/proxy/SymbolIntentApiServiceProxy.ts +103 -97
  178. package/src/proxy/SymbolIntentApiServiceProxyBase.ts +2 -2
  179. package/src/proxy/TopicProtocolApiServiceProxy.ts +260 -275
  180. package/src/websocket/WebSocketClient.ts +3 -2
  181. package/README.MD +0 -62
  182. package/dist/types/models/datas/search/ReqSearchParamsObj.d.ts.map +0 -1
  183. /package/dist/cjs/models/datas/{body → path}/ReqPathData.js +0 -0
  184. /package/dist/cjs/models/datas/{search → path}/ReqSearchParamsObj.js +0 -0
  185. /package/dist/esm/models/datas/{body → path}/ReqPathData.js +0 -0
  186. /package/dist/esm/models/datas/{search → path}/ReqSearchParamsObj.js +0 -0
  187. /package/dist/types/models/datas/{body → path}/ReqPathData.d.ts +0 -0
  188. /package/dist/types/models/datas/{search → path}/ReqSearchParamsObj.d.ts +0 -0
  189. /package/src/models/datas/{body → path}/ReqPathData.ts +0 -0
  190. /package/src/models/datas/{search → path}/ReqSearchParamsObj.ts +0 -0
@@ -1,7 +1,7 @@
1
- import { getSim, Sim } from '@dooboostore/simple-boot';
1
+ import { getSim, sim, SimConfig } from '@dooboostore/simple-boot';
2
2
  import { HttpHeaders } from '../codes/HttpHeaders';
3
3
  import { ApiService } from '@dooboostore/simple-boot';
4
- import { RandomUtils } from '@dooboostore/core';
4
+ import { ConstructorType, RandomUtils } from '@dooboostore/core';
5
5
  import { HttpFetcherTarget } from '@dooboostore/core';
6
6
 
7
7
  export type TopicProtocolConfig<TBody = unknown> = {
@@ -26,303 +26,288 @@ type TopicProtocolRequest<TBody = unknown> = {
26
26
  type FileEntry = { id: string; file: Blob; filename?: string };
27
27
  type MixedPart = { headers: Record<string, string>; body: Uint8Array };
28
28
 
29
- @Sim
30
- export class TopicProtocolApiServiceProxy<T extends object> implements ProxyHandler<T> {
31
- constructor(private apiService: ApiService) {}
29
+ // ── Helpers (module-level, mirrors SymbolIntentApiServiceProxy pattern) ──────
32
30
 
33
- private static isFileValue(value: unknown): value is File | Blob | ArrayBuffer | Uint8Array {
34
- if (typeof File !== 'undefined' && value instanceof File) {
35
- return true;
36
- }
37
- if (typeof Blob !== 'undefined' && value instanceof Blob) {
38
- return true;
39
- }
40
- if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
41
- return true;
42
- }
43
- if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
44
- return true;
45
- }
46
- return false;
47
- }
31
+ const isFileValue = (value: unknown): value is File | Blob | ArrayBuffer | Uint8Array => {
32
+ if (typeof File !== 'undefined' && value instanceof File) return true;
33
+ if (typeof Blob !== 'undefined' && value instanceof Blob) return true;
34
+ if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) return true;
35
+ if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) return true;
36
+ return false;
37
+ };
48
38
 
49
- private static toFileEntry(value: File | Blob | ArrayBuffer | Uint8Array, id: string): FileEntry | undefined {
50
- if (typeof Blob === 'undefined') {
51
- return undefined;
52
- }
53
- if (typeof File !== 'undefined' && value instanceof File) {
54
- const blob = new Blob([value], { type: value.type });
55
- return { id, file: blob, filename: value.name };
56
- }
57
- if (value instanceof Blob) {
58
- return { id, file: value };
59
- }
60
- const view = value instanceof Uint8Array ? value : new Uint8Array(value);
61
- const safe = value instanceof Uint8Array ? new Uint8Array(value) : new Uint8Array(view);
62
- const blob = new Blob([safe]);
63
- return { id, file: blob };
39
+ const toFileEntry = (value: File | Blob | ArrayBuffer | Uint8Array, id: string): FileEntry | undefined => {
40
+ if (typeof Blob === 'undefined') return undefined;
41
+ if (typeof File !== 'undefined' && value instanceof File) {
42
+ return { id, file: new Blob([value], { type: value.type }), filename: value.name };
64
43
  }
44
+ if (value instanceof Blob) return { id, file: value };
45
+ const safe = value instanceof Uint8Array ? new Uint8Array(value) : new Uint8Array(value);
46
+ return { id, file: new Blob([safe]) };
47
+ };
65
48
 
66
- private static buildRequest<TBody>(target: string, body?: TBody, type: TopicProtocolType = 'intent') {
67
- const fileEntries: FileEntry[] = [];
68
- let fileIndex = 0;
49
+ const buildRequest = <TBody>(target: string, body?: TBody, type: TopicProtocolType = 'intent') => {
50
+ const fileEntries: FileEntry[] = [];
51
+ let fileIndex = 0;
69
52
 
70
- const registerFile = (value: File | Blob | ArrayBuffer | Uint8Array): string | undefined => {
71
- const id = `f${++fileIndex}`;
72
- const entry = TopicProtocolApiServiceProxy.toFileEntry(value, id);
73
- if (entry) {
74
- fileEntries.push(entry);
75
- return id;
76
- }
77
- return undefined;
78
- };
79
-
80
- const replaceFiles = (value: unknown): unknown => {
81
- if (TopicProtocolApiServiceProxy.isFileValue(value)) {
82
- const id = registerFile(value);
83
- if (id) {
84
- return { $file: id };
85
- }
86
- return value;
87
- }
88
- if (Array.isArray(value)) {
89
- return value.map(item => replaceFiles(item));
90
- }
91
- if (value && typeof value === 'object') {
92
- const result: Record<string, unknown> = {};
93
- Object.keys(value).forEach(key => {
94
- result[key] = replaceFiles((value as Record<string, unknown>)[key]);
95
- });
96
- return result;
97
- }
98
- return value;
99
- };
100
-
101
- const payload = replaceFiles(body);
102
- const request: TopicProtocolRequest<TBody> = {
103
- type,
104
- target,
105
- requestUUID: RandomUtils.uuid4(),
106
- body: payload as TBody,
107
- files: fileEntries.length > 0 ? fileEntries.map(entry => ({ id: entry.id })) : undefined
108
- };
109
-
110
- return { request, fileEntries };
111
- }
53
+ const registerFile = (value: File | Blob | ArrayBuffer | Uint8Array): string | undefined => {
54
+ const id = `f${++fileIndex}`;
55
+ const entry = toFileEntry(value, id);
56
+ if (entry) { fileEntries.push(entry); return id; }
57
+ return undefined;
58
+ };
112
59
 
113
- private static async parseMultipartMixed(response: Response) {
114
- const contentType = response.headers.get('Content-Type') ?? '';
115
- const boundaryMatch = contentType.match(/boundary=([^;]+)/i);
116
- if (!boundaryMatch) {
117
- return undefined;
60
+ const replaceFiles = (value: unknown): unknown => {
61
+ if (isFileValue(value)) {
62
+ const id = registerFile(value);
63
+ return id ? { $file: id } : value;
118
64
  }
119
- const boundary = boundaryMatch[1].replace(/^"|"$/g, '');
120
- const buffer = new Uint8Array(await response.arrayBuffer());
121
- const boundaryBytes = new TextEncoder().encode(`--${boundary}`);
122
- const endBoundaryBytes = new TextEncoder().encode(`--${boundary}--`);
123
- const separator = new TextEncoder().encode('\r\n\r\n');
124
-
125
- const indexOf = (source: Uint8Array, needle: Uint8Array, from = 0) => {
126
- for (let i = from; i <= source.length - needle.length; i++) {
127
- let matched = true;
128
- for (let j = 0; j < needle.length; j++) {
129
- if (source[i + j] !== needle[j]) {
130
- matched = false;
131
- break;
132
- }
133
- }
134
- if (matched) {
135
- return i;
136
- }
137
- }
138
- return -1;
139
- };
140
-
141
- const parts: MixedPart[] = [];
142
- let cursor = indexOf(buffer, boundaryBytes, 0);
143
- while (cursor !== -1) {
144
- const nextBoundary = indexOf(buffer, boundaryBytes, cursor + boundaryBytes.length);
145
- const endBoundary = indexOf(buffer, endBoundaryBytes, cursor + boundaryBytes.length);
146
- const isFinal = endBoundary !== -1 && (endBoundary < nextBoundary || nextBoundary === -1);
147
- const partStart = cursor + boundaryBytes.length;
148
- if (isFinal) {
149
- break;
150
- }
151
- let headerStart = partStart;
152
- if (buffer[headerStart] === 13 && buffer[headerStart + 1] === 10) {
153
- headerStart += 2;
154
- }
155
- const headerEnd = indexOf(buffer, separator, headerStart);
156
- if (headerEnd === -1) {
157
- break;
158
- }
159
- const headerText = new TextDecoder().decode(buffer.slice(headerStart, headerEnd));
160
- const headers: Record<string, string> = {};
161
- headerText.split('\r\n').forEach(line => {
162
- const idx = line.indexOf(':');
163
- if (idx > -1) {
164
- const key = line.slice(0, idx).trim().toLowerCase();
165
- headers[key] = line.slice(idx + 1).trim();
166
- }
65
+ if (Array.isArray(value)) return value.map(item => replaceFiles(item));
66
+ if (value && typeof value === 'object') {
67
+ const result: Record<string, unknown> = {};
68
+ Object.keys(value).forEach(key => {
69
+ result[key] = replaceFiles((value as Record<string, unknown>)[key]);
167
70
  });
168
- const bodyStart = headerEnd + separator.length;
169
- const bodyEnd = nextBoundary === -1 ? buffer.length : nextBoundary - 2;
170
- const body = buffer.slice(bodyStart, bodyEnd);
171
- parts.push({ headers, body });
172
- cursor = nextBoundary;
71
+ return result;
173
72
  }
73
+ return value;
74
+ };
174
75
 
175
- return { boundary, parts };
176
- }
76
+ const payload = replaceFiles(body);
77
+ const request: TopicProtocolRequest<TBody> = {
78
+ type,
79
+ target,
80
+ requestUUID: RandomUtils.uuid4(),
81
+ body: payload as TBody,
82
+ files: fileEntries.length > 0 ? fileEntries.map(e => ({ id: e.id })) : undefined,
83
+ };
177
84
 
178
- private static parseDispositionFilename(disposition?: string) {
179
- if (!disposition) {
180
- return undefined;
181
- }
182
- const match = disposition.match(/filename=\"?([^\";]+)\"?/i);
183
- return match ? match[1] : undefined;
184
- }
85
+ return { request, fileEntries };
86
+ };
185
87
 
186
- private static parseDispositionName(disposition?: string) {
187
- if (!disposition) {
188
- return undefined;
189
- }
190
- const match = disposition.match(/name=\"?([^\";]+)\"?/i);
191
- return match ? match[1] : undefined;
192
- }
88
+ const parseDispositionName = (disposition?: string) => {
89
+ const match = disposition?.match(/name=\"?([^\";]+)\"?/i);
90
+ return match ? match[1] : undefined;
91
+ };
92
+
93
+ const parseDispositionFilename = (disposition?: string) => {
94
+ const match = disposition?.match(/filename=\"?([^\";]+)\"?/i);
95
+ return match ? match[1] : undefined;
96
+ };
193
97
 
194
- private static replaceFileRefs(value: unknown, fileMap: Map<string, { name?: string; mime?: string; size: number; buffer: ArrayBuffer }>) {
195
- if (value && typeof value === 'object' && '$file' in value) {
196
- const fileKey = (value as { $file?: unknown }).$file;
197
- if (typeof fileKey === 'string') {
198
- const file = fileMap.get(fileKey);
199
- if (file) {
200
- return file;
201
- }
98
+ const parseMultipartMixed = async (response: Response) => {
99
+ const contentType = response.headers.get('Content-Type') ?? '';
100
+ const boundaryMatch = contentType.match(/boundary=([^;]+)/i);
101
+ if (!boundaryMatch) return undefined;
102
+
103
+ const boundary = boundaryMatch[1].replace(/^"|"$/g, '');
104
+ const buffer = new Uint8Array(await response.arrayBuffer());
105
+ const boundaryBytes = new TextEncoder().encode(`--${boundary}`);
106
+ const endBoundaryBytes = new TextEncoder().encode(`--${boundary}--`);
107
+ const separator = new TextEncoder().encode('\r\n\r\n');
108
+
109
+ const indexOf = (source: Uint8Array, needle: Uint8Array, from = 0) => {
110
+ for (let i = from; i <= source.length - needle.length; i++) {
111
+ let matched = true;
112
+ for (let j = 0; j < needle.length; j++) {
113
+ if (source[i + j] !== needle[j]) { matched = false; break; }
202
114
  }
115
+ if (matched) return i;
203
116
  }
204
- if (Array.isArray(value)) {
205
- return value.map(item => TopicProtocolApiServiceProxy.replaceFileRefs(item, fileMap));
206
- }
207
- if (value && typeof value === 'object') {
208
- const result: Record<string, unknown> = {};
209
- Object.keys(value).forEach(key => {
210
- result[key] = TopicProtocolApiServiceProxy.replaceFileRefs((value as Record<string, unknown>)[key], fileMap);
211
- });
212
- return result;
117
+ return -1;
118
+ };
119
+
120
+ const parts: MixedPart[] = [];
121
+ let cursor = indexOf(buffer, boundaryBytes, 0);
122
+
123
+ while (cursor !== -1) {
124
+ const nextBoundary = indexOf(buffer, boundaryBytes, cursor + boundaryBytes.length);
125
+ const endBoundary = indexOf(buffer, endBoundaryBytes, cursor + boundaryBytes.length);
126
+ const isFinal = endBoundary !== -1 && (endBoundary < nextBoundary || nextBoundary === -1);
127
+ const partStart = cursor + boundaryBytes.length;
128
+ if (isFinal) break;
129
+
130
+ let headerStart = partStart;
131
+ if (buffer[headerStart] === 13 && buffer[headerStart + 1] === 10) headerStart += 2;
132
+
133
+ const headerEnd = indexOf(buffer, separator, headerStart);
134
+ if (headerEnd === -1) break;
135
+
136
+ const headerText = new TextDecoder().decode(buffer.slice(headerStart, headerEnd));
137
+ const headers: Record<string, string> = {};
138
+ headerText.split('\r\n').forEach(line => {
139
+ const idx = line.indexOf(':');
140
+ if (idx > -1) headers[line.slice(0, idx).trim().toLowerCase()] = line.slice(idx + 1).trim();
141
+ });
142
+
143
+ const bodyStart = headerEnd + separator.length;
144
+ const bodyEnd = nextBoundary === -1 ? buffer.length : nextBoundary - 2;
145
+ parts.push({ headers, body: buffer.slice(bodyStart, bodyEnd) });
146
+ cursor = nextBoundary;
147
+ }
148
+
149
+ return { boundary, parts };
150
+ };
151
+
152
+ const replaceFileRefs = (
153
+ value: unknown,
154
+ fileMap: Map<string, { name?: string; mime?: string; size: number; buffer: ArrayBuffer }>,
155
+ ): unknown => {
156
+ if (value && typeof value === 'object' && '$file' in value) {
157
+ const fileKey = (value as { $file?: unknown }).$file;
158
+ if (typeof fileKey === 'string') {
159
+ const file = fileMap.get(fileKey);
160
+ if (file) return file;
213
161
  }
214
- return value;
215
162
  }
163
+ if (Array.isArray(value)) return value.map(item => replaceFileRefs(item, fileMap));
164
+ if (value && typeof value === 'object') {
165
+ const result: Record<string, unknown> = {};
166
+ Object.keys(value).forEach(key => {
167
+ result[key] = replaceFileRefs((value as Record<string, unknown>)[key], fileMap);
168
+ });
169
+ return result;
170
+ }
171
+ return value;
172
+ };
216
173
 
217
- private static normalizeResponse(value: unknown) {
218
- if (value && typeof value === 'object' && 'state' in value && 'body' in value) {
219
- const typed = value as { state?: string; body?: unknown };
220
- if (typed.state === 'error') {
221
- const message = typeof typed.body === 'object' && typed.body && 'message' in typed.body ? String((typed.body as { message?: unknown }).message) : 'TopicProtocol error';
222
- throw new Error(message);
223
- }
224
- return typed.body;
174
+ const normalizeResponse = (value: unknown): unknown => {
175
+ if (value && typeof value === 'object' && 'state' in value && 'body' in value) {
176
+ const typed = value as { state?: string; body?: unknown };
177
+ if (typed.state === 'error') {
178
+ const message = typeof typed.body === 'object' && typed.body && 'message' in typed.body
179
+ ? String((typed.body as { message?: unknown }).message)
180
+ : 'TopicProtocol error';
181
+ throw new Error(message);
225
182
  }
226
- return value;
183
+ return typed.body;
227
184
  }
185
+ return value;
186
+ };
187
+
188
+ // ── createHandler (module-level export, same as SymbolIntentApiServiceProxy) ─
189
+
190
+ export const createTopicHandler = <T extends object>(apiService: ApiService): ProxyHandler<T> => {
191
+ return {
192
+ get(target: T, prop: string | symbol, receiver: unknown): unknown {
193
+ if (prop === '_SimpleBoot_origin') return target;
194
+
195
+ const simConfig = getSim(target);
196
+ const value = Reflect.get(target, prop, receiver);
197
+
198
+ if (typeof value === 'function' && simConfig?.symbol) {
199
+ return function (...args: unknown[]) {
200
+ const f = value as Function;
201
+
202
+ const p = (userConfig?: TopicProtocolConfig) => {
203
+ const symbolValue = Array.isArray(simConfig.symbol) ? simConfig.symbol[0] : simConfig.symbol;
204
+ const symbolName = (symbolValue as Symbol | undefined)?.description ?? String(symbolValue ?? simConfig.symbol);
205
+ const targetPath = `Symbol.for(${symbolName}):/${String(prop)}`;
206
+
207
+ const { request, fileEntries } = buildRequest(targetPath, userConfig?.body, userConfig?.type ?? 'intent');
208
+
209
+ const formData = new FormData();
210
+ formData.append('body', JSON.stringify(request));
211
+ fileEntries.forEach(entry => {
212
+ if (entry.filename) formData.append(entry.id, entry.file, entry.filename);
213
+ else formData.append(entry.id, entry.file);
214
+ });
228
215
 
229
- static createHandler<T extends object>(apiService: ApiService): ProxyHandler<T> {
230
- return {
231
- get(target: T, prop: string | symbol, receiver: unknown): unknown {
232
- const simConfig = getSim(target);
233
- const value = Reflect.get(target, prop, receiver);
234
- if (typeof value === 'function' && simConfig?.symbol) {
235
- return function (...args: unknown[]) {
236
- const f = value as Function;
237
- const p = (userConfig?: TopicProtocolConfig) => {
238
- const symbolValue = Array.isArray(simConfig.symbol) ? simConfig.symbol[0] : simConfig.symbol;
239
- const symbolName = (symbolValue as Symbol | undefined)?.description ?? String(symbolValue ?? simConfig.symbol);
240
- const targetPath = `Symbol.for(${symbolName}):/${String(prop)}`;
241
- const { request, fileEntries } = TopicProtocolApiServiceProxy.buildRequest(targetPath, userConfig?.body, userConfig?.type ?? 'intent');
242
- const formData = new FormData();
243
- formData.append('body', JSON.stringify(request));
244
- fileEntries.forEach(entry => {
245
- if (entry.filename) {
246
- formData.append(entry.id, entry.file, entry.filename);
247
- } else {
248
- formData.append(entry.id, entry.file);
249
- }
250
- });
251
-
252
- const headers = {
253
- ...(userConfig?.headers ?? {}),
254
- [HttpHeaders.XSimpleBootHttpTopicProtocol]: 'topic'
255
- };
256
-
257
- const endpoint = userConfig?.endpoint ?? '/';
258
- return apiService.post({
259
- target: endpoint,
260
- config: {
261
- responseTransform: userConfig?.bypassTransform
262
- ? 'response'
263
- : userConfig?.transformText
264
- ? 'text'
265
- : async (response: Response) => {
266
- const contentType = response.headers.get('Content-Type') ?? '';
267
- if (contentType.toLowerCase().includes('multipart/mixed')) {
268
- const parsed = await TopicProtocolApiServiceProxy.parseMultipartMixed(response);
269
- if (!parsed) {
270
- return undefined;
216
+ const headers: Record<string, string> = {
217
+ ...(userConfig?.headers as Record<string, string> ?? {}),
218
+ [HttpHeaders.XSimpleBootHttpTopicProtocol]: 'topic',
219
+ };
220
+
221
+ const endpoint = userConfig?.endpoint ?? '/';
222
+
223
+ return (apiService as any).post({
224
+ target: endpoint,
225
+ config: {
226
+ responseTransform: userConfig?.bypassTransform
227
+ ? 'response'
228
+ : userConfig?.transformText
229
+ ? 'text'
230
+ : async (response: Response) => {
231
+ const ct = response.headers.get('Content-Type') ?? '';
232
+ if (ct.toLowerCase().includes('multipart/mixed')) {
233
+ const parsed = await parseMultipartMixed(response);
234
+ if (!parsed) return undefined;
235
+
236
+ const fileMap = new Map<string, { name?: string; mime?: string; size: number; buffer: ArrayBuffer }>();
237
+ let jsonBody: unknown = undefined;
238
+
239
+ parsed.parts.forEach(part => {
240
+ const partType = part.headers['content-type'] ?? '';
241
+ const disposition = part.headers['content-disposition'];
242
+ const name = parseDispositionName(disposition);
243
+
244
+ if (partType.includes('application/json')) {
245
+ const text = new TextDecoder().decode(part.body);
246
+ jsonBody = text ? JSON.parse(text) : undefined;
247
+ return;
271
248
  }
272
- const fileMap = new Map<string, { name?: string; mime?: string; size: number; buffer: ArrayBuffer }>();
273
- let jsonBody: unknown = undefined;
274
- parsed.parts.forEach(part => {
275
- const partType = part.headers['content-type'] ?? '';
276
- const disposition = part.headers['content-disposition'];
277
- const name = TopicProtocolApiServiceProxy.parseDispositionName(disposition);
278
- if (partType.includes('application/json')) {
279
- const text = new TextDecoder().decode(part.body);
280
- jsonBody = text ? JSON.parse(text) : undefined;
281
- return;
282
- }
283
- const filename = TopicProtocolApiServiceProxy.parseDispositionFilename(disposition);
284
- if (name) {
285
- const view = part.body;
286
- const safe = new Uint8Array(view.byteLength);
287
- safe.set(view);
288
- fileMap.set(name, {
289
- name: filename,
290
- mime: partType || 'application/octet-stream',
291
- size: view.byteLength,
292
- buffer: safe.buffer
293
- });
294
- }
295
- });
296
- if (jsonBody) {
297
- const replaced = TopicProtocolApiServiceProxy.replaceFileRefs(jsonBody, fileMap);
298
- return TopicProtocolApiServiceProxy.normalizeResponse(replaced);
249
+ if (name) {
250
+ const filename = parseDispositionFilename(disposition);
251
+ const safe = new Uint8Array(part.body.byteLength);
252
+ safe.set(part.body);
253
+ fileMap.set(name, {
254
+ name: filename,
255
+ mime: partType || 'application/octet-stream',
256
+ size: part.body.byteLength,
257
+ buffer: safe.buffer,
258
+ });
299
259
  }
300
- return undefined;
301
- }
302
- const text = await response.text();
303
- const parsed = text ? JSON.parse(text) : undefined;
304
- return TopicProtocolApiServiceProxy.normalizeResponse(parsed);
305
- },
306
- transformText: userConfig?.transformText,
307
- config: userConfig?.config,
308
- fetch: {
309
- credentials: 'include',
310
- headers,
311
- body: formData
312
- }
313
- } as any
314
- });
315
- };
316
- args.push(p);
317
- return Reflect.apply(f, target, args);
260
+ });
261
+
262
+ if (jsonBody) return normalizeResponse(replaceFileRefs(jsonBody, fileMap));
263
+ return undefined;
264
+ }
265
+
266
+ const text = await response.text();
267
+ const parsed = text ? JSON.parse(text) : undefined;
268
+ return normalizeResponse(parsed);
269
+ },
270
+ transformText: userConfig?.transformText,
271
+ config: userConfig?.config,
272
+ fetch: {
273
+ credentials: 'include',
274
+ headers,
275
+ body: formData,
276
+ },
277
+ } as any,
278
+ });
318
279
  };
319
- }
320
- return value;
280
+
281
+ args.push(p);
282
+ return Reflect.apply(f, target, args);
283
+ };
321
284
  }
322
- };
323
- }
324
285
 
325
- get(target: T, prop: string | symbol, receiver: unknown): unknown {
326
- return TopicProtocolApiServiceProxy.createHandler<T>(this.apiService).get!(target, prop, receiver);
286
+ return Reflect.get(target, prop, receiver);
287
+ },
288
+
289
+ apply(target: T, thisArg: unknown, argArray: unknown[]): unknown {
290
+ console.log('TopicProtocolApiServiceProxy apply');
291
+ return Reflect.apply(target as any, thisArg, argArray as any[]);
292
+ },
293
+ };
294
+ };
295
+
296
+ // ── Default factory export (same shape as SymbolIntentApiServiceProxy) ───────
297
+
298
+ export default (config?: SimConfig): ConstructorType<any> => {
299
+ @sim(config)
300
+ class TopicProtocolApiServiceProxy<T extends object> implements ProxyHandler<T> {
301
+ constructor(private apiService: ApiService) {}
302
+
303
+ get(target: T, prop: string | symbol, receiver: unknown): unknown {
304
+ return createTopicHandler<T>(this.apiService).get!(target, prop, receiver);
305
+ }
306
+
307
+ apply(target: T, thisArg: unknown, argArray: unknown[]): unknown {
308
+ return createTopicHandler<T>(this.apiService).apply!(target, thisArg, argArray as any[]);
309
+ }
327
310
  }
328
- }
311
+
312
+ return TopicProtocolApiServiceProxy;
313
+ };
@@ -1,6 +1,7 @@
1
1
  import {RandomUtils} from '@dooboostore/core';
2
2
  import {TopicRequest, TopicResponse, TopicServerEventRequest, TopicServerEventResponse, TopicServerEventUnSubscribeRequest, TopicSubscribeRequest, TopicType, TopicUnsubscribeRequest} from './WebSocketManager';
3
- import {MessageOperator, Observable, Subject} from '@dooboostore/core';
3
+ import {Observable, Subject} from '@dooboostore/core';
4
+ import { filter } from '@dooboostore/core/message/operators';
4
5
 
5
6
  export * from '@dooboostore/core';
6
7
 
@@ -565,7 +566,7 @@ export class WebSocketClient {
565
566
  const value: TopicRequest = { type: option?.type, uuid, requestUUID, body: body };
566
567
  self.sendMessage(value);
567
568
  }
568
- return topicSubject.pipe(MessageOperator.filter(data => data && data.requestUUID === requestUUID));
569
+ return topicSubject.pipe(filter(data => data && data.requestUUID === requestUUID));
569
570
  },
570
571
  onUnsubscribe: (callback: OnUnsubscribeCallback) => {
571
572
  onUnsubscribes.push(callback);
package/README.MD DELETED
@@ -1,62 +0,0 @@
1
- # @dooboostore/simple-boot-http-server
2
-
3
- [![NPM version](https://img.shields.io/npm/v/@dooboostore/simple-boot-http-server.svg?style=flat-square)](https://www.npmjs.com/package/@dooboostore/simple-boot-http-server)
4
- [![Build and Test](https://github.com/dooboostore-develop/packages/actions/workflows/main.yaml/badge.svg?branch=main)](https://github.com/dooboostore-develop/packages/actions/workflows/main.yaml)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
6
-
7
- **Full Documentation:** [https://dooboostore-develop.github.io/@dooboostore/simple-boot-http-server](https://dooboostore-develop.github.io/@dooboostore/simple-boot-http-server)
8
-
9
- A lightweight and powerful HTTP web server framework for Node.js, built on `@dooboostore/simple-boot`.
10
-
11
- ---
12
-
13
- ## Features
14
-
15
- - **Declarative Routing**: Use decorators like `@Router`, `@Route`, `@GET`, `@POST`, etc., to map URL paths and HTTP methods directly to your controller methods.
16
- - **Middleware via Filters**: Intercept the request-response cycle with `Filter` classes to implement cross-cutting concerns like logging, authentication, and CORS.
17
- - **Lifecycle Endpoints**: Hook into the request lifecycle with `EndPoint` classes that run on request start, close, or error.
18
- - **Integrated Exception Handling**: Leverage `@simple-boot`'s AOP capabilities to create robust global error handlers.
19
- - **Built-in Session Management**: Easily manage user sessions with a built-in session manager and customizable storage.
20
- - **HTTPS and HTTP/2 Support**: Secure your server with HTTPS and HTTP/2 configuration.
21
- - **WebSocket Topics + Events**: Topic-based subscribe/request/response with server events.
22
- - **WebSocket Binary File Transfer**: Send/receive files inside regular message payloads (JSON + binary framing).
23
- - **WebSocket Auto Reconnect**: Automatic reconnect and re-subscribe with configurable retry.
24
-
25
- ## Quick Start
26
-
27
- ```bash
28
- npx @dooboostore/create-simple-boot-http-server my-server
29
- cd my-server
30
- npm start
31
- ```
32
-
33
- ## Security Hardening
34
-
35
- - Enforce TLS in production: configure HTTPS (or terminate TLS at a trusted reverse proxy) and redirect HTTP to HTTPS.
36
- - Keep session cookies hardened: use `httpOnly`, `secure`, `sameSite`, short expiration, and explicit `domain/path` when required.
37
- - Protect WebSocket endpoints: validate origin, authenticate at connect/subscribe time, and authorize per topic.
38
- - Validate all inputs: apply strict validation for route params, query/body payloads, and uploaded files (type/size/path).
39
- - Avoid sensitive data leakage: never return stack traces, internal file paths, raw SQL errors, or secrets in responses/logs.
40
- - Configure CORS minimally: allow only required origins/methods/headers, avoid wildcard credentials.
41
- - Add rate limiting and timeout guards: mitigate brute force and resource-exhaustion attacks.
42
- - Rotate secrets/keys regularly and load them from environment or a secret manager (never hardcode in source).
43
- - Keep dependencies updated and scan regularly for CVEs.
44
-
45
- ### Minimal Production Checklist
46
-
47
- - `HTTPS` enabled end-to-end
48
- - Session cookie options set (`httpOnly`, `secure`, `sameSite`)
49
- - CORS restricted to trusted origins
50
- - Request/WS auth + authorization enforced
51
- - Upload size/type limits configured
52
- - Error responses sanitized
53
-
54
- ## Learn More
55
-
56
- The detailed API documentation, including all decorators and usage examples, is available on our documentation website.
57
-
58
- - WebSocket Guide: [document/websocket/README.md](document/websocket/README.md)
59
-
60
- ## License
61
-
62
- This package is licensed under the [MIT License](https://opensource.org/licenses/MIT).