@hyperttp/core 1.3.0 → 1.3.3
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/Core/HyperCore.d.ts +185 -8
- package/Core/HyperCore.d.ts.map +1 -1
- package/Core/HyperCore.js +246 -99
- package/Core/HyperCore.js.map +1 -1
- package/README.md +169 -96
- package/package.json +2 -2
- package/transports/manager.d.ts +61 -3
- package/transports/manager.d.ts.map +1 -1
- package/transports/manager.js +130 -93
- package/transports/manager.js.map +1 -1
- package/transports/node.d.ts +26 -9
- package/transports/node.d.ts.map +1 -1
- package/transports/node.js +63 -134
- package/transports/node.js.map +1 -1
- package/utils/decompress.d.ts +20 -0
- package/utils/decompress.d.ts.map +1 -1
- package/utils/decompress.js +75 -25
- package/utils/decompress.js.map +1 -1
- package/utils/merge.js +12 -18
- package/utils/merge.js.map +1 -1
- package/utils/normalize.d.ts +26 -1
- package/utils/normalize.d.ts.map +1 -1
- package/utils/normalize.js +68 -89
- package/utils/normalize.js.map +1 -1
- package/utils/pipeline.d.ts +42 -10
- package/utils/pipeline.d.ts.map +1 -1
- package/utils/pipeline.js +34 -15
- package/utils/pipeline.js.map +1 -1
- package/utils/response.d.ts +41 -23
- package/utils/response.d.ts.map +1 -1
- package/utils/response.js +76 -36
- package/utils/response.js.map +1 -1
- package/utils/retryUtils.d.ts +20 -0
- package/utils/retryUtils.d.ts.map +1 -1
- package/utils/retryUtils.js +24 -0
- package/utils/retryUtils.js.map +1 -1
package/transports/manager.js
CHANGED
|
@@ -32,37 +32,22 @@ const CANDIDATES_MAP = {
|
|
|
32
32
|
node: TRANSPORTS.filter((t) => t.runtime.includes("node")).sort((a, b) => b.priority - a.priority),
|
|
33
33
|
bun: TRANSPORTS.filter((t) => t.runtime.includes("bun")).sort((a, b) => b.priority - a.priority),
|
|
34
34
|
};
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (logger) {
|
|
42
|
-
if (typeof logger === "function") {
|
|
43
|
-
logger(message, err);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const candidate = logger;
|
|
47
|
-
if (typeof candidate.error === "function") {
|
|
48
|
-
candidate.error(message, err);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (!config.verbose) {
|
|
53
|
-
console.error(message, err);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
35
|
+
const CACHE = new Map();
|
|
36
|
+
const DEBUG = false;
|
|
37
|
+
const trace = (...args) => {
|
|
38
|
+
if (DEBUG)
|
|
39
|
+
console.log("[hyperttp]", ...args);
|
|
40
|
+
};
|
|
56
41
|
function isModuleNotFoundError(err) {
|
|
57
42
|
if (!err || typeof err !== "object")
|
|
58
43
|
return false;
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
return (code === "ERR_MODULE_NOT_FOUND" ||
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
const e = err;
|
|
45
|
+
const msg = err instanceof Error ? err.message : "";
|
|
46
|
+
return (e.code === "ERR_MODULE_NOT_FOUND" ||
|
|
47
|
+
msg.includes("Cannot find module") ||
|
|
48
|
+
msg.includes("Failed to resolve"));
|
|
64
49
|
}
|
|
65
|
-
async function
|
|
50
|
+
async function resolveModule(pkg) {
|
|
66
51
|
if (pkg.startsWith(".")) {
|
|
67
52
|
try {
|
|
68
53
|
return import.meta.resolve(pkg, import.meta.url);
|
|
@@ -71,122 +56,174 @@ async function resolveTransportModulePath(pkg) {
|
|
|
71
56
|
return null;
|
|
72
57
|
}
|
|
73
58
|
}
|
|
74
|
-
const physicalPath = resolveFrom.silent(process.cwd(), pkg);
|
|
75
|
-
if (physicalPath) {
|
|
76
|
-
return pathToFileURL(physicalPath).href;
|
|
77
|
-
}
|
|
78
59
|
try {
|
|
79
60
|
return import.meta.resolve(pkg, import.meta.url);
|
|
80
61
|
}
|
|
81
62
|
catch {
|
|
82
|
-
|
|
63
|
+
//
|
|
83
64
|
}
|
|
65
|
+
const physical = resolveFrom.silent(process.cwd(), pkg);
|
|
66
|
+
if (physical)
|
|
67
|
+
return pathToFileURL(physical).href;
|
|
68
|
+
return null;
|
|
84
69
|
}
|
|
85
|
-
async function
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
70
|
+
async function loadCtor(pkg, exportName) {
|
|
71
|
+
const resolved = await resolveModule(pkg);
|
|
72
|
+
if (!resolved) {
|
|
73
|
+
trace("resolve failed:", pkg);
|
|
88
74
|
return null;
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
75
|
+
}
|
|
76
|
+
const mod = (await import(resolved));
|
|
77
|
+
const candidate = mod[exportName] ?? mod.default;
|
|
78
|
+
if (typeof candidate !== "function") {
|
|
79
|
+
trace("invalid export:", pkg, exportName);
|
|
94
80
|
return null;
|
|
81
|
+
}
|
|
95
82
|
return candidate;
|
|
96
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* @ru Главный резолвер транспорта (диагностический и детерминированный).
|
|
86
|
+
* @en Main transport resolver (diagnostic and deterministic).
|
|
87
|
+
* @param config - Client configuration (may contain customTransport).
|
|
88
|
+
* @returns Promise resolving to a HyperTransport instance.
|
|
89
|
+
* @throws If no compatible transport can be loaded.
|
|
90
|
+
*/
|
|
97
91
|
export async function resolveTransport(config) {
|
|
98
|
-
if (config.customTransport)
|
|
99
|
-
config.logger?.(`info`, "[Hyperttp] Using custom user-provided transport.", config);
|
|
92
|
+
if (config.customTransport)
|
|
100
93
|
return config.customTransport;
|
|
101
|
-
}
|
|
102
94
|
const runtime = CURRENT_RUNTIME;
|
|
103
|
-
const cachedCtor = GLOBAL_TRANSPORT_CLASS_CACHE[runtime];
|
|
104
|
-
if (cachedCtor) {
|
|
105
|
-
config.logger?.(`info`, `[Hyperttp] Using cached transport class for runtime: ${runtime}`, config);
|
|
106
|
-
return new cachedCtor(config);
|
|
107
|
-
}
|
|
108
95
|
const candidates = CANDIDATES_MAP[runtime];
|
|
96
|
+
const failures = [];
|
|
109
97
|
for (const t of candidates) {
|
|
98
|
+
trace("trying:", t.pkg);
|
|
110
99
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
100
|
+
const cached = CACHE.get(t.pkg);
|
|
101
|
+
if (cached) {
|
|
102
|
+
trace("cache hit:", t.pkg);
|
|
103
|
+
return new cached(config);
|
|
104
|
+
}
|
|
105
|
+
const ctor = await loadCtor(t.pkg, t.export);
|
|
106
|
+
if (!ctor) {
|
|
107
|
+
failures.push(`${t.pkg} (no export: ${t.export})`);
|
|
115
108
|
continue;
|
|
116
109
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return new
|
|
110
|
+
CACHE.set(t.pkg, ctor);
|
|
111
|
+
trace("selected:", t.pkg);
|
|
112
|
+
return new ctor(config);
|
|
120
113
|
}
|
|
121
114
|
catch (err) {
|
|
122
115
|
if (isModuleNotFoundError(err)) {
|
|
123
|
-
|
|
116
|
+
failures.push(`${t.pkg} (not installed)`);
|
|
124
117
|
continue;
|
|
125
118
|
}
|
|
126
|
-
|
|
127
|
-
throw err;
|
|
119
|
+
throw new Error(`[Hyperttp] transport crash in ${t.pkg}: ${err?.message}`, { cause: err });
|
|
128
120
|
}
|
|
129
121
|
}
|
|
130
|
-
throw new Error(`No compatible transport
|
|
131
|
-
.
|
|
132
|
-
.join(", ")} is installed.`);
|
|
122
|
+
throw new Error(`No compatible transport for runtime="${runtime}".\n` +
|
|
123
|
+
`Failures:\n- ${failures.join("\n- ")}`);
|
|
133
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* @ru Управляет жизненным циклом транспортного слоя: ленивая инициализация, кеширование, синхронизация конфигурации.
|
|
127
|
+
* @en Manages the transport layer lifecycle: lazy initialization, caching, configuration synchronization.
|
|
128
|
+
*/
|
|
134
129
|
export class TransportManager {
|
|
135
130
|
transport = null;
|
|
136
|
-
|
|
131
|
+
promise = null;
|
|
137
132
|
config;
|
|
138
|
-
|
|
133
|
+
/**
|
|
134
|
+
* @ru Создаёт менеджер транспорта.
|
|
135
|
+
* @en Creates a transport manager.
|
|
136
|
+
* @param config - Client configuration.
|
|
137
|
+
* @param custom - Optional externally provided transport instance.
|
|
138
|
+
*/
|
|
139
|
+
constructor(config, custom) {
|
|
139
140
|
this.config = config;
|
|
140
|
-
if (
|
|
141
|
-
this.transport =
|
|
142
|
-
this.
|
|
143
|
-
this.syncConfig();
|
|
141
|
+
if (custom) {
|
|
142
|
+
this.transport = custom;
|
|
143
|
+
this.promise = Promise.resolve(custom);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* @ru Обновляет конфигурацию клиента.
|
|
148
|
+
* @en Updates the client configuration.
|
|
149
|
+
* @param config - New configuration.
|
|
150
|
+
*/
|
|
146
151
|
setConfig(config) {
|
|
147
152
|
this.config = config;
|
|
148
|
-
this.syncConfig();
|
|
149
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* @ru Возвращает текущий экземпляр транспорта (если уже инициализирован).
|
|
156
|
+
* @en Returns the current transport instance (if already initialised).
|
|
157
|
+
*/
|
|
150
158
|
get instance() {
|
|
151
159
|
return this.transport;
|
|
152
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* @ru Асинхронно получает экземпляр транспорта (инициализирует при необходимости).
|
|
163
|
+
* @en Asynchronously obtains a transport instance (initialises if needed).
|
|
164
|
+
* @returns Promise resolving to a HyperTransport.
|
|
165
|
+
*/
|
|
153
166
|
async get() {
|
|
154
167
|
if (this.transport)
|
|
155
168
|
return this.transport;
|
|
156
|
-
if (this.
|
|
157
|
-
return this.
|
|
158
|
-
this.
|
|
169
|
+
if (this.promise)
|
|
170
|
+
return this.promise;
|
|
171
|
+
this.promise = resolveTransport(this.config).then((t) => {
|
|
159
172
|
this.transport = t;
|
|
160
|
-
this.syncConfig();
|
|
161
173
|
return t;
|
|
162
174
|
});
|
|
163
|
-
return this.
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
return this.promise;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* @ru Выполняет запрос через транспорт и возвращает обычный (не стриминговый) ответ.
|
|
179
|
+
* @en Executes a request through the transport and returns a regular (non‑streaming) response.
|
|
180
|
+
* @param req - Request parameters.
|
|
181
|
+
* @returns Promise resolving to HttpResponse.
|
|
182
|
+
*/
|
|
170
183
|
async execute(req) {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
const t = this.transport || (await this.get());
|
|
185
|
+
return mapResponseFast(await t.execute(req));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* @ru Выполняет запрос через транспорт и возвращает стриминговый ответ.
|
|
189
|
+
* @en Executes a request through the transport and returns a streaming response.
|
|
190
|
+
* @param req - Request parameters.
|
|
191
|
+
* @returns Promise resolving to StreamResponse.
|
|
192
|
+
*/
|
|
175
193
|
async executeStream(req) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
194
|
+
const t = this.transport || (await this.get());
|
|
195
|
+
return mapStreamFast(await t.execute(req));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* @ru Возвращает отладочную информацию о текущем транспорте.
|
|
199
|
+
* @en Returns debug information about the current transport.
|
|
200
|
+
*/
|
|
201
|
+
get debug() {
|
|
202
|
+
const t = this.transport;
|
|
203
|
+
if (!t)
|
|
204
|
+
return null;
|
|
205
|
+
return {
|
|
206
|
+
name: t.__name ?? t.constructor?.name,
|
|
207
|
+
pkg: t.__pkg ?? "unknown",
|
|
208
|
+
runtime: getRuntime(),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* @ru Завершает работу транспорта (закрывает соединения при graceful shutdown, иначе принудительно уничтожает).
|
|
213
|
+
* @en Shuts down the transport (gracefully closes connections or forcefully destroys).
|
|
214
|
+
* @param graceful - If true, attempt graceful close; otherwise call destroy.
|
|
215
|
+
* @returns Promise that resolves when shutdown is complete.
|
|
216
|
+
*/
|
|
180
217
|
async destroy(graceful = true) {
|
|
181
|
-
const
|
|
182
|
-
if (!
|
|
218
|
+
const t = this.transport;
|
|
219
|
+
if (!t)
|
|
183
220
|
return;
|
|
184
|
-
if (graceful && typeof
|
|
185
|
-
await
|
|
221
|
+
if (graceful && typeof t.close === "function") {
|
|
222
|
+
await t.close();
|
|
186
223
|
return;
|
|
187
224
|
}
|
|
188
|
-
if (typeof
|
|
189
|
-
await
|
|
225
|
+
if (typeof t.destroy === "function") {
|
|
226
|
+
await t.destroy();
|
|
190
227
|
}
|
|
191
228
|
}
|
|
192
229
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/transports/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/transports/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,WAAW,MAAM,cAAc,CAAC;AAQvC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAatE,MAAM,CAAC,MAAM,eAAe,GAC1B,OAAO,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AAE9C,MAAM,UAAU,UAAU;IACxB,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC;QACE,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,CAAC,KAAK,CAAC;QAChB,GAAG,EAAE,yBAAyB;QAC9B,MAAM,EAAE,cAAc;QACtB,QAAQ,EAAE,GAAG;KACd;IACD;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,CAAC,MAAM,CAAC;QACjB,GAAG,EAAE,4BAA4B;QACjC,MAAM,EAAE,iBAAiB;QACzB,QAAQ,EAAE,EAAE;KACb;IACD;QACE,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QACxB,GAAG,EAAE,uBAAuB;QAC5B,MAAM,EAAE,eAAe;QACvB,QAAQ,EAAE,EAAE;KACb;CACF,CAAC;AAEF,MAAM,cAAc,GAAoC;IACtD,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAC7D,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAClC;IACD,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAC3D,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAClC;CACF,CAAC;AAEF,MAAM,KAAK,GAA+B,IAAI,GAAG,EAAE,CAAC;AAEpD,MAAM,KAAK,GAAG,KAAK,CAAC;AACpB,MAAM,KAAK,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE;IACnC,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF,SAAS,qBAAqB,CAAC,GAAY;IACzC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAElD,MAAM,CAAC,GAAG,GAAoB,CAAC;IAC/B,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpD,OAAO,CACL,CAAC,CAAC,IAAI,KAAK,sBAAsB;QACjC,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QAClC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAClC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,GAAW;IACtC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,EAAE;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;IACxD,IAAI,QAAQ;QAAE,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IAElD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,GAAW,EACX,UAAkB;IAElB,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IAE1C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,KAAK,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAA4B,CAAC;IAEhE,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC;IAEjD,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,KAAK,CAAC,iBAAiB,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,SAA0B,CAAC;AACpC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAyB;IAEzB,IAAI,MAAM,CAAC,eAAe;QAAE,OAAO,MAAM,CAAC,eAAe,CAAC;IAE1D,MAAM,OAAO,GAAG,eAAe,CAAC;IAChC,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAE3C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAExB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,MAAM,EAAE,CAAC;gBACX,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC3B,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;YAE7C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;gBACnD,SAAS;YACX,CAAC;YAED,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACvB,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YAE1B,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/B,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC;gBAC1C,SAAS;YACX,CAAC;YAED,MAAM,IAAI,KAAK,CACb,iCAAiC,CAAC,CAAC,GAAG,KAAM,GAAa,EAAE,OAAO,EAAE,EACpE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,wCAAwC,OAAO,MAAM;QACnD,gBAAgB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAC1C,CAAC;AACJ,CAAC;AAQD;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IACnB,SAAS,GAA0B,IAAI,CAAC;IACxC,OAAO,GAAmC,IAAI,CAAC;IAC/C,MAAM,CAAoB;IAElC;;;;;OAKG;IACH,YAAY,MAAyB,EAAE,MAAuB;QAC5D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;YACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,MAAyB;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;OAGG;IACH,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG;QACP,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC;QAC1C,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;QAEtC,IAAI,CAAC,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;YACtD,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;YACnB,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CACX,GAA6C;QAE7C,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/C,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAoB,CAAC;IAClE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,aAAa,CACjB,GAA6C;QAE7C,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/C,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAsB,CAAC;IAClE,CAAC;IAED;;;OAGG;IACH,IAAW,KAAK;QACd,MAAM,CAAC,GAAG,IAAI,CAAC,SAAgB,CAAC;QAChC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpB,OAAO;YACL,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,WAAW,EAAE,IAAI;YACrC,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS;YACzB,OAAO,EAAE,UAAU,EAAE;SACtB,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;QACzB,IAAI,CAAC,CAAC;YAAE,OAAO;QAEf,IAAI,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC9C,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;CACF"}
|
package/transports/node.d.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ru Транспорт на основе глобального fetch (Node.js 18+). Обрабатывает нормализацию URL, сериализацию тела и очистку потока.
|
|
3
|
+
* @en Transport based on the global fetch API (Node.js 18+). Handles URL normalisation, body serialisation, and stream cleanup.
|
|
4
|
+
*/
|
|
1
5
|
import type { HttpClientOptions, HyperTransport, TransportRequest, TransportResponse } from "@hyperttp/types";
|
|
2
6
|
export interface NodeTransportConfig extends HttpClientOptions {
|
|
3
7
|
baseUrl?: string;
|
|
4
|
-
network?: {
|
|
5
|
-
maxConcurrent?: number;
|
|
6
|
-
keepAliveTimeout?: number;
|
|
7
|
-
};
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* @ru Реализация транспорта для Node.js с использованием fetch.
|
|
11
|
+
* @en Node.js transport implementation using fetch.
|
|
12
|
+
*/
|
|
9
13
|
export declare class NodeTransport implements HyperTransport {
|
|
14
|
+
/**
|
|
15
|
+
* @ru Конфигурация клиента.
|
|
16
|
+
* @en Client configuration.
|
|
17
|
+
*/
|
|
10
18
|
config: NodeTransportConfig;
|
|
11
|
-
private readonly
|
|
12
|
-
private readonly
|
|
19
|
+
private readonly isProduction;
|
|
20
|
+
private readonly cleanBaseUrl;
|
|
21
|
+
/**
|
|
22
|
+
* @ru Создаёт экземпляр NodeTransport.
|
|
23
|
+
* @en Creates a NodeTransport instance.
|
|
24
|
+
* @param config - Transport configuration (may include baseUrl).
|
|
25
|
+
*/
|
|
13
26
|
constructor(config: NodeTransportConfig);
|
|
14
|
-
|
|
27
|
+
/**
|
|
28
|
+
* @ru Выполняет HTTP-запрос через fetch.
|
|
29
|
+
* @en Executes an HTTP request via fetch.
|
|
30
|
+
* @param req - Transport request object (method, url, headers, body, signal).
|
|
31
|
+
* @returns Promise resolving to a TransportResponse.
|
|
32
|
+
* @throws If a localhost URL is requested in production environment.
|
|
33
|
+
*/
|
|
15
34
|
execute(req: TransportRequest): Promise<TransportResponse>;
|
|
16
|
-
close(): Promise<void>;
|
|
17
|
-
destroy(): Promise<void>;
|
|
18
35
|
}
|
|
19
36
|
//# sourceMappingURL=node.d.ts.map
|
package/transports/node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/transports/node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/transports/node.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD;;;OAGG;IACI,MAAM,EAAE,mBAAmB,CAAC;IAEnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC;;;;OAIG;gBACS,MAAM,EAAE,mBAAmB;IAQvC;;;;;;OAMG;IACU,OAAO,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAmDxE"}
|
package/transports/node.js
CHANGED
|
@@ -1,148 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @ru Транспорт на основе глобального fetch (Node.js 18+). Обрабатывает нормализацию URL, сериализацию тела и очистку потока.
|
|
3
|
+
* @en Transport based on the global fetch API (Node.js 18+). Handles URL normalisation, body serialisation, and stream cleanup.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @ru Реализация транспорта для Node.js с использованием fetch.
|
|
7
|
+
* @en Node.js transport implementation using fetch.
|
|
8
|
+
*/
|
|
5
9
|
export class NodeTransport {
|
|
10
|
+
/**
|
|
11
|
+
* @ru Конфигурация клиента.
|
|
12
|
+
* @en Client configuration.
|
|
13
|
+
*/
|
|
6
14
|
config;
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
isProduction;
|
|
16
|
+
cleanBaseUrl;
|
|
17
|
+
/**
|
|
18
|
+
* @ru Создаёт экземпляр NodeTransport.
|
|
19
|
+
* @en Creates a NodeTransport instance.
|
|
20
|
+
* @param config - Transport configuration (may include baseUrl).
|
|
21
|
+
*/
|
|
9
22
|
constructor(config) {
|
|
10
23
|
this.config = config;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
maxSockets: config.network?.maxConcurrent ?? 500,
|
|
15
|
-
maxFreeSockets: Math.min(256, Math.floor((config.network?.maxConcurrent ?? 500) / 2)),
|
|
16
|
-
scheduling: "lifo",
|
|
17
|
-
};
|
|
18
|
-
this.httpAgent = new http.Agent(agentOptions);
|
|
19
|
-
this.httpsAgent = new https.Agent(agentOptions);
|
|
20
|
-
}
|
|
21
|
-
get baseUrl() {
|
|
22
|
-
return this.config.baseUrl ?? "http://localhost:3000";
|
|
24
|
+
this.isProduction = process.env.NODE_ENV === "production";
|
|
25
|
+
const base = config.baseUrl ?? "http://localhost:3000";
|
|
26
|
+
this.cleanBaseUrl = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
23
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* @ru Выполняет HTTP-запрос через fetch.
|
|
30
|
+
* @en Executes an HTTP request via fetch.
|
|
31
|
+
* @param req - Transport request object (method, url, headers, body, signal).
|
|
32
|
+
* @returns Promise resolving to a TransportResponse.
|
|
33
|
+
* @throws If a localhost URL is requested in production environment.
|
|
34
|
+
*/
|
|
24
35
|
async execute(req) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
const urlStr = req.url;
|
|
37
|
+
const fullUrl = urlStr.startsWith("http")
|
|
38
|
+
? urlStr
|
|
39
|
+
: urlStr.charCodeAt(0) === 47
|
|
40
|
+
? this.cleanBaseUrl + urlStr
|
|
41
|
+
: this.cleanBaseUrl + "/" + urlStr;
|
|
42
|
+
if (this.isProduction && fullUrl.includes("//localhost")) {
|
|
28
43
|
throw new Error("Localhost URL detected in production environment");
|
|
29
44
|
}
|
|
30
|
-
if (req.signal?.aborted) {
|
|
31
|
-
const abortError = req.signal.reason || new Error("The operation was aborted.");
|
|
32
|
-
if (abortError instanceof Error && !abortError.name)
|
|
33
|
-
abortError.name = "AbortError";
|
|
34
|
-
throw abortError;
|
|
35
|
-
}
|
|
36
|
-
// Клонируем заголовки, чтобы не мутировать исходный объект запроса
|
|
37
|
-
const headers = { ...req.headers };
|
|
38
|
-
// Хелпер для регистронезависимой проверки заголовков
|
|
39
|
-
const hasHeader = (name) => Object.keys(headers).some((k) => k.toLowerCase() === name.toLowerCase());
|
|
40
45
|
let finalBody = req.body;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
else if (typeof finalBody === "string") {
|
|
49
|
-
if (!hasHeader("content-length")) {
|
|
50
|
-
headers["Content-Length"] = String(Buffer.byteLength(finalBody, "utf-8"));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else if (!(finalBody instanceof ReadableStream)) {
|
|
54
|
-
// Сериализуем объект один раз здесь
|
|
55
|
-
finalBody = JSON.stringify(finalBody);
|
|
56
|
-
if (!hasHeader("content-length")) {
|
|
57
|
-
headers["Content-Length"] = String(Buffer.byteLength(finalBody, "utf-8"));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
46
|
+
if (finalBody !== undefined &&
|
|
47
|
+
finalBody !== null &&
|
|
48
|
+
typeof finalBody === "object" &&
|
|
49
|
+
!Buffer.isBuffer(finalBody) &&
|
|
50
|
+
!(finalBody instanceof ReadableStream)) {
|
|
51
|
+
finalBody = JSON.stringify(finalBody);
|
|
60
52
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
let nodeResInstance = null;
|
|
67
|
-
const reqOptions = {
|
|
68
|
-
method: req.method,
|
|
69
|
-
headers: headers,
|
|
70
|
-
agent,
|
|
71
|
-
};
|
|
72
|
-
const nodeReq = requestFn(fullUrl, reqOptions, (nodeRes) => {
|
|
73
|
-
nodeResInstance = nodeRes;
|
|
74
|
-
nodeRes.on("end", () => {
|
|
75
|
-
isDone = true;
|
|
76
|
-
cleanupSignal();
|
|
77
|
-
});
|
|
78
|
-
nodeRes.on("close", () => {
|
|
79
|
-
isDone = true;
|
|
80
|
-
cleanupSignal();
|
|
81
|
-
});
|
|
82
|
-
const webStream = Readable.toWeb(nodeRes);
|
|
83
|
-
const dump = async () => {
|
|
84
|
-
isDone = true;
|
|
85
|
-
cleanupSignal();
|
|
86
|
-
nodeRes.destroy();
|
|
87
|
-
};
|
|
88
|
-
const bodyPayload = Object.assign(webStream, {
|
|
89
|
-
dump,
|
|
90
|
-
});
|
|
91
|
-
resolve({
|
|
92
|
-
status: nodeRes.statusCode ?? 200,
|
|
93
|
-
headers: nodeRes.headers,
|
|
94
|
-
url: fullUrl.toString(),
|
|
95
|
-
body: bodyPayload,
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
let onAbort;
|
|
99
|
-
if (req.signal) {
|
|
100
|
-
onAbort = () => {
|
|
101
|
-
if (isDone)
|
|
102
|
-
return;
|
|
103
|
-
isDone = true;
|
|
104
|
-
nodeReq.destroy();
|
|
105
|
-
if (nodeResInstance) {
|
|
106
|
-
nodeResInstance.destroy();
|
|
107
|
-
}
|
|
108
|
-
const err = req.signal?.reason || new Error("The operation was aborted.");
|
|
109
|
-
if (err instanceof Error && !err.name)
|
|
110
|
-
err.name = "AbortError";
|
|
111
|
-
reject(err);
|
|
112
|
-
};
|
|
113
|
-
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
114
|
-
}
|
|
115
|
-
function cleanupSignal() {
|
|
116
|
-
if (req.signal && onAbort) {
|
|
117
|
-
req.signal.removeEventListener("abort", onAbort);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
nodeReq.on("error", (err) => {
|
|
121
|
-
if (isDone)
|
|
122
|
-
return;
|
|
123
|
-
isDone = true;
|
|
124
|
-
cleanupSignal();
|
|
125
|
-
reject(err);
|
|
126
|
-
});
|
|
127
|
-
if (finalBody !== undefined && finalBody !== null) {
|
|
128
|
-
if (Buffer.isBuffer(finalBody) || typeof finalBody === "string") {
|
|
129
|
-
nodeReq.end(finalBody);
|
|
130
|
-
}
|
|
131
|
-
else if (finalBody instanceof ReadableStream) {
|
|
132
|
-
Readable.fromWeb(finalBody).pipe(nodeReq);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
nodeReq.end();
|
|
137
|
-
}
|
|
53
|
+
const res = await globalThis.fetch(fullUrl, {
|
|
54
|
+
method: req.method,
|
|
55
|
+
headers: req.headers,
|
|
56
|
+
body: finalBody,
|
|
57
|
+
signal: req.signal,
|
|
138
58
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
59
|
+
const webStream = res.body || new ReadableStream();
|
|
60
|
+
webStream.dump = async function () {
|
|
61
|
+
if (res.body && !res.body.locked) {
|
|
62
|
+
await res.body.cancel();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const resHeaders = {};
|
|
66
|
+
for (const [key, value] of res.headers) {
|
|
67
|
+
resHeaders[key] = value;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
status: res.status,
|
|
71
|
+
headers: resHeaders,
|
|
72
|
+
url: res.url,
|
|
73
|
+
body: webStream,
|
|
74
|
+
};
|
|
146
75
|
}
|
|
147
76
|
}
|
|
148
77
|
//# sourceMappingURL=node.js.map
|
package/transports/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/transports/node.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/transports/node.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH;;;GAGG;AACH,MAAM,OAAO,aAAa;IACxB;;;OAGG;IACI,MAAM,CAAsB;IAElB,YAAY,CAAU;IACtB,YAAY,CAAS;IAEtC;;;;OAIG;IACH,YAAY,MAA2B;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;QAE1D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,IAAI,uBAAuB,CAAC;QACvD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpE,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,OAAO,CAAC,GAAqB;QACxC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC;QAEvB,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;YACvC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE;gBAC3B,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,MAAM;gBAC5B,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC;QAEvC,IAAI,IAAI,CAAC,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,SAAS,GAAQ,GAAG,CAAC,IAAI,CAAC;QAC9B,IACE,SAAS,KAAK,SAAS;YACvB,SAAS,KAAK,IAAI;YAClB,OAAO,SAAS,KAAK,QAAQ;YAC7B,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;YAC3B,CAAC,CAAC,SAAS,YAAY,cAAc,CAAC,EACtC,CAAC;YACD,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE;YAC1C,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,OAAiC;YAC9C,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,IAAI,IAAI,cAAc,EAAE,CAAC;QAElD,SAAiB,CAAC,IAAI,GAAG,KAAK;YAC7B,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACjC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,UAAU,GAA2B,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YACvC,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC1B,CAAC;QAED,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,UAAU;YACnB,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,IAAI,EAAE,SAAgD;SACvD,CAAC;IACJ,CAAC;CACF"}
|