@peaje/proxy 0.1.0

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.
@@ -0,0 +1,422 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/express.ts
21
+ var express_exports = {};
22
+ __export(express_exports, {
23
+ default: () => express_default,
24
+ peajeExpress: () => peajeExpress
25
+ });
26
+ module.exports = __toCommonJS(express_exports);
27
+ var import_node_stream = require("stream");
28
+
29
+ // ../shared/src/kit/rutas.ts
30
+ var RUTAS_PROXY = [
31
+ { path: "/llms.txt", nota: "gu\xEDa para agentes (fusionada con la tuya)" },
32
+ { path: "/pricing.md", nota: "precios por endpoint" },
33
+ { path: "/auth.md", nota: 'c\xF3mo pagar (el "auth" ac\xE1 es el pago)' },
34
+ { path: "/agents.md", nota: "qu\xE9 es y cu\xE1ndo usarte" },
35
+ { path: "/openapi.json", nota: "discovery con precios" },
36
+ { path: "/mcp", nota: "MCP server con tools pagas, en TU dominio" },
37
+ { path: "/.well-known/mcp", nota: "el MCP tambi\xE9n ac\xE1: es donde lo buscan los auditores" },
38
+ { path: "/.well-known/ard.json", nota: "cat\xE1logo ARD (path can\xF3nico)" },
39
+ { path: "/.well-known/ai-catalog.json", nota: "cat\xE1logo ARD (alias legacy)" },
40
+ { path: "/.well-known/api-catalog", nota: "linkset RFC 9727" },
41
+ { path: "/.well-known/agent-card.json", nota: "agent card A2A" },
42
+ { path: "/.well-known/mcp/server-card.json", nota: "server card MCP" },
43
+ { path: "/discovery/resources", nota: "lista x402 Bazaar: lo que cobra 402 de verdad" },
44
+ { path: "/.well-known/ucp", nota: "perfil UCP: qu\xE9 sabe hacer tu negocio" },
45
+ { path: "/checkout_sessions", nota: "checkout ACP (el de OpenAI)" },
46
+ { path: "/agentic_commerce/delegate_payment", nota: "delegate payment ACP" },
47
+ { path: "/developers", nota: "portal de desarrolladores, renderizado con tus precios" },
48
+ { path: "/.well-known/peaje-indexnow.txt", nota: "clave IndexNow: avisa a Bing y Copilot cuando cambian precios" },
49
+ // Los auditores prueban /docs y /developers; el gateway redirige uno al otro.
50
+ { path: "/docs", nota: "alias del portal: los auditores prueban ambos" }
51
+ ];
52
+ var RUTAS_DINAMICAS = [
53
+ { path: "/r/:slug", prefijo: "/r/", nota: "links con precio: el 402 en tu dominio" },
54
+ { path: "/checkout_sessions/:id", prefijo: "/checkout_sessions/", nota: "leer una sesi\xF3n de checkout ACP" }
55
+ ];
56
+ var PEAJE_FETCH_HEADER = "x-peaje-fetch";
57
+
58
+ // ../shared/src/kit/proxy-runtime.ts
59
+ var PEAJE_ORIGIN_HEADER = "x-peaje-origin";
60
+ function iguales(a, b) {
61
+ if (a.length !== b.length) return false;
62
+ let d = 0;
63
+ for (let i = 0; i < a.length; i++) d |= a.charCodeAt(i) ^ b.charCodeAt(i);
64
+ return d === 0;
65
+ }
66
+ function coincidePatron(patron, pathname) {
67
+ const p = patron.split("/").filter(Boolean);
68
+ const parts = segmentosCanonicos(pathname);
69
+ for (let i = 0; i < p.length; i++) {
70
+ const seg = p[i];
71
+ if (seg === "*") return true;
72
+ const v = parts[i];
73
+ if (v === void 0) return false;
74
+ if (seg.startsWith(":")) continue;
75
+ if (seg.toLowerCase() !== v.toLowerCase()) return false;
76
+ }
77
+ return p.length === parts.length;
78
+ }
79
+ function segmentosCanonicos(pathname) {
80
+ const out = [];
81
+ for (const crudo of pathname.split(/[\\/]+/)) {
82
+ if (!crudo) continue;
83
+ let seg = crudo;
84
+ try {
85
+ seg = decodeURIComponent(crudo);
86
+ } catch {
87
+ }
88
+ for (const s of seg.split(/[\\/]+/)) {
89
+ if (!s || s === ".") continue;
90
+ if (s === "..") out.pop();
91
+ else out.push(s);
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+ var pathCanonico = (pathname) => `/${segmentosCanonicos(pathname).join("/")}`;
97
+ function patronReenviable(patron) {
98
+ const segs = patron.split("/").filter(Boolean);
99
+ if (segs.length === 0) return false;
100
+ return !(segs.length === 1 && (segs[0] === "*" || segs[0].startsWith(":")));
101
+ }
102
+ function crearProxyRuntime(opciones) {
103
+ const base = opciones.base.replace(/\/+$/, "");
104
+ const ttl = opciones.ttlMs ?? 6e4;
105
+ const fetchImpl = opciones.fetchImpl ?? fetch;
106
+ let cache = {
107
+ at: 0,
108
+ rutas: opciones.respaldo?.rutas ?? [],
109
+ prefijos: opciones.respaldo?.prefijos ?? [],
110
+ patrones: opciones.respaldo?.patrones ?? []
111
+ };
112
+ let refrescando = null;
113
+ async function refrescar() {
114
+ try {
115
+ const res = await fetchImpl(`${base}/kit/manifest.json`, { signal: AbortSignal.timeout(5e3) });
116
+ if (!res.ok) throw new Error(String(res.status));
117
+ const m = await res.json();
118
+ cache = {
119
+ at: Date.now(),
120
+ rutas: Array.isArray(m.rutas) ? m.rutas : cache.rutas,
121
+ prefijos: Array.isArray(m.prefijos) ? m.prefijos : cache.prefijos,
122
+ patrones: Array.isArray(m.patrones) ? m.patrones : cache.patrones
123
+ };
124
+ } catch {
125
+ cache = { ...cache, at: Date.now() };
126
+ } finally {
127
+ refrescando = null;
128
+ }
129
+ }
130
+ async function listas() {
131
+ const vencido = Date.now() - cache.at >= ttl;
132
+ if (vencido) {
133
+ refrescando ??= refrescar();
134
+ if (cache.at === 0) await refrescando;
135
+ }
136
+ return cache;
137
+ }
138
+ return {
139
+ /** Rutas de API con precio que este sitio no puede cobrar por falta de secreto. */
140
+ async sinSecreto() {
141
+ if (opciones.secreto) return [];
142
+ return (await listas()).patrones.filter((p) => patronReenviable(p.path));
143
+ },
144
+ /** Fuerza la carga del manifiesto (útil al arrancar o en tests). */
145
+ cargar: () => refrescando ??= refrescar(),
146
+ /**
147
+ * Decide si la request va al gateway. `method` y `pathname` de la request,
148
+ * `search` con el `?` incluido, y los headers para cortar bucles.
149
+ */
150
+ async decidir(input) {
151
+ const secreto = opciones.secreto || null;
152
+ const prueba = input.headers.get(PEAJE_ORIGIN_HEADER);
153
+ if (secreto && prueba && iguales(prueba, secreto)) return null;
154
+ const { rutas, prefijos, patrones } = await listas();
155
+ const path = input.pathname;
156
+ const canon = pathCanonico(path).toLowerCase();
157
+ const esFetchDePeaje = input.headers.get(PEAJE_FETCH_HEADER) !== null;
158
+ const fija = (rutas.includes(path) || rutas.some((r) => r.toLowerCase() === canon)) && !(canon === "/llms.txt" && esFetchDePeaje);
159
+ const prefijo = prefijos.some((p) => path.startsWith(p) || canon.startsWith(p.toLowerCase()));
160
+ const method = input.method.toUpperCase();
161
+ const patron = secreto !== null && patrones.some((p) => p.method.toUpperCase() === method && patronReenviable(p.path) && coincidePatron(p.path, path));
162
+ if (!fija && !prefijo && !patron) return null;
163
+ return { destino: `${base}${path}${input.search ?? ""}` };
164
+ }
165
+ };
166
+ }
167
+
168
+ // ../shared/src/index.ts
169
+ var TEMPO = {
170
+ mainnet: {
171
+ chainId: 4217,
172
+ rpcUrl: "https://rpc.tempo.xyz",
173
+ explorerUrl: "https://explore.tempo.xyz"
174
+ },
175
+ testnet: {
176
+ chainId: 42431,
177
+ rpcUrl: "https://rpc.moderato.tempo.xyz",
178
+ explorerUrl: "https://explore.testnet.tempo.xyz"
179
+ }
180
+ };
181
+ var TOKENS = {
182
+ pathUsd: "0x20c0000000000000000000000000000000000000",
183
+ usdc: "0x20C000000000000000000000b9537d11c60E8b50"
184
+ };
185
+ var DEFAULT_CURRENCY = TOKENS.pathUsd;
186
+ var NETWORKS = {
187
+ tempo: {
188
+ id: "tempo",
189
+ label: "Tempo",
190
+ token: TOKENS.pathUsd,
191
+ tokenSymbol: "pathUSD",
192
+ decimals: 6,
193
+ mainnet: TEMPO.mainnet,
194
+ testnet: TEMPO.testnet
195
+ },
196
+ arc: {
197
+ id: "arc",
198
+ label: "Arc",
199
+ // Interfaz ERC-20 del USDC nativo de Arc; el gas de la red se paga en ese mismo USDC.
200
+ token: "0x3600000000000000000000000000000000000000",
201
+ tokenSymbol: "USDC",
202
+ decimals: 6,
203
+ // Leído on-chain del contrato: name() = "USDC", version() = "2".
204
+ eip3009: { name: "USDC", version: "2" },
205
+ // Arc no publica direcciones de mainnet todavía (sept 2026).
206
+ mainnet: null,
207
+ testnet: {
208
+ chainId: 5042002,
209
+ rpcUrl: "https://rpc.testnet.arc.io",
210
+ explorerUrl: "https://testnet.arcscan.app"
211
+ }
212
+ },
213
+ arbitrum: {
214
+ id: "arbitrum",
215
+ label: "Arbitrum",
216
+ // USDC de Circle en Arbitrum Sepolia. En Arbitrum One vive en otra dirección,
217
+ // por eso mainnet queda en null hasta que el gateway opere allá.
218
+ token: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
219
+ tokenSymbol: "USDC",
220
+ decimals: 6,
221
+ // Leído on-chain: name() = "USD Coin", version() = "2" (distinto al USDC de Arc).
222
+ eip3009: { name: "USD Coin", version: "2" },
223
+ mainnet: null,
224
+ testnet: {
225
+ chainId: 421614,
226
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
227
+ explorerUrl: "https://sepolia.arbiscan.io"
228
+ }
229
+ },
230
+ "arbitrum-usdg": {
231
+ id: "arbitrum-usdg",
232
+ label: "Arbitrum \xB7 USDG",
233
+ // Mismo contrato y misma cadena que el riel USDC: cambia el stablecoin.
234
+ // Un riel es (cadena, token), y el saldo del negocio se lleva por token.
235
+ token: "0xFFC95faa3d63Cde504a05B567C600B78C0b41892",
236
+ tokenSymbol: "USDG",
237
+ decimals: 6,
238
+ eip3009: { name: "Global Dollar", version: "1" },
239
+ mainnet: null,
240
+ testnet: {
241
+ chainId: 421614,
242
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
243
+ explorerUrl: "https://sepolia.arbiscan.io"
244
+ }
245
+ },
246
+ robinhood: {
247
+ id: "robinhood",
248
+ label: "Robinhood",
249
+ // USDG de Paxos en Robinhood Chain Testnet (el "USDC" de esa testnet es un
250
+ // mock sin EIP-3009). Leído on-chain: dominio "Global Dollar", versión "1".
251
+ token: "0x7E955252E15c84f5768B83c41a71F9eba181802F",
252
+ tokenSymbol: "USDG",
253
+ decimals: 6,
254
+ eip3009: { name: "Global Dollar", version: "1" },
255
+ mainnet: null,
256
+ testnet: {
257
+ chainId: 46630,
258
+ rpcUrl: "https://rpc.testnet.chain.robinhood.com",
259
+ explorerUrl: "https://explorer.testnet.chain.robinhood.com"
260
+ }
261
+ }
262
+ };
263
+ var NETWORK_IDS = Object.keys(NETWORKS);
264
+ var CHAIN_LABELS = [
265
+ ...new Map(NETWORK_IDS.map((id) => [NETWORKS[id].testnet.chainId, NETWORKS[id].label.split(" \xB7 ")[0] ?? NETWORKS[id].label])).values()
266
+ ];
267
+ var GATEWAY_RAILS = {
268
+ "gateway-arbitrum": {
269
+ id: "gateway-arbitrum",
270
+ label: "Arbitrum \xB7 Nanopayments",
271
+ chainId: 421614,
272
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
273
+ token: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
274
+ decimals: 6,
275
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
276
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
277
+ sdkChain: "arbitrumSepolia",
278
+ explorerUrl: "https://sepolia.arbiscan.io"
279
+ },
280
+ "gateway-arc": {
281
+ id: "gateway-arc",
282
+ label: "Arc \xB7 Nanopayments",
283
+ chainId: 5042002,
284
+ rpcUrl: "https://rpc.testnet.arc.network",
285
+ token: "0x3600000000000000000000000000000000000000",
286
+ decimals: 6,
287
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
288
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
289
+ sdkChain: "arcTestnet",
290
+ explorerUrl: "https://testnet.arcscan.app"
291
+ }
292
+ };
293
+ var GATEWAY_RAIL_IDS = Object.keys(GATEWAY_RAILS);
294
+
295
+ // src/index.ts
296
+ var GATEWAY_POR_DEFECTO = "https://peaje-gateway.up.railway.app";
297
+ function env(nombre) {
298
+ return typeof process !== "undefined" ? process.env?.[nombre] : void 0;
299
+ }
300
+ function baseDe({ slug, gateway }) {
301
+ const raiz = (gateway ?? env("PEAJE_GATEWAY_URL") ?? GATEWAY_POR_DEFECTO).replace(/\/+$/, "");
302
+ return `${raiz}/${slug}`;
303
+ }
304
+ var SALTO = ["host", "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "te", "trailer", "content-length"];
305
+ var avisado = false;
306
+ function crearPeaje(opciones) {
307
+ if (!opciones?.slug) throw new Error("@peaje/proxy: `slug` is required (the business slug from your Peaje dashboard).");
308
+ const base = baseDe(opciones);
309
+ const secreto = opciones.secret ?? env("PEAJE_ORIGIN_SECRET") ?? null;
310
+ const fetchImpl = opciones.fetch ?? ((...a) => fetch(...a));
311
+ const runtime = crearProxyRuntime({
312
+ base,
313
+ secreto,
314
+ fetchImpl,
315
+ respaldo: { rutas: RUTAS_PROXY.map((x) => x.path), prefijos: RUTAS_DINAMICAS.map((x) => x.prefijo), patrones: [] }
316
+ });
317
+ return {
318
+ base,
319
+ async decidir({ method, url, headers }) {
320
+ if (!secreto && !avisado) {
321
+ avisado = true;
322
+ void runtime.sinSecreto().then((faltan) => {
323
+ if (faltan.length > 0) {
324
+ console.warn(
325
+ `[peaje] ${faltan.length} priced API route(s) are not charged on this domain because PEAJE_ORIGIN_SECRET is not set: ${faltan.map((p) => `${p.method} ${p.path}`).join(", ")}. Copy it from the Kit page of your Peaje dashboard into your hosting environment variables.`
326
+ );
327
+ }
328
+ });
329
+ }
330
+ const d = await runtime.decidir({ method, pathname: url.pathname, search: url.search, headers });
331
+ return d?.destino ?? null;
332
+ },
333
+ async reenviar(destino, { method, url, headers, body }) {
334
+ const salida = new Headers(headers);
335
+ for (const h of SALTO) salida.delete(h);
336
+ const primero = (h) => headers.get(h)?.split(",")[0]?.trim() || null;
337
+ salida.set("x-forwarded-host", primero("x-forwarded-host") ?? url.host);
338
+ salida.set("x-forwarded-proto", primero("x-forwarded-proto") ?? url.protocol.replace(":", ""));
339
+ const sinBody = method === "GET" || method === "HEAD";
340
+ const res = await fetchImpl(destino, { method, headers: salida, body: sinBody ? null : body, redirect: "manual" });
341
+ const vuelta = new Headers(res.headers);
342
+ vuelta.delete("content-encoding");
343
+ vuelta.delete("content-length");
344
+ vuelta.delete("transfer-encoding");
345
+ vuelta.delete("connection");
346
+ return new Response(method === "HEAD" ? null : res.body, { status: res.status, statusText: res.statusText, headers: vuelta });
347
+ }
348
+ };
349
+ }
350
+
351
+ // src/express.ts
352
+ function headersDe(req) {
353
+ const h = new Headers();
354
+ for (const [clave, valor] of Object.entries(req.headers)) {
355
+ if (valor === void 0) continue;
356
+ if (Array.isArray(valor)) for (const v of valor) h.append(clave, v);
357
+ else h.set(clave, valor);
358
+ }
359
+ return h;
360
+ }
361
+ function urlDe(req) {
362
+ const cifrado = req.socket?.encrypted;
363
+ const proto = req.protocol ?? (cifrado ? "https" : "http");
364
+ const host = req.headers.host ?? "localhost";
365
+ return new URL(req.originalUrl ?? req.url ?? "/", `${proto}://${host}`);
366
+ }
367
+ async function bodyDe(req) {
368
+ if (req.readableEnded || req._body) {
369
+ const b = req.body;
370
+ if (b === void 0 || b === null) return null;
371
+ if (typeof b === "string" || b instanceof Uint8Array) return b;
372
+ if (typeof b === "object" && Object.keys(b).length === 0) return null;
373
+ return JSON.stringify(b);
374
+ }
375
+ const partes = [];
376
+ for await (const parte of req) partes.push(typeof parte === "string" ? Buffer.from(parte) : parte);
377
+ return partes.length > 0 ? Buffer.concat(partes) : null;
378
+ }
379
+ async function enviar(res, respuesta) {
380
+ res.statusCode = respuesta.status;
381
+ if (respuesta.statusText) res.statusMessage = respuesta.statusText;
382
+ const cookies = respuesta.headers.getSetCookie?.() ?? [];
383
+ respuesta.headers.forEach((valor, clave) => {
384
+ if (clave === "set-cookie") return;
385
+ res.setHeader(clave, valor);
386
+ });
387
+ if (cookies.length > 0) res.setHeader("set-cookie", cookies);
388
+ if (!respuesta.body) {
389
+ res.end();
390
+ return;
391
+ }
392
+ await new Promise((resolve, reject) => {
393
+ const cuerpo = import_node_stream.Readable.fromWeb(respuesta.body);
394
+ cuerpo.on("error", reject);
395
+ res.on("finish", resolve);
396
+ res.on("close", resolve);
397
+ cuerpo.pipe(res);
398
+ });
399
+ }
400
+ function peajeExpress(opciones) {
401
+ const peaje = crearPeaje(opciones);
402
+ return (req, res, next) => {
403
+ const r = req;
404
+ const entrada = { method: (r.method ?? "GET").toUpperCase(), url: urlDe(r), headers: headersDe(r) };
405
+ peaje.decidir(entrada).then(async (destino) => {
406
+ if (!destino) return next();
407
+ const body = entrada.method === "GET" || entrada.method === "HEAD" ? null : await bodyDe(r);
408
+ await enviar(res, await peaje.reenviar(destino, { ...entrada, body }));
409
+ }).catch((e) => {
410
+ if (res.headersSent) {
411
+ res.destroy(e instanceof Error ? e : void 0);
412
+ return;
413
+ }
414
+ next(e);
415
+ });
416
+ };
417
+ }
418
+ var express_default = peajeExpress;
419
+ // Annotate the CommonJS export names for ESM import in node:
420
+ 0 && (module.exports = {
421
+ peajeExpress
422
+ });
@@ -0,0 +1,19 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { OpcionesPeaje } from './index.cjs';
3
+
4
+ /**
5
+ * `@peaje/proxy/express`: middleware de Express (4 y 5) o Connect.
6
+ *
7
+ * import { peajeExpress } from '@peaje/proxy/express'
8
+ * app.use(peajeExpress({ slug: 'mi-negocio' }))
9
+ *
10
+ * Va antes de los body parsers (`express.json()`) y de las rutas: el body de
11
+ * una request de Peaje se reenvía crudo al gateway, y el de las demás no se
12
+ * toca. Si igual corre después de un parser, reenvía `req.body` serializado.
13
+ */
14
+
15
+ type Next = (err?: unknown) => void;
16
+ type MiddlewareExpress = (req: IncomingMessage, res: ServerResponse, next: Next) => void;
17
+ declare function peajeExpress(opciones: OpcionesPeaje): MiddlewareExpress;
18
+
19
+ export { type MiddlewareExpress, OpcionesPeaje, peajeExpress as default, peajeExpress };
@@ -0,0 +1,19 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { OpcionesPeaje } from './index.js';
3
+
4
+ /**
5
+ * `@peaje/proxy/express`: middleware de Express (4 y 5) o Connect.
6
+ *
7
+ * import { peajeExpress } from '@peaje/proxy/express'
8
+ * app.use(peajeExpress({ slug: 'mi-negocio' }))
9
+ *
10
+ * Va antes de los body parsers (`express.json()`) y de las rutas: el body de
11
+ * una request de Peaje se reenvía crudo al gateway, y el de las demás no se
12
+ * toca. Si igual corre después de un parser, reenvía `req.body` serializado.
13
+ */
14
+
15
+ type Next = (err?: unknown) => void;
16
+ type MiddlewareExpress = (req: IncomingMessage, res: ServerResponse, next: Next) => void;
17
+ declare function peajeExpress(opciones: OpcionesPeaje): MiddlewareExpress;
18
+
19
+ export { type MiddlewareExpress, OpcionesPeaje, peajeExpress as default, peajeExpress };
@@ -0,0 +1,77 @@
1
+ import {
2
+ crearPeaje
3
+ } from "./chunk-BYMBIN6F.js";
4
+
5
+ // src/express.ts
6
+ import { Readable } from "stream";
7
+ function headersDe(req) {
8
+ const h = new Headers();
9
+ for (const [clave, valor] of Object.entries(req.headers)) {
10
+ if (valor === void 0) continue;
11
+ if (Array.isArray(valor)) for (const v of valor) h.append(clave, v);
12
+ else h.set(clave, valor);
13
+ }
14
+ return h;
15
+ }
16
+ function urlDe(req) {
17
+ const cifrado = req.socket?.encrypted;
18
+ const proto = req.protocol ?? (cifrado ? "https" : "http");
19
+ const host = req.headers.host ?? "localhost";
20
+ return new URL(req.originalUrl ?? req.url ?? "/", `${proto}://${host}`);
21
+ }
22
+ async function bodyDe(req) {
23
+ if (req.readableEnded || req._body) {
24
+ const b = req.body;
25
+ if (b === void 0 || b === null) return null;
26
+ if (typeof b === "string" || b instanceof Uint8Array) return b;
27
+ if (typeof b === "object" && Object.keys(b).length === 0) return null;
28
+ return JSON.stringify(b);
29
+ }
30
+ const partes = [];
31
+ for await (const parte of req) partes.push(typeof parte === "string" ? Buffer.from(parte) : parte);
32
+ return partes.length > 0 ? Buffer.concat(partes) : null;
33
+ }
34
+ async function enviar(res, respuesta) {
35
+ res.statusCode = respuesta.status;
36
+ if (respuesta.statusText) res.statusMessage = respuesta.statusText;
37
+ const cookies = respuesta.headers.getSetCookie?.() ?? [];
38
+ respuesta.headers.forEach((valor, clave) => {
39
+ if (clave === "set-cookie") return;
40
+ res.setHeader(clave, valor);
41
+ });
42
+ if (cookies.length > 0) res.setHeader("set-cookie", cookies);
43
+ if (!respuesta.body) {
44
+ res.end();
45
+ return;
46
+ }
47
+ await new Promise((resolve, reject) => {
48
+ const cuerpo = Readable.fromWeb(respuesta.body);
49
+ cuerpo.on("error", reject);
50
+ res.on("finish", resolve);
51
+ res.on("close", resolve);
52
+ cuerpo.pipe(res);
53
+ });
54
+ }
55
+ function peajeExpress(opciones) {
56
+ const peaje = crearPeaje(opciones);
57
+ return (req, res, next) => {
58
+ const r = req;
59
+ const entrada = { method: (r.method ?? "GET").toUpperCase(), url: urlDe(r), headers: headersDe(r) };
60
+ peaje.decidir(entrada).then(async (destino) => {
61
+ if (!destino) return next();
62
+ const body = entrada.method === "GET" || entrada.method === "HEAD" ? null : await bodyDe(r);
63
+ await enviar(res, await peaje.reenviar(destino, { ...entrada, body }));
64
+ }).catch((e) => {
65
+ if (res.headersSent) {
66
+ res.destroy(e instanceof Error ? e : void 0);
67
+ return;
68
+ }
69
+ next(e);
70
+ });
71
+ };
72
+ }
73
+ var express_default = peajeExpress;
74
+ export {
75
+ express_default as default,
76
+ peajeExpress
77
+ };