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

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 (66) hide show
  1. package/README.md +100 -14
  2. package/dist/cjs/SimpleBootHttpServer.js +34 -18
  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/codes/Mimes.js +2 -0
  7. package/dist/cjs/codes/Mimes.js.map +2 -2
  8. package/dist/cjs/decorators/MethodMapping.js +2 -2
  9. package/dist/cjs/decorators/MethodMapping.js.map +2 -2
  10. package/dist/cjs/filters/ResourceFilter.js +0 -1
  11. package/dist/cjs/filters/ResourceFilter.js.map +2 -2
  12. package/dist/cjs/option/HttpServerOption.js.map +2 -2
  13. package/dist/cjs/proxy/TopicProtocolApiServiceProxy.js +244 -266
  14. package/dist/cjs/proxy/TopicProtocolApiServiceProxy.js.map +3 -3
  15. package/dist/cjs/websocket/WebSocketClient.js +2 -1
  16. package/dist/cjs/websocket/WebSocketClient.js.map +2 -2
  17. package/dist/cjs/websocket/WebSocketManager.js +68 -40
  18. package/dist/cjs/websocket/WebSocketManager.js.map +2 -2
  19. package/dist/esm/SimpleBootHttpServer.js +34 -18
  20. package/dist/esm/SimpleBootHttpServer.js.map +2 -2
  21. package/dist/esm/bundle-entry.js +14 -12
  22. package/dist/esm/bundle-entry.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/option/HttpServerOption.js.map +2 -2
  30. package/dist/esm/proxy/TopicProtocolApiServiceProxy.js +244 -266
  31. package/dist/esm/proxy/TopicProtocolApiServiceProxy.js.map +3 -3
  32. package/dist/esm/websocket/WebSocketClient.js +3 -2
  33. package/dist/esm/websocket/WebSocketClient.js.map +2 -2
  34. package/dist/esm/websocket/WebSocketManager.js +68 -40
  35. package/dist/esm/websocket/WebSocketManager.js.map +2 -2
  36. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +19134 -9180
  37. package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +4 -4
  38. package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
  39. package/dist/types/bundle-entry.d.ts +7 -12
  40. package/dist/types/bundle-entry.d.ts.map +1 -1
  41. package/dist/types/codes/Mimes.d.ts +2 -0
  42. package/dist/types/codes/Mimes.d.ts.map +1 -1
  43. package/dist/types/decorators/MethodMapping.d.ts +8 -0
  44. package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
  45. package/dist/types/filters/ResourceFilter.d.ts.map +1 -1
  46. package/dist/types/option/HttpServerOption.d.ts +6 -2
  47. package/dist/types/option/HttpServerOption.d.ts.map +1 -1
  48. package/dist/types/proxy/TopicProtocolApiServiceProxy.d.ts +5 -15
  49. package/dist/types/proxy/TopicProtocolApiServiceProxy.d.ts.map +1 -1
  50. package/dist/types/websocket/WebSocketClient.d.ts.map +1 -1
  51. package/dist/types/websocket/WebSocketManager.d.ts +3 -1
  52. package/dist/types/websocket/WebSocketManager.d.ts.map +1 -1
  53. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +19121 -9173
  54. package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
  55. package/dist/umd-bundle/websocket-client.umd.js +412 -1482
  56. package/dist/umd-bundle/websocket-client.umd.js.map +4 -4
  57. package/package.json +8 -8
  58. package/src/SimpleBootHttpServer.ts +37 -18
  59. package/src/bundle-entry.ts +12 -12
  60. package/src/codes/Mimes.ts +2 -0
  61. package/src/decorators/MethodMapping.ts +14 -7
  62. package/src/filters/ResourceFilter.ts +0 -1
  63. package/src/option/HttpServerOption.ts +4 -2
  64. package/src/proxy/TopicProtocolApiServiceProxy.ts +260 -275
  65. package/src/websocket/WebSocketClient.ts +3 -2
  66. package/src/websocket/WebSocketManager.ts +69 -41
@@ -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);