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