@filamentjs/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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Thomas
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
14
+ OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15
+ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # @filamentjs/proxy
2
+
3
+ A strict reverse proxy for explicitly registered FilamentJS routes and trusted,
4
+ named HTTP upstreams. It is not an open forward proxy: clients cannot choose an
5
+ origin, host, protocol, or arbitrary path.
6
+
7
+ ## Key features
8
+
9
+ - Fixed named HTTP(S) upstreams and exact Filament route mappings.
10
+ - Independently encoded path parameters and configurable query forwarding.
11
+ - Conservative request/response header allowlists and no automatic redirects.
12
+ - Bounded response buffering, total timeouts, generic 502/504 errors, and
13
+ constrained pre-response retries for idempotent methods.
14
+ - Setup-time rejection of open-proxy inputs, route collisions, and unsafe
15
+ header configuration.
16
+
17
+ ## Quick start
18
+
19
+ ```sh
20
+ npm install @filamentjs/proxy filamentjs
21
+ ```
22
+
23
+ ```ts
24
+ import { createApp, type FrameworkMeta } from "filamentjs";
25
+ import { setup } from "@filamentjs/proxy";
26
+
27
+ const app = createApp<FrameworkMeta>(
28
+ { application: { maxRequestSize: "1MiB" } },
29
+ {},
30
+ );
31
+ setup(app, {
32
+ upstreams: {
33
+ catalog: { origin: "https://catalog.internal", basePath: "/v1" },
34
+ },
35
+ routes: [
36
+ {
37
+ method: "GET",
38
+ path: "/products/:id",
39
+ upstream: "catalog",
40
+ upstreamPath: "/items/:id",
41
+ },
42
+ ],
43
+ });
44
+ ```
45
+
46
+ Requires Node 24+ and the exact supported peer `filamentjs@0.6.0`.
47
+
48
+ ## How it works and options
49
+
50
+ Named parameters are independently percent-encoded. Query fields are forwarded
51
+ by default and can be disabled per mapping. Filament has no wildcard route, so
52
+ every proxy path must be registered explicitly.
53
+
54
+ The default request allowlist is `Accept`, `Accept-Language`, `Content-Type`,
55
+ and `User-Agent`. Authorization and cookies require deliberate allowlisting;
56
+ host, forwarding identity, content length, and hop-by-hop fields are always
57
+ rejected. Responses use a small end-to-end allowlist and omit `Location` and
58
+ `Set-Cookie` by default. Redirects are returned without being followed.
59
+
60
+ Responses are buffered up to 8 MiB by default so upstream failure cannot leak a
61
+ partial downstream response. The default total timeout is 10 seconds. Failures
62
+ return generic 502/504 bodies and go to `onError`. Retries default to zero, are
63
+ limited to two, and are permitted only for GET/HEAD before response bytes are
64
+ sent. Incoming bodies are available only for the methods Filament buffers.
65
+
66
+ Downstream disconnect cancellation and truly streaming proxy responses require
67
+ additional Filament lifecycle support and are not claimed by this release.
68
+
69
+ ### Public API
70
+
71
+ | Surface | Meaning |
72
+ | --- | --- |
73
+ | `setup(app, options)` | Validates mappings and registers exact proxy routes; returns registered paths. |
74
+ | `SetupOptions` | Trusted upstream map, route list, optional fetch implementation and error hook. |
75
+ | `Upstream` | Fixed HTTP(S) origin, optional base path, and trusted added headers. |
76
+ | `ProxyRoute` | Method/path mapping, templates, query/header policy, timeout, size, retries, and metadata. |
77
+
78
+ Proxy handlers own the response and therefore stop later routes/transformers.
79
+ Authentication/authorization should run before a sensitive proxy route; rate
80
+ limiting may run before or after authentication depending on whether the key is
81
+ safe. A 502 denotes an upstream/protocol failure and 504 a timeout. No response
82
+ bytes are committed before the bounded upstream response succeeds.
83
+
84
+ ## Development and demo
85
+
86
+ From a source checkout:
87
+
88
+ ```sh
89
+ npm test
90
+ npm run example
91
+ npm run demo
92
+ ```
93
+
94
+ The unattended demo creates a loopback upstream, proxies a live parameterized
95
+ request, prints the upstream-observed path, closes both servers, and exits. No
96
+ external service is contacted. There is no pre-0.1 migration contract.
97
+
98
+ ## License
99
+
100
+ ISC
@@ -0,0 +1,3 @@
1
+ export { setup } from "./src/proxy.js";
2
+ export type { ProxyRoute, SetupOptions, SetupResult, Upstream, } from "./src/types.js";
3
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,YAAY,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,GACT,MAAM,gBAAgB,CAAC"}
package/dist/proxy.js ADDED
@@ -0,0 +1,2 @@
1
+ export { setup } from "./src/proxy.js";
2
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Application, ContextMeta, FrameworkMeta } from "filamentjs";
2
+ import type { SetupOptions, SetupResult } from "./types.js";
3
+ export declare function setup<A extends FrameworkMeta, C extends ContextMeta>(app: Application<A, C>, options: SetupOptions<A>): SetupResult;
4
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,aAAa,EAGd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAEV,YAAY,EACZ,WAAW,EAEZ,MAAM,YAAY,CAAC;AA+KpB,wBAAgB,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,SAAS,WAAW,EAClE,GAAG,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GACvB,WAAW,CA2Hb"}
@@ -0,0 +1,235 @@
1
+ const installed = new WeakSet();
2
+ const HOP_BY_HOP = new Set([
3
+ "connection",
4
+ "keep-alive",
5
+ "proxy-authenticate",
6
+ "proxy-authorization",
7
+ "te",
8
+ "trailer",
9
+ "transfer-encoding",
10
+ "upgrade",
11
+ ]);
12
+ const NEVER_FORWARD_REQUEST = new Set([
13
+ ...HOP_BY_HOP,
14
+ "host",
15
+ "forwarded",
16
+ "x-forwarded-for",
17
+ "x-forwarded-host",
18
+ "x-forwarded-proto",
19
+ "content-length",
20
+ ]);
21
+ const DEFAULT_REQUEST = [
22
+ "accept",
23
+ "accept-language",
24
+ "content-type",
25
+ "user-agent",
26
+ ];
27
+ const DEFAULT_RESPONSE = [
28
+ "cache-control",
29
+ "content-language",
30
+ "content-type",
31
+ "etag",
32
+ "expires",
33
+ "last-modified",
34
+ "vary",
35
+ "www-authenticate",
36
+ ];
37
+ function positiveInteger(value, label, maximum = Number.MAX_SAFE_INTEGER) {
38
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
39
+ throw new RangeError(`${label} must be a positive integer no greater than ${maximum}`);
40
+ return value;
41
+ }
42
+ function headerNames(values, response = false) {
43
+ const result = values.map((value) => value.toLowerCase());
44
+ if (result.some((value) => !/^[!#$%&'*+.^_`|~0-9a-z-]+$/.test(value) ||
45
+ HOP_BY_HOP.has(value) ||
46
+ (!response && NEVER_FORWARD_REQUEST.has(value))))
47
+ throw new TypeError("header allowlist contains a forbidden header");
48
+ return [...new Set(result)];
49
+ }
50
+ function trustedUpstream(value) {
51
+ const url = new URL(value.origin);
52
+ if (!/^https?:$/.test(url.protocol) ||
53
+ url.username ||
54
+ url.password ||
55
+ url.search ||
56
+ url.hash ||
57
+ url.pathname !== "/")
58
+ throw new TypeError("upstream origin must be a credential-free HTTP(S) origin");
59
+ const basePath = value.basePath ?? "";
60
+ if (basePath !== "" &&
61
+ (!basePath.startsWith("/") ||
62
+ basePath.includes("?") ||
63
+ basePath.includes("#") ||
64
+ basePath.split("/").some((part) => part === "." || part === "..")))
65
+ throw new TypeError("upstream basePath is unsafe");
66
+ const headers = new Headers(value.headers);
67
+ for (const name of headers.keys())
68
+ if (NEVER_FORWARD_REQUEST.has(name.toLowerCase()))
69
+ throw new TypeError("upstream headers contain a forbidden header");
70
+ return { origin: url, basePath: basePath.replace(/\/$/, ""), headers };
71
+ }
72
+ function validateTemplate(template) {
73
+ if (!template.startsWith("/") ||
74
+ template.startsWith("//") ||
75
+ template.includes("?") ||
76
+ template.includes("#") ||
77
+ template.includes("\\"))
78
+ throw new TypeError("upstreamPath must be an absolute path template");
79
+ if (template.split("/").some((part) => part === "." || part === ".."))
80
+ throw new TypeError("upstreamPath contains traversal");
81
+ }
82
+ function rewrite(template, req) {
83
+ validateTemplate(template);
84
+ const result = template.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_all, name) => {
85
+ const value = req.params[name];
86
+ if (value === undefined)
87
+ throw new Error(`Missing route parameter ${name}`);
88
+ return encodeURIComponent(value);
89
+ });
90
+ if (result.split("/").some((part) => part === "." || part === ".."))
91
+ throw new TypeError("rewritten path contains traversal");
92
+ return result;
93
+ }
94
+ function query(req) {
95
+ const params = new URLSearchParams();
96
+ for (const [name, value] of Object.entries(req.query))
97
+ for (const item of Array.isArray(value) ? value : [value])
98
+ params.append(name, item);
99
+ const text = params.toString();
100
+ return text === "" ? "" : `?${text}`;
101
+ }
102
+ function requestHeaders(req, allowed, fixed) {
103
+ const result = new Headers(fixed);
104
+ for (const name of allowed) {
105
+ const value = req.headers.get(name);
106
+ if (Array.isArray(value))
107
+ for (const item of value)
108
+ result.append(name, item);
109
+ else if (value !== null)
110
+ result.set(name, value);
111
+ }
112
+ return result;
113
+ }
114
+ async function boundedBody(response, maximum, abort) {
115
+ const declared = response.headers.get("content-length");
116
+ if (declared !== null &&
117
+ (!/^\d+$/.test(declared) || Number(declared) > maximum))
118
+ throw new RangeError("upstream response exceeds maxResponseBytes");
119
+ if (response.body === null)
120
+ return Buffer.alloc(0);
121
+ const chunks = [];
122
+ let length = 0;
123
+ for await (const chunk of response.body) {
124
+ length += chunk.byteLength;
125
+ if (length > maximum) {
126
+ abort.abort();
127
+ throw new RangeError("upstream response exceeds maxResponseBytes");
128
+ }
129
+ chunks.push(chunk);
130
+ }
131
+ return Buffer.concat(chunks, length);
132
+ }
133
+ function retryable(method, status) {
134
+ return ((method === "GET" || method === "HEAD") &&
135
+ (status === undefined || status === 502 || status === 503 || status === 504));
136
+ }
137
+ export function setup(app, options) {
138
+ if (installed.has(app))
139
+ throw new Error("proxy is already installed on this application");
140
+ if (options.routes.length === 0)
141
+ throw new TypeError("at least one proxy route is required");
142
+ const upstreams = new Map(Object.entries(options.upstreams).map(([name, value]) => [
143
+ name,
144
+ trustedUpstream(value),
145
+ ]));
146
+ const seen = new Set();
147
+ const registrations = [];
148
+ for (const route of options.routes) {
149
+ const identity = `${route.method} ${route.path}`;
150
+ if (seen.has(identity))
151
+ throw new Error(`Proxy route collision: ${identity}`);
152
+ seen.add(identity);
153
+ const upstream = upstreams.get(route.upstream);
154
+ if (upstream === undefined)
155
+ throw new TypeError(`Unknown upstream: ${route.upstream}`);
156
+ const timeoutMs = positiveInteger(route.timeoutMs ?? 10_000, "timeoutMs", 300_000);
157
+ const maximum = positiveInteger(route.maxResponseBytes ?? 8 * 1024 * 1024, "maxResponseBytes");
158
+ const retries = route.retries ?? 0;
159
+ if (!Number.isSafeInteger(retries) ||
160
+ retries < 0 ||
161
+ retries > 2 ||
162
+ (retries > 0 && !retryable(route.method)))
163
+ throw new RangeError("retries must be 0..2 and require GET or HEAD");
164
+ const reqAllowed = headerNames(route.requestHeaders ?? DEFAULT_REQUEST);
165
+ const resAllowed = headerNames(route.responseHeaders ?? DEFAULT_RESPONSE, true);
166
+ validateTemplate(route.upstreamPath ?? route.path);
167
+ const handler = async function proxyRouteHandler(req, res) {
168
+ const path = rewrite(route.upstreamPath ?? req.path, req);
169
+ const target = new URL(`${upstream.basePath}${path}${route.forwardQuery === false ? "" : query(req)}`, upstream.origin);
170
+ if (target.origin !== upstream.origin.origin)
171
+ throw new Error("resolved proxy target escaped the trusted origin");
172
+ let lastStatus;
173
+ for (let attempt = 0; attempt <= retries; attempt++) {
174
+ const abort = new AbortController();
175
+ const timer = setTimeout(() => abort.abort(), timeoutMs);
176
+ try {
177
+ const init = {
178
+ method: req.method,
179
+ headers: requestHeaders(req, reqAllowed, upstream.headers),
180
+ redirect: "manual",
181
+ signal: abort.signal,
182
+ };
183
+ if (req.body !== undefined)
184
+ init.body = req.body;
185
+ const upstreamResponse = await (options.fetch ?? fetch)(target, init);
186
+ lastStatus = upstreamResponse.status;
187
+ if (attempt < retries && retryable(req.method, lastStatus)) {
188
+ await upstreamResponse.body?.cancel();
189
+ continue;
190
+ }
191
+ const body = await boundedBody(upstreamResponse, maximum, abort);
192
+ res.statusCode = upstreamResponse.status;
193
+ for (const name of resAllowed) {
194
+ const value = upstreamResponse.headers.get(name);
195
+ if (value !== null)
196
+ res.headers.set(name, value);
197
+ }
198
+ if (req.method === "HEAD" ||
199
+ upstreamResponse.status === 204 ||
200
+ upstreamResponse.status === 304)
201
+ await res.end();
202
+ else
203
+ await res.send(body);
204
+ return;
205
+ }
206
+ catch (error) {
207
+ options.onError?.(error, {
208
+ route: route.path,
209
+ upstream: route.upstream,
210
+ attempt,
211
+ });
212
+ if (attempt < retries && retryable(req.method))
213
+ continue;
214
+ res.statusCode =
215
+ error instanceof DOMException && error.name === "AbortError"
216
+ ? 504
217
+ : 502;
218
+ await res.send(res.statusCode === 504 ? "Gateway Timeout" : "Bad Gateway");
219
+ return;
220
+ }
221
+ finally {
222
+ clearTimeout(timer);
223
+ }
224
+ }
225
+ res.statusCode = lastStatus ?? 502;
226
+ await res.send("Bad Gateway");
227
+ };
228
+ registrations.push({ route, handler });
229
+ }
230
+ installed.add(app);
231
+ for (const { route, handler } of registrations)
232
+ app.route(route.method, route.path, ...(route.metadata === undefined ? [] : [route.metadata]), handler);
233
+ return Object.freeze({ routes: Object.freeze([...seen]) });
234
+ }
235
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { FrameworkMeta, HttpMethod } from "filamentjs";
2
+ export interface Upstream {
3
+ /** Fixed trusted HTTP(S) origin. Credentials, query, and fragments are rejected. */
4
+ readonly origin: string;
5
+ readonly basePath?: string;
6
+ readonly headers?: Readonly<Record<string, string>>;
7
+ }
8
+ export interface ProxyRoute<A extends FrameworkMeta = FrameworkMeta> {
9
+ readonly path: string;
10
+ readonly method: HttpMethod;
11
+ readonly upstream: string;
12
+ /** Literal path template. `:name` placeholders use encoded route parameters. */
13
+ readonly upstreamPath?: string;
14
+ readonly forwardQuery?: boolean;
15
+ readonly requestHeaders?: readonly string[];
16
+ readonly responseHeaders?: readonly string[];
17
+ readonly timeoutMs?: number;
18
+ readonly maxResponseBytes?: number;
19
+ readonly retries?: number;
20
+ readonly metadata?: Partial<A>;
21
+ }
22
+ export interface SetupOptions<A extends FrameworkMeta = FrameworkMeta> {
23
+ readonly upstreams: Readonly<Record<string, Upstream>>;
24
+ readonly routes: readonly ProxyRoute<A>[];
25
+ readonly fetch?: typeof globalThis.fetch;
26
+ readonly onError?: (error: unknown, context: {
27
+ readonly route: string;
28
+ readonly upstream: string;
29
+ readonly attempt: number;
30
+ }) => void;
31
+ }
32
+ export interface SetupResult {
33
+ readonly routes: readonly string[];
34
+ }
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IACnE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,CACjB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;QACP,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,KACE,IAAI,CAAC;CACX;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@filamentjs/proxy",
3
+ "version": "0.1.0",
4
+ "description": "Strict exact-route reverse proxy policy for FilamentJS",
5
+ "type": "module",
6
+ "main": "./dist/proxy.js",
7
+ "types": "./dist/proxy.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/proxy.d.ts",
11
+ "import": "./dist/proxy.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/proxy.js",
16
+ "dist/proxy.js.map",
17
+ "dist/proxy.d.ts",
18
+ "dist/proxy.d.ts.map",
19
+ "dist/src/",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "node ./node_modules/typescript/bin/tsc -p tsconfig.json",
25
+ "link:local-filament": "node scripts/link-local-filament.mjs",
26
+ "test": "npm run link:local-filament && npm run build && node --test dist/test/*.test.js",
27
+ "example": "npm run link:local-filament && npm run build && node dist/example/example.js",
28
+ "demo": "npm run link:local-filament && npm run build && node dist/demo/demo.js",
29
+ "prepack": "npm test"
30
+ },
31
+ "engines": {
32
+ "node": ">=24"
33
+ },
34
+ "peerDependencies": {
35
+ "filamentjs": "0.6.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.19.43",
39
+ "typescript": "^5.9.3"
40
+ },
41
+ "keywords": [
42
+ "filamentjs",
43
+ "reverse-proxy",
44
+ "gateway"
45
+ ],
46
+ "license": "ISC"
47
+ }