@klnap/next-proxy-chain 1.0.0 → 1.0.2

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/index.d.cts CHANGED
@@ -1,31 +1,105 @@
1
1
  import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
2
- import { ProxyContext, MergeStrategy, ChainLogger, RequestHeaderPolicy, NextResult, ProxyPathFilter, BaseProxyRule } from './core.cjs';
3
- export { CompiledScope, DEFAULT_BLOCKED_REQUEST_HEADERS, EntryWrite, HostMatcher, LogLevel, NEXT, NextOptions, PathMatcher, PathOptions, PipelineOutcome, ProxyResult, RouteScopeInput, RouteScopeList, RuleTimeoutError, applyMergedToResponse, assertIncludeExcludeNoOverlap, compileScopes, cookieItemsFromHeaders, createConsoleLogger, createProxyContext, decodeOverriddenRequestHeaders, formatPipelineLine, headersDiffer, host, isNextResult, isRequestHeaderAllowed, listSetCookie, mergeRequestHeaderOverrides, mergeWrites, next, normalizeHost, parseCookieName, path, pathMatches, responseHeaderItems, ruleLabel, ruleMatches, toPathRegex, toRule, toStatelessRegExp, withPaths, withRuleTimeout } from './core.cjs';
4
2
 
5
3
  /**
6
- * A single proxy step.
7
- *
8
- * Contract:
9
- * - `return` / `undefined` — continue with no mutations
10
- * - `return next({ request?, headers? })` continue with mutations
11
- * - `return Response | NextResponse` — if NextResponse.next() or NextResponse.rewrite(), chain continues with mutations; otherwise terminates after merge
12
- *
13
- * Third-party middleware (e.g. next-intl) should run **last** and return its
14
- * Response directly; any terminal Response terminates the chain after merge.
4
+ * Unique symbol identifying an explicit continue action in the proxy chain pipeline.
5
+ */
6
+ declare const NEXT: unique symbol;
7
+ /**
8
+ * Protected request headers that cannot be rewritten unless explicitly permitted by policy.
9
+ */
10
+ declare const DEFAULT_BLOCKED_REQUEST_HEADERS: readonly string[];
11
+
12
+ /**
13
+ * Configuration options for chain continuation.
14
+ */
15
+ type NextOptions = {
16
+ /** Request header overrides visible to downstream rules and handlers. */
17
+ request?: HeadersInit;
18
+ /** Response headers merged into the final outgoing response. */
19
+ headers?: HeadersInit;
20
+ };
21
+ /**
22
+ * Continuation token indicating the pipeline should proceed to subsequent rules.
23
+ */
24
+ type NextResult = NextOptions & {
25
+ readonly [NEXT]: true;
26
+ };
27
+ /**
28
+ * Shared state for an individual request pipeline execution.
29
+ * Backed by Map to eliminate prototype-pollution risks.
30
+ */
31
+ type ProxyContext = Map<string, unknown>;
32
+ type ProxyResult<TResponse = NextResponse | Response> = TResponse | NextResult | undefined | void;
33
+ type PathMatcher = RegExp | string;
34
+ type PathOptions = {
35
+ /** Optional locale prefixes for path matching. */
36
+ locales?: readonly string[];
37
+ };
38
+ type HostMatcher = string | RegExp | readonly (string | RegExp)[];
39
+ /**
40
+ * Scoping configuration for rule inclusion or exclusion.
41
+ */
42
+ type RouteScopeInput = PathMatcher | {
43
+ host: HostMatcher;
44
+ path?: PathMatcher | PathMatcher[];
45
+ } | {
46
+ path: PathMatcher | PathMatcher[];
47
+ host?: HostMatcher;
48
+ };
49
+ type RouteScopeList = RouteScopeInput | RouteScopeInput[];
50
+ /** Compiled route scope evaluated during request execution. */
51
+ type CompiledScope = {
52
+ host?: RegExp;
53
+ path?: RegExp;
54
+ };
55
+ interface BaseProxyRule<TFn> {
56
+ run: TFn;
57
+ include?: CompiledScope[];
58
+ exclude?: CompiledScope[];
59
+ name?: string;
60
+ }
61
+ type ProxyPathFilter = {
62
+ include?: RouteScopeList;
63
+ exclude?: RouteScopeList;
64
+ name?: string;
65
+ pathOptions?: PathOptions;
66
+ };
67
+ /**
68
+ * A single proxy execution function.
15
69
  */
16
70
  type ProxyFn<TContext = ProxyContext> = (req: NextRequest, ctx: TContext, event: NextFetchEvent) => NextResponse | Response | NextResult | undefined | void | Promise<NextResponse | Response | NextResult | undefined | void>;
17
71
  type ProxyRule<TContext = ProxyContext> = BaseProxyRule<ProxyFn<TContext>>;
