@fedify/uri-template 2.0.0-pr.475.1

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,72 @@
1
+ import { TemplateAst } from "./ast.js";
2
+
3
+ //#region src/expand.d.ts
4
+
5
+ /**
6
+ * A scalar value that can be used in template expansion.
7
+ */
8
+ type Scalar = string | number | boolean;
9
+ /**
10
+ * A list of scalar values that can be used in template expansion.
11
+ */
12
+ type List = (Scalar | undefined)[];
13
+ /**
14
+ * A map-like object with scalar values.
15
+ */
16
+ type MapLike = Record<string, Scalar | undefined>;
17
+ /**
18
+ * Valid value types for template variables.
19
+ */
20
+ type VarsValue = Scalar | List | MapLike | undefined;
21
+ /**
22
+ * A collection of variables for template expansion.
23
+ * Maps variable names to their values.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const vars: Vars = {
28
+ * var: "value",
29
+ * list: ["red", "green", "blue"],
30
+ * keys: { semi: ";", dot: ".", comma: "," }
31
+ * };
32
+ * ```
33
+ */
34
+ type Vars = Record<string, VarsValue>;
35
+ /**
36
+ * Expand a parsed template with variables according to RFC 6570 (Level 1-4).
37
+ *
38
+ * @param ast - The parsed template AST to expand
39
+ * @param vars - Variables to substitute into the template
40
+ * @returns The expanded URL string
41
+ *
42
+ * @remarks
43
+ * - Idempotent percent encoding (existing `%XX` kept)
44
+ * - Operator-specific empty/undefined rules:
45
+ * - ";" empty -> `nameOnly` (";x")
46
+ * - "?" "&" empty -> "key=" ("?x=")
47
+ * - `undefined` -> `omit` (all operators)
48
+ * - Label "." emits the dot even if empty ("X{.y}" with y="" -> "X.")
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * import { parse } from "./parser.ts";
53
+ * import { expand } from "./expand.ts";
54
+ *
55
+ * const ast = parse("{+x,hello,y}");
56
+ * const url = expand(ast, { x: "1024", hello: "Hello World!", y: "768" });
57
+ * // Returns: "1024,Hello%20World!,768"
58
+ * ```
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * import { parse } from "./parser.ts";
63
+ * import { expand } from "./expand.ts";
64
+ *
65
+ * const ast = parse("{+path}/here");
66
+ * const url = expand(ast, { path: "/foo/bar" });
67
+ * // Returns: "/foo/bar/here"
68
+ * ```
69
+ */
70
+ declare function expand(ast: TemplateAst, vars: Vars): string;
71
+ //#endregion
72
+ export { Vars, expand };
package/dist/expand.js ADDED
@@ -0,0 +1,94 @@
1
+ import { OP, encodeComponentIdempotent } from "./spec.js";
2
+
3
+ //#region src/expand.ts
4
+ function emitNamed(spec, name, raw) {
5
+ return spec.named ? raw === "" && spec.ifEmpty === "empty" ? `${name}${spec.kvSep}` : `${name}${spec.kvSep}${raw}` : raw;
6
+ }
7
+ function expandVar(op, v, value) {
8
+ const spec = OP[op];
9
+ const enc = (str) => encodeComponentIdempotent(str, spec.allowReserved, spec.reservedSet);
10
+ if (value === void 0 || value === null) return [];
11
+ if (Array.isArray(value)) {
12
+ const items = value.filter((x) => x !== void 0).map((x) => enc(String(x)));
13
+ if (items.length === 0) {
14
+ if (spec.first === ".") return [""];
15
+ if (spec.named && spec.ifEmpty === "nameOnly") return [v.name];
16
+ if (spec.named && spec.ifEmpty === "empty") return [`${v.name}${spec.kvSep}`];
17
+ return [];
18
+ }
19
+ if (v.explode) return items.map((it) => emitNamed(spec, v.name, it));
20
+ return [emitNamed(spec, v.name, items.join(","))];
21
+ }
22
+ if (typeof value === "object" && value && !Array.isArray(value)) {
23
+ const entries = Object.entries(value).filter(([, vv]) => vv !== void 0);
24
+ if (entries.length === 0) {
25
+ if (spec.named && spec.ifEmpty === "nameOnly") return [v.name];
26
+ if (spec.named && spec.ifEmpty === "empty") return [`${v.name}${spec.kvSep}`];
27
+ return [];
28
+ }
29
+ if (v.explode) return entries.map(([k, vv]) => `${enc(k)}${spec.kvSep}${enc(String(vv))}`);
30
+ const joined = entries.map(([k, vv]) => `${enc(k)},${enc(String(vv))}`).join(",");
31
+ return [emitNamed(spec, v.name, joined)];
32
+ }
33
+ let s = String(value);
34
+ if (v.prefix !== void 0) s = s.slice(0, v.prefix);
35
+ const e = enc(s);
36
+ if (e.length === 0) {
37
+ if (spec.first === ".") return [""];
38
+ if (spec.named && spec.ifEmpty === "nameOnly") return [v.name];
39
+ if (spec.named && spec.ifEmpty === "empty") return [`${v.name}${spec.kvSep}`];
40
+ return [];
41
+ }
42
+ return [emitNamed(spec, v.name, e)];
43
+ }
44
+ /**
45
+ * Expand a parsed template with variables according to RFC 6570 (Level 1-4).
46
+ *
47
+ * @param ast - The parsed template AST to expand
48
+ * @param vars - Variables to substitute into the template
49
+ * @returns The expanded URL string
50
+ *
51
+ * @remarks
52
+ * - Idempotent percent encoding (existing `%XX` kept)
53
+ * - Operator-specific empty/undefined rules:
54
+ * - ";" empty -> `nameOnly` (";x")
55
+ * - "?" "&" empty -> "key=" ("?x=")
56
+ * - `undefined` -> `omit` (all operators)
57
+ * - Label "." emits the dot even if empty ("X{.y}" with y="" -> "X.")
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * import { parse } from "./parser.ts";
62
+ * import { expand } from "./expand.ts";
63
+ *
64
+ * const ast = parse("{+x,hello,y}");
65
+ * const url = expand(ast, { x: "1024", hello: "Hello World!", y: "768" });
66
+ * // Returns: "1024,Hello%20World!,768"
67
+ * ```
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * import { parse } from "./parser.ts";
72
+ * import { expand } from "./expand.ts";
73
+ *
74
+ * const ast = parse("{+path}/here");
75
+ * const url = expand(ast, { path: "/foo/bar" });
76
+ * // Returns: "/foo/bar/here"
77
+ * ```
78
+ */
79
+ function expand(ast, vars) {
80
+ let out = "";
81
+ for (const node of ast.nodes) if (node.kind === "literal") out += node.value;
82
+ else {
83
+ const spec = OP[node.op];
84
+ const pieces = [];
85
+ for (const v of node.vars) pieces.push(...expandVar(node.op, v, vars[v.name]));
86
+ if (pieces.length === 0) continue;
87
+ if (spec.first) out += spec.first;
88
+ out += pieces.join(spec.itemSep);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ //#endregion
94
+ export { expand };
package/dist/index.cjs ADDED
@@ -0,0 +1,9 @@
1
+ const require_expand = require('./expand.cjs');
2
+ const require_match = require('./match.cjs');
3
+ const require_parser = require('./parser.cjs');
4
+ const require_compile = require('./compile.cjs');
5
+
6
+ exports.compile = require_compile.compile;
7
+ exports.expand = require_expand.expand;
8
+ exports.match = require_match.match;
9
+ exports.parse = require_parser.parse;
@@ -0,0 +1,6 @@
1
+ import { Expression, Literal, Node, Operator, TemplateAst, VarSpec } from "./ast.cjs";
2
+ import { Vars, expand } from "./expand.cjs";
3
+ import { EncodingPolicy, MatchOptions, match } from "./match.cjs";
4
+ import { CompileOptions, CompiledTemplate, compile } from "./compile.cjs";
5
+ import { parse } from "./parser.cjs";
6
+ export { CompileOptions, CompiledTemplate, EncodingPolicy, Expression, Literal, MatchOptions, Node, Operator, TemplateAst, VarSpec, Vars, compile, expand, match, parse };
@@ -0,0 +1,6 @@
1
+ import { Expression, Literal, Node, Operator, TemplateAst, VarSpec } from "./ast.js";
2
+ import { Vars, expand } from "./expand.js";
3
+ import { EncodingPolicy, MatchOptions, match } from "./match.js";
4
+ import { CompileOptions, CompiledTemplate, compile } from "./compile.js";
5
+ import { parse } from "./parser.js";
6
+ export { CompileOptions, CompiledTemplate, EncodingPolicy, Expression, Literal, MatchOptions, Node, Operator, TemplateAst, VarSpec, Vars, compile, expand, match, parse };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { expand } from "./expand.js";
2
+ import { match } from "./match.js";
3
+ import { parse } from "./parser.js";
4
+ import { compile } from "./compile.js";
5
+
6
+ export { compile, expand, match, parse };
package/dist/match.cjs ADDED
@@ -0,0 +1,180 @@
1
+ const require_spec = require('./spec.cjs');
2
+
3
+ //#region src/match.ts
4
+ function decodeAccordingToPolicy(s, policy) {
5
+ if (policy === "opaque") return s;
6
+ if (policy === "cooked") return require_spec.strictPercentDecode(s);
7
+ return {
8
+ raw: s,
9
+ decoded: require_spec.strictPercentDecode(s)
10
+ };
11
+ }
12
+ /**
13
+ * consume a percent triplet or a single char; used to advance safely
14
+ *
15
+ * Percent triplet handling policies:
16
+ * - When advancing, treat "%XX" as a single atom to avoid slicing inside a byte.
17
+ * - In strict mode, a bare '%' or bad hex after '%' fails the match.
18
+ */
19
+ function advanceOne(url, i) {
20
+ if (i < url.length && url[i] === "%" && require_spec.looksLikePctTriplet(url, i)) return i + 3;
21
+ return i + 1;
22
+ }
23
+ /**
24
+ * Match a URL string against a compiled template and extract variables.
25
+ *
26
+ * @param ast - The parsed template AST to match against
27
+ * @param url - The URL string to match
28
+ * @param opts - Optional matching options
29
+ * @returns An object with extracted variables if matched, null otherwise
30
+ *
31
+ * Encoding policies:
32
+ * - "opaque" -> return raw percent-encoded slices (byte-for-byte round-trip)
33
+ * - "cooked" -> decode %XX exactly once
34
+ * - "lossless"-> { raw, decoded } pair
35
+ *
36
+ * Strictness:
37
+ * - strict=true rejects bad percent triplets (e.g. "%GZ"), preventing
38
+ * ambiguous or lossy normalization early.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { parse } from "./parser.ts";
43
+ * import { match } from "./match.ts";
44
+ *
45
+ * const ast = parse("/repos{/owner,repo}{?q,lang}");
46
+ * const result = match(ast, "/repos/alice/hello%2Fworld?q=a%20b&lang=en", { encoding: "opaque" });
47
+ * // Returns: { vars: { owner: "alice", repo: "hello%2Fworld", q: "a%20b", lang: "en" } }
48
+ * ```
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * import { parse } from "./parser.ts";
53
+ * import { match } from "./match.ts";
54
+ *
55
+ * const ast = parse("/files{/path}");
56
+ * const result = match(ast, "/files/a%2Fb", { encoding: "cooked" });
57
+ * // Returns: { vars: { path: "a/b" } }
58
+ * ```
59
+ */
60
+ function match(ast, url, opts) {
61
+ const policy = opts?.encoding ?? "opaque";
62
+ const strict = opts?.strict ?? true;
63
+ let i = 0;
64
+ const varsOut = {};
65
+ const readLiteral = (lit) => {
66
+ if (url.slice(i, i + lit.length) !== lit) return false;
67
+ i += lit.length;
68
+ return true;
69
+ };
70
+ const nextIs = (s) => url.slice(i, i + s.length) === s;
71
+ for (const node of ast.nodes) {
72
+ if (node.kind === "literal") {
73
+ if (!readLiteral(node.value)) return null;
74
+ continue;
75
+ }
76
+ const spec = require_spec.OP[node.op];
77
+ if (spec.first) {
78
+ if (!readLiteral(spec.first)) return null;
79
+ }
80
+ const takeUntil = (stopPred) => {
81
+ const start = i;
82
+ while (i < url.length && !stopPred(i)) {
83
+ if (strict && url[i] === "%" && !require_spec.looksLikePctTriplet(url, i)) return "";
84
+ i = advanceOne(url, i);
85
+ }
86
+ return url.slice(start, i);
87
+ };
88
+ const emitScalar = (v, raw) => {
89
+ varsOut[v.name] = decodeAccordingToPolicy(raw, policy);
90
+ };
91
+ const splitItems = (raw) => {
92
+ if (raw.length === 0) return [""];
93
+ const sep = spec.itemSep;
94
+ const out = [];
95
+ let start = 0;
96
+ for (let j = 0; j <= raw.length;) {
97
+ if (j === raw.length || raw.slice(j, j + sep.length) === sep) {
98
+ out.push(raw.slice(start, j));
99
+ j += sep.length;
100
+ start = j;
101
+ continue;
102
+ }
103
+ j = advanceOne(raw, j);
104
+ }
105
+ return out;
106
+ };
107
+ const captureOneVar = (v, last, nextLiteral) => {
108
+ const stopPred = (j) => {
109
+ if (nextLiteral && url.slice(j, j + nextLiteral.length) === nextLiteral) return true;
110
+ if (!last && spec.itemSep && url.slice(j, j + spec.itemSep.length) === spec.itemSep) return true;
111
+ return false;
112
+ };
113
+ const raw = takeUntil(stopPred);
114
+ if (raw === "" && strict && i < url.length && url[i] !== spec.itemSep && nextLiteral === null) return false;
115
+ if (v.explode) {
116
+ const parts = splitItems(raw);
117
+ if (spec.named) varsOut[v.name] = parseExplodeNamedParts(v, parts, spec, policy);
118
+ else varsOut[v.name] = parts.map((p) => decodeAccordingToPolicy(p, policy));
119
+ } else if (spec.named) {
120
+ const [ok, val] = captureNamedNonExplode(v, raw, spec, policy);
121
+ if (!ok) return false;
122
+ varsOut[v.name] = val;
123
+ } else emitScalar(v, raw);
124
+ if (!last && spec.itemSep && nextIs(spec.itemSep)) i += spec.itemSep.length;
125
+ return true;
126
+ };
127
+ for (let idx = 0; idx < node.vars.length; idx++) {
128
+ const v = node.vars[idx];
129
+ const last = idx === node.vars.length - 1;
130
+ let nextLiteral = null;
131
+ const nodeIndex = ast.nodes.indexOf(node);
132
+ for (let k = nodeIndex + 1; k < ast.nodes.length; k++) {
133
+ const n = ast.nodes[k];
134
+ if (n.kind === "literal" && n.value.length > 0) {
135
+ nextLiteral = n.value;
136
+ break;
137
+ }
138
+ if (n.kind === "expression" && require_spec.OP[n.op].first) {
139
+ nextLiteral = require_spec.OP[n.op].first;
140
+ break;
141
+ }
142
+ }
143
+ if (!captureOneVar(v, last, nextLiteral)) return null;
144
+ }
145
+ }
146
+ return { vars: varsOut };
147
+ }
148
+ function captureNamedNonExplode(v, raw, spec, policy) {
149
+ const eq = raw.indexOf(spec.kvSep);
150
+ if (eq === -1) {
151
+ if (spec.first === ";") {
152
+ if (raw === v.name) return [true, decodeAccordingToPolicy("", policy)];
153
+ }
154
+ return [false, null];
155
+ }
156
+ const lhs = raw.slice(0, eq), rhs = raw.slice(eq + spec.kvSep.length);
157
+ if (lhs !== v.name) return [false, null];
158
+ return [true, decodeAccordingToPolicy(rhs, policy)];
159
+ }
160
+ function parseExplodeNamedParts(v, parts, spec, policy) {
161
+ const allVarName = parts.every((p) => p.startsWith(v.name + spec.kvSep));
162
+ if (allVarName) return parts.map((p) => {
163
+ const rhs = p.slice((v.name + spec.kvSep).length);
164
+ return decodeAccordingToPolicy(rhs, policy);
165
+ });
166
+ const obj = {};
167
+ for (const p of parts) {
168
+ const i = p.indexOf(spec.kvSep);
169
+ if (i < 0) {
170
+ obj[decodeAccordingToPolicy(p, policy)] = "";
171
+ continue;
172
+ }
173
+ const k = p.slice(0, i), val = p.slice(i + spec.kvSep.length);
174
+ obj[decodeAccordingToPolicy(k, policy)] = decodeAccordingToPolicy(val, policy);
175
+ }
176
+ return obj;
177
+ }
178
+
179
+ //#endregion
180
+ exports.match = match;
@@ -0,0 +1,92 @@
1
+ import { TemplateAst } from "./ast.cjs";
2
+
3
+ //#region src/match.d.ts
4
+
5
+ /**
6
+ * Encoding policy for variable values when matching URLs.
7
+ *
8
+ * Three modes are supported:
9
+ * - `"opaque"` (default):
10
+ * - Treats `%XX` as opaque atoms.
11
+ * - No decoding is applied; raw bytes are preserved.
12
+ * - Guarantees byte-for-byte symmetry:
13
+ * `expand(match(url)) === url`.
14
+ * - Background: added to solve `uri-template-router#11` where
15
+ * sequences like "%30#" lost information.
16
+ *
17
+ * - `"cooked"`:
18
+ * - Decodes valid `%XX` sequences exactly once.
19
+ * - More convenient for application logic where you want
20
+ * human-readable values (`"a%20b"` → `"a b"`).
21
+ * - Guarantees semantic symmetry:
22
+ * `match(expand(vars)) === vars`.
23
+ * - Note: Name chosen to contrast with `"opaque"`: think of
24
+ * “cooked strings” (escaped string literals) in programming languages.
25
+ *
26
+ * - `"lossless"`:
27
+ * - Returns both raw and decoded forms:
28
+ * `{ raw: "a%2Fb", decoded: "a/b" }`.
29
+ * - Useful if you need to display the original URL while
30
+ * still working with decoded values.
31
+ */
32
+ type EncodingPolicy = "opaque" | "cooked" | "lossless";
33
+ /**
34
+ * Options for matching URLs against templates.
35
+ *
36
+ * @remarks
37
+ * These options control how percent-encoded sequences are handled during matching.
38
+ */
39
+ interface MatchOptions {
40
+ /**
41
+ * The encoding policy for variable values (default: "opaque").
42
+ */
43
+ encoding?: EncodingPolicy;
44
+ /**
45
+ * If true (default), malformed percent triplets cause matching to fail.
46
+ * If false, allows more lenient parsing but may lead to ambiguity.
47
+ */
48
+ strict?: boolean;
49
+ }
50
+ type VarsOut = Record<string, unknown>;
51
+ /**
52
+ * Match a URL string against a compiled template and extract variables.
53
+ *
54
+ * @param ast - The parsed template AST to match against
55
+ * @param url - The URL string to match
56
+ * @param opts - Optional matching options
57
+ * @returns An object with extracted variables if matched, null otherwise
58
+ *
59
+ * Encoding policies:
60
+ * - "opaque" -> return raw percent-encoded slices (byte-for-byte round-trip)
61
+ * - "cooked" -> decode %XX exactly once
62
+ * - "lossless"-> { raw, decoded } pair
63
+ *
64
+ * Strictness:
65
+ * - strict=true rejects bad percent triplets (e.g. "%GZ"), preventing
66
+ * ambiguous or lossy normalization early.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * import { parse } from "./parser.ts";
71
+ * import { match } from "./match.ts";
72
+ *
73
+ * const ast = parse("/repos{/owner,repo}{?q,lang}");
74
+ * const result = match(ast, "/repos/alice/hello%2Fworld?q=a%20b&lang=en", { encoding: "opaque" });
75
+ * // Returns: { vars: { owner: "alice", repo: "hello%2Fworld", q: "a%20b", lang: "en" } }
76
+ * ```
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * import { parse } from "./parser.ts";
81
+ * import { match } from "./match.ts";
82
+ *
83
+ * const ast = parse("/files{/path}");
84
+ * const result = match(ast, "/files/a%2Fb", { encoding: "cooked" });
85
+ * // Returns: { vars: { path: "a/b" } }
86
+ * ```
87
+ */
88
+ declare function match(ast: TemplateAst, url: string, opts?: MatchOptions): null | {
89
+ vars: VarsOut;
90
+ };
91
+ //#endregion
92
+ export { EncodingPolicy, MatchOptions, match };
@@ -0,0 +1,92 @@
1
+ import { TemplateAst } from "./ast.js";
2
+
3
+ //#region src/match.d.ts
4
+
5
+ /**
6
+ * Encoding policy for variable values when matching URLs.
7
+ *
8
+ * Three modes are supported:
9
+ * - `"opaque"` (default):
10
+ * - Treats `%XX` as opaque atoms.
11
+ * - No decoding is applied; raw bytes are preserved.
12
+ * - Guarantees byte-for-byte symmetry:
13
+ * `expand(match(url)) === url`.
14
+ * - Background: added to solve `uri-template-router#11` where
15
+ * sequences like "%30#" lost information.
16
+ *
17
+ * - `"cooked"`:
18
+ * - Decodes valid `%XX` sequences exactly once.
19
+ * - More convenient for application logic where you want
20
+ * human-readable values (`"a%20b"` → `"a b"`).
21
+ * - Guarantees semantic symmetry:
22
+ * `match(expand(vars)) === vars`.
23
+ * - Note: Name chosen to contrast with `"opaque"`: think of
24
+ * “cooked strings” (escaped string literals) in programming languages.
25
+ *
26
+ * - `"lossless"`:
27
+ * - Returns both raw and decoded forms:
28
+ * `{ raw: "a%2Fb", decoded: "a/b" }`.
29
+ * - Useful if you need to display the original URL while
30
+ * still working with decoded values.
31
+ */
32
+ type EncodingPolicy = "opaque" | "cooked" | "lossless";
33
+ /**
34
+ * Options for matching URLs against templates.
35
+ *
36
+ * @remarks
37
+ * These options control how percent-encoded sequences are handled during matching.
38
+ */
39
+ interface MatchOptions {
40
+ /**
41
+ * The encoding policy for variable values (default: "opaque").
42
+ */
43
+ encoding?: EncodingPolicy;
44
+ /**
45
+ * If true (default), malformed percent triplets cause matching to fail.
46
+ * If false, allows more lenient parsing but may lead to ambiguity.
47
+ */
48
+ strict?: boolean;
49
+ }
50
+ type VarsOut = Record<string, unknown>;
51
+ /**
52
+ * Match a URL string against a compiled template and extract variables.
53
+ *
54
+ * @param ast - The parsed template AST to match against
55
+ * @param url - The URL string to match
56
+ * @param opts - Optional matching options
57
+ * @returns An object with extracted variables if matched, null otherwise
58
+ *
59
+ * Encoding policies:
60
+ * - "opaque" -> return raw percent-encoded slices (byte-for-byte round-trip)
61
+ * - "cooked" -> decode %XX exactly once
62
+ * - "lossless"-> { raw, decoded } pair
63
+ *
64
+ * Strictness:
65
+ * - strict=true rejects bad percent triplets (e.g. "%GZ"), preventing
66
+ * ambiguous or lossy normalization early.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * import { parse } from "./parser.ts";
71
+ * import { match } from "./match.ts";
72
+ *
73
+ * const ast = parse("/repos{/owner,repo}{?q,lang}");
74
+ * const result = match(ast, "/repos/alice/hello%2Fworld?q=a%20b&lang=en", { encoding: "opaque" });
75
+ * // Returns: { vars: { owner: "alice", repo: "hello%2Fworld", q: "a%20b", lang: "en" } }
76
+ * ```
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * import { parse } from "./parser.ts";
81
+ * import { match } from "./match.ts";
82
+ *
83
+ * const ast = parse("/files{/path}");
84
+ * const result = match(ast, "/files/a%2Fb", { encoding: "cooked" });
85
+ * // Returns: { vars: { path: "a/b" } }
86
+ * ```
87
+ */
88
+ declare function match(ast: TemplateAst, url: string, opts?: MatchOptions): null | {
89
+ vars: VarsOut;
90
+ };
91
+ //#endregion
92
+ export { EncodingPolicy, MatchOptions, match };