18
- type Entry<TContext> = ProxyFn<TContext> | ProxyRule<TContext>;
19
72
  type DefinedProxy<TContext = ProxyContext> = ProxyFn<TContext> & ((filter: ProxyPathFilter) => ProxyRule<TContext>);
20
- /**
21
- * Define a reusable proxy. Call with a path filter to scope it.
22
- *
23
- * @example
24
- * defineProxy("auth", (req, ctx) => next())
25
- * demoAuthProxy({ include: path("/admin/*") })
26
- */
27
- declare function defineProxy<TContext = ProxyContext>(run: ProxyFn<TContext>): DefinedProxy<TContext>;
28
- declare function defineProxy<TContext = ProxyContext>(name: string, run: ProxyFn<TContext>): DefinedProxy<TContext>;
73
+ type Entry<TContext = ProxyContext> = ProxyFn<TContext> | ProxyRule<TContext>;
74
+ type MergeStrategy = "last-write-wins" | "first-write-wins" | ((key: string, existing: string, incoming: string) => string | null);
75
+ interface EntryWrite {
76
+ ruleIndex: number;
77
+ ruleName: string;
78
+ items: Map<string, string>;
79
+ }
80
+ type LogLevel = "debug" | "info" | "warn" | "error";
81
+ interface ChainLogger {
82
+ debug: (metaOrMsg: unknown, maybeMsg?: string) => void;
83
+ info: (metaOrMsg: unknown, maybeMsg?: string) => void;
84
+ warn: (metaOrMsg: unknown, maybeMsg?: string) => void;
85
+ error: (metaOrMsg: unknown, maybeMsg?: string) => void;
86
+ }
87
+ type PipelineOutcome = {
88
+ kind: "passed";
89
+ duration: number;
90
+ } | {
91
+ kind: "next";
92
+ duration: number;
93
+ headers: number;
94
+ } | {
95
+ kind: "stop";
96
+ duration: number;
97
+ status: number;
98
+ };
99
+ interface RequestHeaderPolicy {
100
+ blocked?: readonly string[];
101
+ allowed?: readonly string[];
102
+ }
29
103
  interface ChainConfig<TContext = ProxyContext> {
30
104
  createContext: (req: NextRequest, event: NextFetchEvent) => TContext;
31
105
  cookieMergeStrategy: MergeStrategy;
@@ -33,33 +107,179 @@ interface ChainConfig<TContext = ProxyContext> {
33
107
  onError?: (err: unknown, ruleIndex: number, req: NextRequest, ctx: TContext) => NextResponse | Response | undefined | void | Promise<NextResponse | Response | undefined | void>;
34
108
  debug: boolean;
35
109
  logger: ChainLogger;
36
- /** Per-rule wall-clock limit in ms. `0` disables. */
110
+ /** Maximum wall-clock execution time per rule in milliseconds. 0 disables timeout. */
37
111
  ruleTimeoutMs: number;
38
112
  requestHeaderPolicy: RequestHeaderPolicy;
39
- /** Throw an error if platform does not support request header overrides. Defaults to false (logs warning). */
113
+ /** Throw an error on startup if the runtime platform does not support request header overrides. */
40
114
  strictOverrideCheck?: boolean;
41
115
  }
42
116
  type ProxyChainOptions<TContext = ProxyContext> = Partial<ChainConfig<TContext>>;
117
+
118
+ /**
119
+ * Creates an isolated per-request context instance.
120
+ * Backed by Map to eliminate prototype pollution risks.
121
+ */
122
+ declare function createProxyContext(): ProxyContext;
123
+
124
+ /**
125
+ * Strips sticky and global flags (g, y) from RegExp instances.
126
+ * On Edge runtimes, compiled regex instances can be shared across requests;
127
+ * sticky and global flags mutate stateful indices, causing non-deterministic matching.
128
+ */
129
+ declare function toStatelessRegExp(re: RegExp): RegExp;
130
+ /**
131
+ * Builds a compiled Host matcher RegExp from domain names, wildcards, or regex patterns.
132
+ */
133
+ declare function host(matcher: HostMatcher): RegExp;
134
+ /**
135
+ * Compiles a path pattern (glob string, param pattern, or RegExp) into a stateless regular expression.
136
+ */
137
+ declare function path(pattern: string | RegExp, options?: PathOptions): RegExp;
138
+ declare function toPathRegex(matcher: PathMatcher | PathMatcher[], options?: PathOptions): RegExp;
139
+ /**
140
+ * Compiles route scoping declarations into normalized regex clauses.
141
+ */
142
+ declare function compileScopes(input: RouteScopeList | undefined, pathOptions?: PathOptions): CompiledScope[] | undefined;
143
+ declare function toRule<TFn>(entry: TFn | BaseProxyRule<TFn>): BaseProxyRule<TFn>;
144
+ declare function ruleLabel<TFn>(rule: BaseProxyRule<TFn>, index: number): string;
145
+ /**
146
+ * Decorates a proxy entry with declarative path and host inclusion/exclusion filters.
147
+ */
148
+ declare function withPaths<TFn>(entry: TFn | BaseProxyRule<TFn>, filter: ProxyPathFilter): BaseProxyRule<TFn>;
149
+ /**
150
+ * Determines whether a given request matches a rule's scoping constraints.
151
+ */
152
+ declare function ruleMatches<TFn>(pathname: string, hostname: string | null, rule: BaseProxyRule<TFn>): boolean;
153
+ declare function pathMatches<TFn>(pathname: string, rule: BaseProxyRule<TFn>): boolean;
154
+ /**
155
+ * Normalizes Host or X-Forwarded-Host header values to lower-case hostname without port.
156
+ */
157
+ declare function normalizeHost(raw: string | null | undefined): string | null;
158
+ /**
159
+ * Validates that an identical scope is not declared simultaneously in include and exclude.
160
+ */
161
+ declare function assertIncludeExcludeNoOverlap(include: CompiledScope[] | undefined, exclude: CompiledScope[] | undefined, label: string): void;
162
+
163
+ /**
164
+ * Converts HeadersInit or undefined to a standard Web Headers instance.
165
+ */
166
+ declare function headersInitToHeaders(init?: HeadersInit): Headers;
167
+ /**
168
+ * Compares two Headers instances to determine if overrides contain differences.
169
+ */
170
+ declare function headersDiffer(base: Headers, overrides: Headers): boolean;
171
+ /**
172
+ * Checks whether a request header is permitted to be mutated under the configured policy.
173
+ */
174
+ declare function isRequestHeaderAllowed(name: string, policy?: RequestHeaderPolicy): boolean;
175
+ /**
176
+ * Merges new request header overrides into the target Headers instance, filtering prohibited headers.
177
+ */
178
+ declare function mergeRequestHeaderOverrides(target: Headers, overrides: Headers, policy?: RequestHeaderPolicy): {
179
+ applied: number;
180
+ dropped: string[];
181
+ };
182
+ /**
183
+ * Extracts cookie name from a raw Set-Cookie directive.
184
+ */
185
+ declare function parseCookieName(setCookie: string): string;
186
+ /**
187
+ * Extracts all Set-Cookie directives using standard getSetCookie() when available.
188
+ */
189
+ declare function listSetCookie(headers: Headers): string[];
190
+ /**
191
+ * Maps cookie name to Set-Cookie header value from a Headers instance.
192
+ */
193
+ declare function cookieItemsFromHeaders(headers: Headers): Map<string, string>;
194
+ /**
195
+ * Filters out Set-Cookie and internal transport headers (x-middleware-*) from response headers.
196
+ */
197
+ declare function responseHeaderItems(headers: Headers): Map<string, string>;
198
+ /**
199
+ * Decodes Next.js request header overrides encoded into response headers.
200
+ */
201
+ declare function decodeOverriddenRequestHeaders(headers: Headers): Headers;
202
+ /**
203
+ * Deterministically merges writes across rules based on configured strategy.
204
+ */
205
+ declare function mergeWrites(writes: EntryWrite[], strategy: MergeStrategy, onConflict?: (key: string, winner: string, loser: string) => void): Map<string, string>;
206
+ /**
207
+ * Applies merged headers and deduplicated cookies onto an outgoing response.
208
+ */
209
+ declare function applyMergedToResponse(res: Response, headers: Map<string, string>, cookies: Map<string, string>): void;
210
+
211
+ /**
212
+ * Creates a continuation action instructing the proxy chain to proceed to subsequent rules.
213
+ *
214
+ * @param options - Optional request and response header mutations.
215
+ * @returns NextResult token indicating pipeline continuation.
216
+ */
217
+ declare function next(options?: NextOptions): NextResult;
218
+ /**
219
+ * Type guard verifying whether a returned value is a proxy continuation token.
220
+ *
221
+ * @param value - Value to test.
222
+ * @returns True if value is a NextResult continuation token.
223
+ */
224
+ declare function isNextResult(value: unknown): value is NextResult;
225
+
226
+ declare function createConsoleLogger(minLevel?: LogLevel): ChainLogger;
227
+ /**
228
+ * Formats a single structured log line for an executed proxy rule.
229
+ */
230
+ declare function formatPipelineLine(input: {
231
+ method: string;
232
+ path: string;
233
+ name: string;
234
+ traceId?: string;
235
+ outcome: PipelineOutcome;
236
+ }): string;
237
+
238
+ declare class RuleTimeoutError extends Error {
239
+ readonly ruleName: string;
240
+ readonly timeoutMs: number;
241
+ constructor(ruleName: string, timeoutMs: number);
242
+ }
243
+ /**
244
+ * Enforces a strict execution time budget on an asynchronous rule operation.
245
+ */
246
+ declare function withRuleTimeout<T>(promise: Promise<T>, timeoutMs: number | undefined, ruleName: string): Promise<T>;
247
+
43
248
  declare function safeInternalError(): NextResponse;
44
- /** Assert once per process at construction time that NextResponse.next() supports request header overrides. */
45
249
  declare function assertOverrideMechanismSupported(strict?: boolean): boolean;
250
+ type ResolvedOutcome = {
251
+ kind: "continue";
252
+ request?: Headers;
253
+ responseHeaders: Map<string, string>;
254
+ cookies: Map<string, string>;
255
+ rewriteUrl?: string;
256
+ } | {
257
+ kind: "stop";
258
+ response: NextResponse | Response;
259
+ };
260
+ declare function resolveRuleResult(res: NextResponse | Response | NextResult | undefined | void, requestHeaderPolicy: RequestHeaderPolicy, logger: ChainLogger, debug: boolean): ResolvedOutcome;
261
+ declare function finalizeResponse(res: NextResponse | Response, headerWrites: EntryWrite[], cookieWrites: EntryWrite[], headerStrategy: MergeStrategy, cookieStrategy: MergeStrategy, logger: ChainLogger, debug: boolean): NextResponse | Response;
262
+
263
+ /**
264
+ * Creates a reusable, scope-aware proxy rule function.
265
+ *
266
+ * @example
267
+ * const authRule = defineProxy("auth", async (req, ctx) => next())
268
+ * const scopedAuthRule = authRule({ include: path("/admin/*") })
269
+ */
270
+ declare function defineProxy<TContext = ProxyContext>(run: ProxyFn<TContext>): DefinedProxy<TContext>;
271
+ declare function defineProxy<TContext = ProxyContext>(name: string, run: ProxyFn<TContext>): DefinedProxy<TContext>;
46
272
  /**
47
- * Compose proxy rules into a single Edge entry (`proxy.ts` / middleware).
273
+ * Composes independent proxy rules into a single pipeline for Next.js 15 (middleware.ts) and Next.js 16 (proxy.ts).
48
274
  *
49
275
  * @example
50
276
  * export default proxyChain({ debug: true }, [
51
- * hostProxy,
52
- * demoAuthProxy({
53
- * include: [
54
- * path("/admin/*"),
55
- * { host: "shop.localhost", path: "/checkout/*" },
56
- * ],
57
- * exclude: { host: "museum.localhost" },
58
- * }),
59
- * i18nProxy,
277
+ * requestIdRule,
278
+ * authRule({ include: path("/dashboard/*") }),
279
+ * i18nRule,
60
280
  * ])
61
281
  */
62
282
  declare function proxyChain<TContext = ProxyContext>(entries: Entry<TContext>[]): (req: NextRequest, event: NextFetchEvent) => Promise<NextResponse | Response>;
63
283
  declare function proxyChain<TContext = ProxyContext>(options: ProxyChainOptions<TContext>, entries: Entry<TContext>[]): (req: NextRequest, event: NextFetchEvent) => Promise<NextResponse | Response>;
64
284
 
65
- export { BaseProxyRule, type ChainConfig, ChainLogger, type DefinedProxy, MergeStrategy, NextResult, type ProxyChainOptions, ProxyContext, type ProxyFn, ProxyPathFilter, type ProxyRule, RequestHeaderPolicy, assertOverrideMechanismSupported, defineProxy, proxyChain, safeInternalError };
285
+ export { type BaseProxyRule, type ChainConfig, type ChainLogger, type CompiledScope, DEFAULT_BLOCKED_REQUEST_HEADERS, type DefinedProxy, type Entry, type EntryWrite, type HostMatcher, type LogLevel, type MergeStrategy, NEXT, type NextOptions, type NextResult, type PathMatcher, type PathOptions, type PipelineOutcome, type ProxyChainOptions, type ProxyContext, type ProxyFn, type ProxyPathFilter, type ProxyResult, type ProxyRule, type RequestHeaderPolicy, type ResolvedOutcome, type RouteScopeInput, type RouteScopeList, RuleTimeoutError, applyMergedToResponse, assertIncludeExcludeNoOverlap, assertOverrideMechanismSupported, compileScopes, cookieItemsFromHeaders, createConsoleLogger, createProxyContext, decodeOverriddenRequestHeaders, defineProxy, finalizeResponse, formatPipelineLine, headersDiffer, headersInitToHeaders, host, isNextResult, isRequestHeaderAllowed, listSetCookie, mergeRequestHeaderOverrides, mergeWrites, next, normalizeHost, parseCookieName, path, pathMatches, proxyChain, resolveRuleResult, responseHeaderItems, ruleLabel, ruleMatches, safeInternalError, toPathRegex, toRule, toStatelessRegExp, withPaths, withRuleTimeout };
package/dist/index.d.ts CHANGED
@@ -1,31 +1,105 @@
1
1
  import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
2
- import { ProxyContext, MergeStrategy, ChainLogger, RequestHeaderPolicy, NextResult, ProxyPathFilter, BaseProxyRule } from './core.js';
3
- export { CompiledScope, DEFAULT_BLOCKED_REQUEST_HEADERS, EntryWrite, HostMatcher, LogLevel, NEXT, NextOptions, PathMatcher, PathOptions, PipelineOutcome, ProxyResult, RouteScopeInput, RouteScopeList, RuleTimeoutError, applyMergedToResponse, assertIncludeExcludeNoOverlap, compileScopes, cookieItemsFromHeaders, createConsoleLogger, createProxyContext, decodeOverriddenRequestHeaders, formatPipelineLine, headersDiffer, host, isNextResult, isRequestHeaderAllowed, listSetCookie, mergeRequestHeaderOverrides, mergeWrites, next, normalizeHost, parseCookieName, path, pathMatches, responseHeaderItems, ruleLabel, ruleMatches, toPathRegex, toRule, toStatelessRegExp, withPaths, withRuleTimeout } from './core.js';
4
2
 
5
3
  /**
6
- * A single proxy step.
7
- *
8
- * Contract:
9
- * - `return` / `undefined` — continue with no mutations
10
- * - `return next({ request?, headers? })` continue with mutations
11
- * - `return Response | NextResponse` — if NextResponse.next() or NextResponse.rewrite(), chain continues with mutations; otherwise terminates after merge
12
- *
13
- * Third-party middleware (e.g. next-intl) should run **last** and return its
14
- * Response directly; any terminal Response terminates the chain after merge.
4
+ * Unique symbol identifying an explicit continue action in the proxy chain pipeline.
5
+ */
6
+ declare const NEXT: unique symbol;
7
+ /**
8
+ * Protected request headers that cannot be rewritten unless explicitly permitted by policy.
9
+ */
10
+ declare const DEFAULT_BLOCKED_REQUEST_HEADERS: readonly string[];
11
+
12
+ /**
13
+ * Configuration options for chain continuation.
14
+ */
15
+ type NextOptions = {
16
+ /** Request header overrides visible to downstream rules and handlers. */
17
+ request?: HeadersInit;
18
+ /** Response headers merged into the final outgoing response. */
19
+ headers?: HeadersInit;
20
+ };
21
+ /**
22
+ * Continuation token indicating the pipeline should proceed to subsequent rules.
23
+ */
24
+ type NextResult = NextOptions & {
25
+ readonly [NEXT]: true;
26
+ };
27
+ /**
28
+ * Shared state for an individual request pipeline execution.
29
+ * Backed by Map to eliminate prototype-pollution risks.
30
+ */
31
+ type ProxyContext = Map<string, unknown>;
32
+ type ProxyResult<TResponse = NextResponse | Response> = TResponse | NextResult | undefined | void;
33
+ type PathMatcher = RegExp | string;
34
+ type PathOptions = {
35
+ /** Optional locale prefixes for path matching. */
36
+ locales?: readonly string[];
37
+ };
38
+ type HostMatcher = string | RegExp | readonly (string | RegExp)[];
39
+ /**
40
+ * Scoping configuration for rule inclusion or exclusion.
41
+ */
42
+ type RouteScopeInput = PathMatcher | {
43
+ host: HostMatcher;
44
+ path?: PathMatcher | PathMatcher[];
45
+ } | {
46
+ path: PathMatcher | PathMatcher[];
47
+ host?: HostMatcher;
48
+ };
49
+ type RouteScopeList = RouteScopeInput | RouteScopeInput[];
50
+ /** Compiled route scope evaluated during request execution. */
51
+ type CompiledScope = {
52
+ host?: RegExp;
53
+ path?: RegExp;
54
+ };
55
+ interface BaseProxyRule<TFn> {
56
+ run: TFn;
57
+ include?: CompiledScope[];
58
+ exclude?: CompiledScope[];
59
+ name?: string;
60
+ }
61
+ type ProxyPathFilter = {
62
+ include?: RouteScopeList;
63
+ exclude?: RouteScopeList;
64
+ name?: string;
65
+ pathOptions?: PathOptions;
66
+ };
67
+ /**
68
+ * A single proxy execution function.
15
69
  */
16
70
  type ProxyFn<TContext = ProxyContext> = (req: NextRequest, ctx: TContext, event: NextFetchEvent) => NextResponse | Response | NextResult | undefined | void | Promise<NextResponse | Response | NextResult | undefined | void>;
17
71
  type ProxyRule<TContext = ProxyContext> = BaseProxyRule<ProxyFn<TContext>>;
18
- type Entry<TContext> = ProxyFn<TContext> | ProxyRule<TContext>;
19
72
  type DefinedProxy<TContext = ProxyContext> = ProxyFn<TContext> & ((filter: ProxyPathFilter) => ProxyRule<TContext>);
20
- /**
21
- * Define a reusable proxy. Call with a path filter to scope it.
22
- *
23
- * @example
24
- * defineProxy("auth", (req, ctx) => next())
25
- * demoAuthProxy({ include: path("/admin/*") })
26
- */
27
- declare function defineProxy<TContext = ProxyContext>(run: ProxyFn<TContext>): DefinedProxy<TContext>;
28
- declare function defineProxy<TContext = ProxyContext>(name: string, run: ProxyFn<TContext>): DefinedProxy<TContext>;
73
+ type Entry<TContext = ProxyContext> = ProxyFn<TContext> | ProxyRule<TContext>;
74
+ type MergeStrategy = "last-write-wins" | "first-write-wins" | ((key: string, existing: string, incoming: string) => string | null);
75
+ interface EntryWrite {
76
+ ruleIndex: number;
77
+ ruleName: string;
78
+ items: Map<string, string>;
79
+ }
80
+ type LogLevel = "debug" | "info" | "warn" | "error";
81
+ interface ChainLogger {
82
+ debug: (metaOrMsg: unknown, maybeMsg?: string) => void;
83
+ info: (metaOrMsg: unknown, maybeMsg?: string) => void;
84
+ warn: (metaOrMsg: unknown, maybeMsg?: string) => void;
85
+ error: (metaOrMsg: unknown, maybeMsg?: string) => void;
86
+ }
87
+ type PipelineOutcome = {
88
+ kind: "passed";
89
+ duration: number;
90
+ } | {
91
+ kind: "next";
92
+ duration: number;
93
+ headers: number;
94
+ } | {
95
+ kind: "stop";
96
+ duration: number;
97
+ status: number;
98
+ };
99
+ interface RequestHeaderPolicy {
100
+ blocked?: readonly string[];
101
+ allowed?: readonly string[];
102
+ }
29
103
  interface ChainConfig<TContext = ProxyContext> {
30
104
  createContext: (req: NextRequest, event: NextFetchEvent) => TContext;
31
105
  cookieMergeStrategy: MergeStrategy;
@@ -33,33 +107,179 @@ interface ChainConfig<TContext = ProxyContext> {
33
107
  onError?: (err: unknown, ruleIndex: number, req: NextRequest, ctx: TContext) => NextResponse | Response | undefined | void | Promise<NextResponse | Response | undefined | void>;
34
108
  debug: boolean;
35
109
  logger: ChainLogger;
36
- /** Per-rule wall-clock limit in ms. `0` disables. */
110
+ /** Maximum wall-clock execution time per rule in milliseconds. 0 disables timeout. */
37
111
  ruleTimeoutMs: number;
38
112
  requestHeaderPolicy: RequestHeaderPolicy;
39
- /** Throw an error if platform does not support request header overrides. Defaults to false (logs warning). */
113
+ /** Throw an error on startup if the runtime platform does not support request header overrides. */
40
114
  strictOverrideCheck?: boolean;
41
115
  }
42
116
  type ProxyChainOptions<TContext = ProxyContext> = Partial<ChainConfig<TContext>>;
117
+
118
+ /**
119
+ * Creates an isolated per-request context instance.
120
+ * Backed by Map to eliminate prototype pollution risks.
121
+ */
122
+ declare function createProxyContext(): ProxyContext;
123
+
124
+ /**
125
+ * Strips sticky and global flags (g, y) from RegExp instances.
126
+ * On Edge runtimes, compiled regex instances can be shared across requests;
127
+ * sticky and global flags mutate stateful indices, causing non-deterministic matching.
128
+ */
129
+ declare function toStatelessRegExp(re: RegExp): RegExp;
130
+ /**
131
+ * Builds a compiled Host matcher RegExp from domain names, wildcards, or regex patterns.
132
+ */
133
+ declare function host(matcher: HostMatcher): RegExp;
134
+ /**
135
+ * Compiles a path pattern (glob string, param pattern, or RegExp) into a stateless regular expression.
136
+ */
137
+ declare function path(pattern: string | RegExp, options?: PathOptions): RegExp;
138
+ declare function toPathRegex(matcher: PathMatcher | PathMatcher[], options?: PathOptions): RegExp;
139
+ /**
140
+ * Compiles route scoping declarations into normalized regex clauses.
141
+ */
142
+ declare function compileScopes(input: RouteScopeList | undefined, pathOptions?: PathOptions): CompiledScope[] | undefined;
143
+ declare function toRule<TFn>(entry: TFn | BaseProxyRule<TFn>): BaseProxyRule<TFn>;
144
+ declare function ruleLabel<TFn>(rule: BaseProxyRule<TFn>, index: number): string;
145
+ /**
146
+ * Decorates a proxy entry with declarative path and host inclusion/exclusion filters.
147
+ */
148
+ declare function withPaths<TFn>(entry: TFn | BaseProxyRule<TFn>, filter: ProxyPathFilter): BaseProxyRule<TFn>;
149
+ /**
150
+ * Determines whether a given request matches a rule's scoping constraints.
151
+ */
152
+ declare function ruleMatches<TFn>(pathname: string, hostname: string | null, rule: BaseProxyRule<TFn>): boolean;
153
+ declare function pathMatches<TFn>(pathname: string, rule: BaseProxyRule<TFn>): boolean;
154
+ /**
155
+ * Normalizes Host or X-Forwarded-Host header values to lower-case hostname without port.
156
+ */
157
+ declare function normalizeHost(raw: string | null | undefined): string | null;
158
+ /**
159
+ * Validates that an identical scope is not declared simultaneously in include and exclude.
160
+ */
161
+ declare function assertIncludeExcludeNoOverlap(include: CompiledScope[] | undefined, exclude: CompiledScope[] | undefined, label: string): void;
162
+
163
+ /**
164
+ * Converts HeadersInit or undefined to a standard Web Headers instance.
165
+ */
166
+ declare function headersInitToHeaders(init?: HeadersInit): Headers;
167
+ /**
168
+ * Compares two Headers instances to determine if overrides contain differences.
169
+ */
170
+ declare function headersDiffer(base: Headers, overrides: Headers): boolean;
171
+ /**
172
+ * Checks whether a request header is permitted to be mutated under the configured policy.
173
+ */
174
+ declare function isRequestHeaderAllowed(name: string, policy?: RequestHeaderPolicy): boolean;
175
+ /**
176
+ * Merges new request header overrides into the target Headers instance, filtering prohibited headers.
177
+ */
178
+ declare function mergeRequestHeaderOverrides(target: Headers, overrides: Headers, policy?: RequestHeaderPolicy): {
179
+ applied: number;
180
+ dropped: string[];
181
+ };
182
+ /**
183
+ * Extracts cookie name from a raw Set-Cookie directive.
184
+ */
185
+ declare function parseCookieName(setCookie: string): string;
186
+ /**
187
+ * Extracts all Set-Cookie directives using standard getSetCookie() when available.
188
+ */
189
+ declare function listSetCookie(headers: Headers): string[];
190
+ /**
191
+ * Maps cookie name to Set-Cookie header value from a Headers instance.
192
+ */
193
+ declare function cookieItemsFromHeaders(headers: Headers): Map<string, string>;
194
+ /**
195
+ * Filters out Set-Cookie and internal transport headers (x-middleware-*) from response headers.
196
+ */
197
+ declare function responseHeaderItems(headers: Headers): Map<string, string>;
198
+ /**
199
+ * Decodes Next.js request header overrides encoded into response headers.
200
+ */
201
+ declare function decodeOverriddenRequestHeaders(headers: Headers): Headers;
202
+ /**
203
+ * Deterministically merges writes across rules based on configured strategy.
204
+ */
205
+ declare function mergeWrites(writes: EntryWrite[], strategy: MergeStrategy, onConflict?: (key: string, winner: string, loser: string) => void): Map<string, string>;
206
+ /**
207
+ * Applies merged headers and deduplicated cookies onto an outgoing response.
208
+ */
209
+ declare function applyMergedToResponse(res: Response, headers: Map<string, string>, cookies: Map<string, string>): void;
210
+
211
+ /**
212
+ * Creates a continuation action instructing the proxy chain to proceed to subsequent rules.
213
+ *
214
+ * @param options - Optional request and response header mutations.
215
+ * @returns NextResult token indicating pipeline continuation.
216
+ */
217
+ declare function next(options?: NextOptions): NextResult;
218
+ /**
219
+ * Type guard verifying whether a returned value is a proxy continuation token.
220
+ *
221
+ * @param value - Value to test.
222
+ * @returns True if value is a NextResult continuation token.
223
+ */
224
+ declare function isNextResult(value: unknown): value is NextResult;
225
+
226
+ declare function createConsoleLogger(minLevel?: LogLevel): ChainLogger;
227
+ /**
228
+ * Formats a single structured log line for an executed proxy rule.
229
+ */
230
+ declare function formatPipelineLine(input: {
231
+ method: string;
232
+ path: string;
233
+ name: string;
234
+ traceId?: string;
235
+ outcome: PipelineOutcome;
236
+ }): string;
237
+
238
+ declare class RuleTimeoutError extends Error {
239
+ readonly ruleName: string;
240
+ readonly timeoutMs: number;
241
+ constructor(ruleName: string, timeoutMs: number);
242
+ }
243
+ /**
244
+ * Enforces a strict execution time budget on an asynchronous rule operation.
245
+ */
246
+ declare function withRuleTimeout<T>(promise: Promise<T>, timeoutMs: number | undefined, ruleName: string): Promise<T>;
247
+
43
248
  declare function safeInternalError(): NextResponse;
44
- /** Assert once per process at construction time that NextResponse.next() supports request header overrides. */
45
249
  declare function assertOverrideMechanismSupported(strict?: boolean): boolean;
250
+ type ResolvedOutcome = {
251
+ kind: "continue";
252
+ request?: Headers;
253
+ responseHeaders: Map<string, string>;
254
+ cookies: Map<string, string>;
255
+ rewriteUrl?: string;
256
+ } | {
257
+ kind: "stop";
258
+ response: NextResponse | Response;
259
+ };
260
+ declare function resolveRuleResult(res: NextResponse | Response | NextResult | undefined | void, requestHeaderPolicy: RequestHeaderPolicy, logger: ChainLogger, debug: boolean): ResolvedOutcome;
261
+ declare function finalizeResponse(res: NextResponse | Response, headerWrites: EntryWrite[], cookieWrites: EntryWrite[], headerStrategy: MergeStrategy, cookieStrategy: MergeStrategy, logger: ChainLogger, debug: boolean): NextResponse | Response;
262
+
263
+ /**
264
+ * Creates a reusable, scope-aware proxy rule function.
265
+ *
266
+ * @example
267
+ * const authRule = defineProxy("auth", async (req, ctx) => next())
268
+ * const scopedAuthRule = authRule({ include: path("/admin/*") })
269
+ */
270
+ declare function defineProxy<TContext = ProxyContext>(run: ProxyFn<TContext>): DefinedProxy<TContext>;
271
+ declare function defineProxy<TContext = ProxyContext>(name: string, run: ProxyFn<TContext>): DefinedProxy<TContext>;
46
272
  /**
47
- * Compose proxy rules into a single Edge entry (`proxy.ts` / middleware).
273
+ * Composes independent proxy rules into a single pipeline for Next.js 15 (middleware.ts) and Next.js 16 (proxy.ts).
48
274
  *
49
275
  * @example
50
276
  * export default proxyChain({ debug: true }, [
51
- * hostProxy,
52
- * demoAuthProxy({
53
- * include: [
54
- * path("/admin/*"),
55
- * { host: "shop.localhost", path: "/checkout/*" },
56
- * ],
57
- * exclude: { host: "museum.localhost" },
58
- * }),
59
- * i18nProxy,
277
+ * requestIdRule,
278
+ * authRule({ include: path("/dashboard/*") }),
279
+ * i18nRule,
60
280
  * ])
61
281
  */
62
282
  declare function proxyChain<TContext = ProxyContext>(entries: Entry<TContext>[]): (req: NextRequest, event: NextFetchEvent) => Promise<NextResponse | Response>;
63
283
  declare function proxyChain<TContext = ProxyContext>(options: ProxyChainOptions<TContext>, entries: Entry<TContext>[]): (req: NextRequest, event: NextFetchEvent) => Promise<NextResponse | Response>;
64
284
 
65
- export { BaseProxyRule, type ChainConfig, ChainLogger, type DefinedProxy, MergeStrategy, NextResult, type ProxyChainOptions, ProxyContext, type ProxyFn, ProxyPathFilter, type ProxyRule, RequestHeaderPolicy, assertOverrideMechanismSupported, defineProxy, proxyChain, safeInternalError };
285
+ export { type BaseProxyRule, type ChainConfig, type ChainLogger, type CompiledScope, DEFAULT_BLOCKED_REQUEST_HEADERS, type DefinedProxy, type Entry, type EntryWrite, type HostMatcher, type LogLevel, type MergeStrategy, NEXT, type NextOptions, type NextResult, type PathMatcher, type PathOptions, type PipelineOutcome, type ProxyChainOptions, type ProxyContext, type ProxyFn, type ProxyPathFilter, type ProxyResult, type ProxyRule, type RequestHeaderPolicy, type ResolvedOutcome, type RouteScopeInput, type RouteScopeList, RuleTimeoutError, applyMergedToResponse, assertIncludeExcludeNoOverlap, assertOverrideMechanismSupported, compileScopes, cookieItemsFromHeaders, createConsoleLogger, createProxyContext, decodeOverriddenRequestHeaders, defineProxy, finalizeResponse, formatPipelineLine, headersDiffer, headersInitToHeaders, host, isNextResult, isRequestHeaderAllowed, listSetCookie, mergeRequestHeaderOverrides, mergeWrites, next, normalizeHost, parseCookieName, path, pathMatches, proxyChain, resolveRuleResult, responseHeaderItems, ruleLabel, ruleMatches, safeInternalError, toPathRegex, toRule, toStatelessRegExp, withPaths, withRuleTimeout };