@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,418 @@
1
+ import { compile } from "./compile.js";
2
+ import { deepStrictEqual } from "node:assert/strict";
3
+ import { describe, test } from "node:test";
4
+
5
+ //#region src/rfc6570.spec.ts
6
+ describe("basic expansion", () => {
7
+ test("scalar", () => {
8
+ const t = compile("{var}");
9
+ deepStrictEqual(t.expand({ var: "value" }), "value");
10
+ deepStrictEqual(t.expand({ var: "va lue" }), "va%20lue");
11
+ deepStrictEqual(t.expand({ var: "100%" }), "100%25");
12
+ });
13
+ test("list (no explode)", () => {
14
+ const t = compile("{list}");
15
+ deepStrictEqual(t.expand({ list: [
16
+ "red",
17
+ "green",
18
+ "blue"
19
+ ] }), "red,green,blue");
20
+ });
21
+ test("map (no explode)", () => {
22
+ const t = compile("{keys}");
23
+ deepStrictEqual(t.expand({ keys: {
24
+ semi: ";",
25
+ dot: ".",
26
+ comma: ","
27
+ } }), "semi,%3B,dot,.,comma,%2C");
28
+ });
29
+ test("multiple vars", () => {
30
+ const t = compile("{x,y}");
31
+ deepStrictEqual(t.expand({ x: "1024" }), "1024");
32
+ });
33
+ });
34
+ describe("{+var} reserved expansion", () => {
35
+ test("{+var} allows reserved - basic", () => {
36
+ const t = compile("{+var}");
37
+ deepStrictEqual(t.expand({ var: "value" }), "value");
38
+ });
39
+ test("{+var} allows reserved - percent encoding", () => {
40
+ const t = compile("{+hello}");
41
+ deepStrictEqual(t.expand({ hello: "Hello World!" }), "Hello%20World!");
42
+ });
43
+ test("{+var} allows reserved - already encoded", () => {
44
+ const t = compile("{+half}");
45
+ deepStrictEqual(t.expand({ half: "50%" }), "50%25");
46
+ });
47
+ test("{+var} allows reserved - base URL comparison", () => {
48
+ const t1 = compile("{base}index");
49
+ const t2 = compile("{+base}index");
50
+ const vars = { base: "http://example.com/home/" };
51
+ deepStrictEqual(t1.expand(vars), "http%3A%2F%2Fexample.com%2Fhome%2Findex");
52
+ deepStrictEqual(t2.expand(vars), "http://example.com/home/index");
53
+ });
54
+ test("{+var} allows reserved - empty value", () => {
55
+ const t = compile("O{+empty}X");
56
+ deepStrictEqual(t.expand({ empty: "" }), "OX");
57
+ });
58
+ test("{+var} allows reserved - undefined value", () => {
59
+ const t = compile("O{+undef}X");
60
+ deepStrictEqual(t.expand({ undef: void 0 }), "OX");
61
+ });
62
+ test("{+var} allows reserved - path", () => {
63
+ const t = compile("{+path}");
64
+ deepStrictEqual(t.expand({ path: "/foo/bar" }), "/foo/bar");
65
+ });
66
+ test("{+var} allows reserved - path appended", () => {
67
+ const t = compile("{+path}/here");
68
+ deepStrictEqual(t.expand({ path: "/foo/bar" }), "/foo/bar/here");
69
+ });
70
+ test("{+var} allows reserved - in query", () => {
71
+ const t = compile("here?ref={+path}");
72
+ deepStrictEqual(t.expand({ path: "/foo/bar" }), "here?ref=/foo/bar");
73
+ });
74
+ test("{+var} allows reserved - mixed with normal var", () => {
75
+ const t = compile("up{+path}{var}/here");
76
+ deepStrictEqual(t.expand({
77
+ path: "/foo/bar",
78
+ var: "value"
79
+ }), "up/foo/barvalue/here");
80
+ });
81
+ test("{+var} allows reserved - multiple vars", () => {
82
+ const t = compile("{+x,hello,y}");
83
+ deepStrictEqual(t.expand({
84
+ x: "1024",
85
+ hello: "Hello World!",
86
+ y: "768"
87
+ }), "1024,Hello%20World!,768");
88
+ });
89
+ test("{+var} allows reserved - multiple vars with path", () => {
90
+ const t = compile("{+path,x}/here");
91
+ deepStrictEqual(t.expand({
92
+ path: "/foo/bar",
93
+ x: "1024"
94
+ }), "/foo/bar,1024/here");
95
+ });
96
+ test("{+var} allows reserved - prefix modifier", () => {
97
+ const t = compile("{+path:6}/here");
98
+ deepStrictEqual(t.expand({ path: "/foo/bar" }), "/foo/b/here");
99
+ });
100
+ test("{+var} allows reserved - list", () => {
101
+ const t = compile("{+list}");
102
+ deepStrictEqual(t.expand({ list: [
103
+ "red",
104
+ "green",
105
+ "blue"
106
+ ] }), "red,green,blue");
107
+ });
108
+ test("{+var} allows reserved - list explode", () => {
109
+ const t = compile("{+list*}");
110
+ deepStrictEqual(t.expand({ list: [
111
+ "red",
112
+ "green",
113
+ "blue"
114
+ ] }), "red,green,blue");
115
+ });
116
+ test("{+var} allows reserved - map", () => {
117
+ const t = compile("{+keys}");
118
+ deepStrictEqual(t.expand({ keys: {
119
+ semi: ";",
120
+ dot: ".",
121
+ comma: ","
122
+ } }), "semi,;,dot,.,comma,,");
123
+ });
124
+ test("{+var} allows reserved - map explode", () => {
125
+ const t = compile("{+keys*}");
126
+ deepStrictEqual(t.expand({ keys: {
127
+ semi: ";",
128
+ dot: ".",
129
+ comma: ","
130
+ } }), "semi=;,dot=.,comma=,");
131
+ });
132
+ });
133
+ describe("{#var} fragment expansion", () => {
134
+ test("{#var} fragment", () => {
135
+ const t = compile("{#frag}");
136
+ deepStrictEqual(t.expand({ frag: "a b" }), "#a%20b");
137
+ deepStrictEqual(t.expand({ frag: "a/b?c#d" }), "#a/b?c%23d");
138
+ });
139
+ });
140
+ describe("{.var} label expansion", () => {
141
+ test("{.var} label - basic", () => {
142
+ const t = compile("www{.domain}");
143
+ deepStrictEqual(t.expand({ domain: "example" }), "www.example");
144
+ });
145
+ test("{.var} label - single variable", () => {
146
+ const t = compile("{.who}");
147
+ deepStrictEqual(t.expand({ who: "fred" }), ".fred");
148
+ });
149
+ test("{.var} label - same variable twice", () => {
150
+ const t = compile("{.who,who}");
151
+ deepStrictEqual(t.expand({ who: "fred" }), ".fred.fred");
152
+ });
153
+ test("{.var} label - mixed variables with encoding", () => {
154
+ const t = compile("{.half,who}");
155
+ deepStrictEqual(t.expand({
156
+ half: "50%",
157
+ who: "fred"
158
+ }), ".50%25.fred");
159
+ });
160
+ test("{.var} label - explode with multiple labels", () => {
161
+ const t = compile("www{.dom*}");
162
+ deepStrictEqual(t.expand({ dom: ["example", "com"] }), "www.example.com");
163
+ });
164
+ test("{.var} label - with prefix", () => {
165
+ const t = compile("X{.var}");
166
+ deepStrictEqual(t.expand({ var: "value" }), "X.value");
167
+ });
168
+ test("{.var} label - empty value", () => {
169
+ const t = compile("X{.empty}");
170
+ deepStrictEqual(t.expand({ empty: "" }), "X.");
171
+ });
172
+ test("{.var} label - undefined value", () => {
173
+ const t = compile("X{.undef}");
174
+ deepStrictEqual(t.expand({ undef: void 0 }), "X");
175
+ });
176
+ test("{.var} label - prefix modifier", () => {
177
+ const t = compile("X{.var:3}");
178
+ deepStrictEqual(t.expand({ var: "value" }), "X.val");
179
+ });
180
+ test("{.var} label - list without explode", () => {
181
+ const t = compile("X{.list}");
182
+ deepStrictEqual(t.expand({ list: [
183
+ "red",
184
+ "green",
185
+ "blue"
186
+ ] }), "X.red,green,blue");
187
+ });
188
+ test("{.var} label - list with explode", () => {
189
+ const t = compile("X{.list*}");
190
+ deepStrictEqual(t.expand({ list: [
191
+ "red",
192
+ "green",
193
+ "blue"
194
+ ] }), "X.red.green.blue");
195
+ });
196
+ test("{.var} label - map without explode", () => {
197
+ const t = compile("X{.keys}");
198
+ deepStrictEqual(t.expand({ keys: {
199
+ semi: ";",
200
+ dot: ".",
201
+ comma: ","
202
+ } }), "X.semi,%3B,dot,.,comma,%2C");
203
+ });
204
+ test("{.var} label - map with explode", () => {
205
+ const t = compile("X{.keys*}");
206
+ deepStrictEqual(t.expand({ keys: {
207
+ semi: ";",
208
+ dot: ".",
209
+ comma: ","
210
+ } }), "X.semi=%3B.dot=..comma=%2C");
211
+ });
212
+ test("{.var} label - empty map without explode", () => {
213
+ const t = compile("X{.empty_keys}");
214
+ deepStrictEqual(t.expand({ empty_keys: {} }), "X");
215
+ });
216
+ test("{.var} label - empty map with explode", () => {
217
+ const t = compile("X{.empty_keys*}");
218
+ deepStrictEqual(t.expand({ empty_keys: {} }), "X");
219
+ });
220
+ });
221
+ describe("path and query operators", () => {
222
+ test("{/var} path segment", () => {
223
+ const t = compile("/users{/id}");
224
+ deepStrictEqual(t.expand({ id: "a/b" }), "/users/a%2Fb");
225
+ });
226
+ test("{;x,y} matrix params", () => {
227
+ const t = compile("/res{;x,y}");
228
+ deepStrictEqual(t.expand({
229
+ x: "a b",
230
+ y: ""
231
+ }), "/res;x=a%20b;y");
232
+ deepStrictEqual(t.expand({
233
+ x: void 0,
234
+ y: void 0
235
+ }), "/res");
236
+ });
237
+ test("{?x,y} query", () => {
238
+ const t = compile("/search{?q,lang}");
239
+ deepStrictEqual(t.expand({
240
+ q: "a b",
241
+ lang: "en"
242
+ }), "/search?q=a%20b&lang=en");
243
+ deepStrictEqual(t.expand({
244
+ q: "",
245
+ lang: void 0
246
+ }), "/search?q=");
247
+ });
248
+ test("{&x,y} query continuation", () => {
249
+ const t = compile("/search?q=init{&x,y}");
250
+ deepStrictEqual(t.expand({
251
+ x: "1",
252
+ y: "2"
253
+ }), "/search?q=init&x=1&y=2");
254
+ });
255
+ });
256
+ describe("explode modifier", () => {
257
+ test("explode list", () => {
258
+ const t = compile("/tags{;tags*}");
259
+ deepStrictEqual(t.expand({ tags: [
260
+ "red",
261
+ "green",
262
+ "blue"
263
+ ] }), "/tags;tags=red;tags=green;tags=blue");
264
+ });
265
+ test("explode map", () => {
266
+ const t = compile("{?keys*}");
267
+ deepStrictEqual(t.expand({ keys: {
268
+ semi: ";",
269
+ dot: ".",
270
+ comma: ","
271
+ } }), "?semi=%3B&dot=.&comma=%2C");
272
+ });
273
+ });
274
+ describe("prefix modifier", () => {
275
+ test("prefix :n with scalar", () => {
276
+ const t = compile("{var:3}");
277
+ deepStrictEqual(t.expand({ var: "abcdef" }), "abc");
278
+ });
279
+ test("prefix with reserved content", () => {
280
+ const t = compile("/cut/{x:5}");
281
+ deepStrictEqual(t.expand({ x: "abc/def?ghi" }), "/cut/abc%2Fd");
282
+ });
283
+ });
284
+ describe("round-trip tests", () => {
285
+ test("expand(match(url)) === url (opaque)", () => {
286
+ const t = compile("/repos{/owner,repo}{?q,lang}");
287
+ const url = t.expand({
288
+ owner: "alice",
289
+ repo: "hello/world",
290
+ q: "a b",
291
+ lang: "en"
292
+ });
293
+ const m = t.match(url, { encoding: "opaque" });
294
+ deepStrictEqual(m !== null, true);
295
+ const url2 = t.expand(m.vars);
296
+ deepStrictEqual(url2, url);
297
+ });
298
+ test("match(expand(vars)) ≅ vars (cooked)", () => {
299
+ const t = compile("/u{/id}{?q}");
300
+ const vars = {
301
+ id: "a/b",
302
+ q: "x y"
303
+ };
304
+ const url = t.expand(vars);
305
+ const m = t.match(url, { encoding: "cooked" });
306
+ deepStrictEqual(m !== null, true);
307
+ deepStrictEqual(m.vars, vars);
308
+ });
309
+ test("explode list stays list in cooked", () => {
310
+ const t = compile("/t{;tags*}");
311
+ const url = t.expand({ tags: ["a b", "c"] });
312
+ const m = t.match(url, { encoding: "cooked" });
313
+ deepStrictEqual(m !== null, true);
314
+ deepStrictEqual(m.vars.tags, ["a b", "c"]);
315
+ });
316
+ test("explode map stays map in cooked", () => {
317
+ const t = compile("{?m*}");
318
+ const url = t.expand({ m: {
319
+ a: "1 2",
320
+ b: "/"
321
+ } });
322
+ const m = t.match(url, { encoding: "cooked" });
323
+ deepStrictEqual(m !== null, true);
324
+ deepStrictEqual(m.vars.m, {
325
+ a: "1 2",
326
+ b: "/"
327
+ });
328
+ });
329
+ });
330
+ describe("encoding and special cases", () => {
331
+ /**
332
+ * Regression for https://github.com/awwright/uri-template-router/issues/11:
333
+ * Ensure that percent-encoded sequences and '#' handling do not lose information.
334
+ */
335
+ test("opaque preserves raw percent sequences", () => {
336
+ const t = compile("{#frag}");
337
+ const url = t.expand({ frag: "%30#" });
338
+ const mOpaque = t.match(url, { encoding: "opaque" });
339
+ deepStrictEqual(mOpaque !== null, true);
340
+ deepStrictEqual(mOpaque.vars.frag, mOpaque.vars.frag);
341
+ const mCooked = t.match(url, { encoding: "cooked" });
342
+ deepStrictEqual(mCooked !== null, true);
343
+ deepStrictEqual(typeof mCooked.vars.frag, "string");
344
+ deepStrictEqual(mCooked.vars.frag.includes("#"), true);
345
+ });
346
+ test("bad percent triplet during match => null (strict default)", () => {
347
+ const t = compile("/x{/id}");
348
+ const m = t.match("/x/%GZ", { encoding: "opaque" });
349
+ deepStrictEqual(m, null);
350
+ });
351
+ test("idempotent expand: do not double-encode existing %XX", () => {
352
+ const t = compile("/p{/x}");
353
+ const url = t.expand({ x: "a%2Fb" });
354
+ deepStrictEqual(url, "/p/a%2Fb");
355
+ });
356
+ test("space encoding", () => {
357
+ const t = compile("/s{/x}{?q}");
358
+ const url = t.expand({
359
+ x: "a b",
360
+ q: "x y"
361
+ });
362
+ deepStrictEqual(url, "/s/a%20b?q=x%20y");
363
+ const m = t.match(url, { encoding: "cooked" });
364
+ deepStrictEqual(m.vars.x, "a b");
365
+ deepStrictEqual(m.vars.q, "x y");
366
+ });
367
+ test("greedy capture until next literal", () => {
368
+ const t = compile("/r{?q}#end");
369
+ const url = t.expand({ q: "a,b,c" });
370
+ deepStrictEqual(url, "/r?q=a%2Cb%2Cc#end");
371
+ const m = t.match(url, { encoding: "cooked" });
372
+ deepStrictEqual(m !== null, true);
373
+ deepStrictEqual(m.vars.q, "a,b,c");
374
+ });
375
+ test("multiple expressions with separators", () => {
376
+ const t = compile("/x{/a}{/b}{?c}{&d}");
377
+ const url = t.expand({
378
+ a: "1",
379
+ b: "2/3",
380
+ c: "4 5",
381
+ d: "6"
382
+ });
383
+ deepStrictEqual(url, "/x/1/2%2F3?c=4%205&d=6");
384
+ const m = t.match(url, { encoding: "cooked" });
385
+ deepStrictEqual(m.vars, {
386
+ a: "1",
387
+ b: "2/3",
388
+ c: "4 5",
389
+ d: "6"
390
+ });
391
+ });
392
+ });
393
+ describe("undefined and empty value handling", () => {
394
+ test("undefined omits in simple operator", () => {
395
+ const t = compile("A{var}Z");
396
+ deepStrictEqual(t.expand({ var: void 0 }), "AZ");
397
+ });
398
+ test("empty string is nameOnly in ';' operator", () => {
399
+ const t = compile("{;x}");
400
+ deepStrictEqual(t.expand({ x: "" }), ";x");
401
+ deepStrictEqual(t.expand({ x: void 0 }), "");
402
+ });
403
+ test("undefined query param omitted", () => {
404
+ const t = compile("/s{?q,lang}");
405
+ deepStrictEqual(t.expand({
406
+ q: void 0,
407
+ lang: "en"
408
+ }), "/s?lang=en");
409
+ });
410
+ test("match allows empty value", () => {
411
+ const t = compile("/s{?q}");
412
+ const m = t.match("/s?q=", { encoding: "opaque" });
413
+ deepStrictEqual(m !== null, true);
414
+ deepStrictEqual(m.vars.q, "");
415
+ });
416
+ });
417
+
418
+ //#endregion
package/dist/spec.cjs ADDED
@@ -0,0 +1,112 @@
1
+
2
+ //#region src/spec.ts
3
+ const UNRESERVED = new Set([..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"]);
4
+ const GEN_DELIMS = ":/?#[]@";
5
+ const SUB_DELIMS = "!$&'()*+,;=";
6
+ const RESERVED_GENERAL = new Set([...GEN_DELIMS + SUB_DELIMS]);
7
+ const RESERVED_NO_HASH = new Set([...":/?[]@" + SUB_DELIMS + "?"]);
8
+ function looksLikePctTriplet(s, i) {
9
+ if (s[i] !== "%" || i + 2 >= s.length) return false;
10
+ const a = s[i + 1], b = s[i + 2];
11
+ const hex = (c) => "0" <= c && c <= "9" || "a" <= c && c <= "f" || "A" <= c && c <= "F";
12
+ return hex(a) && hex(b);
13
+ }
14
+ const OP = {
15
+ "": {
16
+ allowReserved: false,
17
+ itemSep: ",",
18
+ kvSep: "=",
19
+ ifEmpty: "omit"
20
+ },
21
+ "+": {
22
+ allowReserved: true,
23
+ reservedSet: RESERVED_GENERAL,
24
+ itemSep: ",",
25
+ kvSep: "=",
26
+ ifEmpty: "omit"
27
+ },
28
+ "#": {
29
+ first: "#",
30
+ allowReserved: true,
31
+ reservedSet: RESERVED_NO_HASH,
32
+ itemSep: ",",
33
+ kvSep: "=",
34
+ ifEmpty: "omit"
35
+ },
36
+ ".": {
37
+ first: ".",
38
+ allowReserved: false,
39
+ itemSep: ".",
40
+ kvSep: "=",
41
+ ifEmpty: "omit"
42
+ },
43
+ "/": {
44
+ first: "/",
45
+ allowReserved: false,
46
+ itemSep: "/",
47
+ kvSep: "=",
48
+ ifEmpty: "omit"
49
+ },
50
+ ";": {
51
+ first: ";",
52
+ named: true,
53
+ allowReserved: false,
54
+ itemSep: ";",
55
+ kvSep: "=",
56
+ ifEmpty: "nameOnly"
57
+ },
58
+ "?": {
59
+ first: "?",
60
+ named: true,
61
+ allowReserved: false,
62
+ itemSep: "&",
63
+ kvSep: "=",
64
+ ifEmpty: "empty"
65
+ },
66
+ "&": {
67
+ first: "&",
68
+ named: true,
69
+ allowReserved: false,
70
+ itemSep: "&",
71
+ kvSep: "=",
72
+ ifEmpty: "empty"
73
+ }
74
+ };
75
+ function encodeComponentIdempotent(s, allowReserved, reservedSet) {
76
+ let out = "";
77
+ for (let i = 0; i < s.length;) {
78
+ if (s[i] === "%" && looksLikePctTriplet(s, i)) {
79
+ out += s.slice(i, i + 3);
80
+ i += 3;
81
+ continue;
82
+ }
83
+ const ch = s[i];
84
+ if (UNRESERVED.has(ch)) {
85
+ out += ch;
86
+ i++;
87
+ continue;
88
+ }
89
+ if (allowReserved && reservedSet?.has(ch)) {
90
+ out += ch;
91
+ i++;
92
+ continue;
93
+ }
94
+ const bytes = new TextEncoder().encode(ch);
95
+ for (const b of bytes) out += "%" + b.toString(16).toUpperCase().padStart(2, "0");
96
+ i++;
97
+ }
98
+ return out;
99
+ }
100
+ function strictPercentDecode(s) {
101
+ for (let i = 0; i < s.length;) if (s[i] === "%") {
102
+ if (!looksLikePctTriplet(s, i)) throw new Error("Bad percent sequence");
103
+ i += 3;
104
+ } else i++;
105
+ return decodeURIComponent(s);
106
+ }
107
+
108
+ //#endregion
109
+ exports.OP = OP;
110
+ exports.encodeComponentIdempotent = encodeComponentIdempotent;
111
+ exports.looksLikePctTriplet = looksLikePctTriplet;
112
+ exports.strictPercentDecode = strictPercentDecode;
package/dist/spec.js ADDED
@@ -0,0 +1,108 @@
1
+ //#region src/spec.ts
2
+ const UNRESERVED = new Set([..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"]);
3
+ const GEN_DELIMS = ":/?#[]@";
4
+ const SUB_DELIMS = "!$&'()*+,;=";
5
+ const RESERVED_GENERAL = new Set([...GEN_DELIMS + SUB_DELIMS]);
6
+ const RESERVED_NO_HASH = new Set([...":/?[]@" + SUB_DELIMS + "?"]);
7
+ function looksLikePctTriplet(s, i) {
8
+ if (s[i] !== "%" || i + 2 >= s.length) return false;
9
+ const a = s[i + 1], b = s[i + 2];
10
+ const hex = (c) => "0" <= c && c <= "9" || "a" <= c && c <= "f" || "A" <= c && c <= "F";
11
+ return hex(a) && hex(b);
12
+ }
13
+ const OP = {
14
+ "": {
15
+ allowReserved: false,
16
+ itemSep: ",",
17
+ kvSep: "=",
18
+ ifEmpty: "omit"
19
+ },
20
+ "+": {
21
+ allowReserved: true,
22
+ reservedSet: RESERVED_GENERAL,
23
+ itemSep: ",",
24
+ kvSep: "=",
25
+ ifEmpty: "omit"
26
+ },
27
+ "#": {
28
+ first: "#",
29
+ allowReserved: true,
30
+ reservedSet: RESERVED_NO_HASH,
31
+ itemSep: ",",
32
+ kvSep: "=",
33
+ ifEmpty: "omit"
34
+ },
35
+ ".": {
36
+ first: ".",
37
+ allowReserved: false,
38
+ itemSep: ".",
39
+ kvSep: "=",
40
+ ifEmpty: "omit"
41
+ },
42
+ "/": {
43
+ first: "/",
44
+ allowReserved: false,
45
+ itemSep: "/",
46
+ kvSep: "=",
47
+ ifEmpty: "omit"
48
+ },
49
+ ";": {
50
+ first: ";",
51
+ named: true,
52
+ allowReserved: false,
53
+ itemSep: ";",
54
+ kvSep: "=",
55
+ ifEmpty: "nameOnly"
56
+ },
57
+ "?": {
58
+ first: "?",
59
+ named: true,
60
+ allowReserved: false,
61
+ itemSep: "&",
62
+ kvSep: "=",
63
+ ifEmpty: "empty"
64
+ },
65
+ "&": {
66
+ first: "&",
67
+ named: true,
68
+ allowReserved: false,
69
+ itemSep: "&",
70
+ kvSep: "=",
71
+ ifEmpty: "empty"
72
+ }
73
+ };
74
+ function encodeComponentIdempotent(s, allowReserved, reservedSet) {
75
+ let out = "";
76
+ for (let i = 0; i < s.length;) {
77
+ if (s[i] === "%" && looksLikePctTriplet(s, i)) {
78
+ out += s.slice(i, i + 3);
79
+ i += 3;
80
+ continue;
81
+ }
82
+ const ch = s[i];
83
+ if (UNRESERVED.has(ch)) {
84
+ out += ch;
85
+ i++;
86
+ continue;
87
+ }
88
+ if (allowReserved && reservedSet?.has(ch)) {
89
+ out += ch;
90
+ i++;
91
+ continue;
92
+ }
93
+ const bytes = new TextEncoder().encode(ch);
94
+ for (const b of bytes) out += "%" + b.toString(16).toUpperCase().padStart(2, "0");
95
+ i++;
96
+ }
97
+ return out;
98
+ }
99
+ function strictPercentDecode(s) {
100
+ for (let i = 0; i < s.length;) if (s[i] === "%") {
101
+ if (!looksLikePctTriplet(s, i)) throw new Error("Bad percent sequence");
102
+ i += 3;
103
+ } else i++;
104
+ return decodeURIComponent(s);
105
+ }
106
+
107
+ //#endregion
108
+ export { OP, encodeComponentIdempotent, looksLikePctTriplet, strictPercentDecode };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@fedify/uri-template",
3
+ "version": "2.0.0-pr.475.1",
4
+ "description": "URI Template (RFC 6570) utilities for Fedify",
5
+ "keywords": [
6
+ "Fedify",
7
+ "URI",
8
+ "template",
9
+ "RFC6570"
10
+ ],
11
+ "author": {
12
+ "name": "Lee ByeongJun",
13
+ "email": "lbj199874@gmail.com"
14
+ },
15
+ "homepage": "https://fedify.dev/",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/fedify-dev/fedify.git",
19
+ "directory": "packages/uri-template"
20
+ },
21
+ "license": "MIT",
22
+ "bugs": {
23
+ "url": "https://github.com/fedify-dev/fedify/issues"
24
+ },
25
+ "funding": [
26
+ "https://opencollective.com/fedify",
27
+ "https://github.com/sponsors/dahlia"
28
+ ],
29
+ "type": "module",
30
+ "module": "./dist/index.js",
31
+ "main": "./dist/index.cjs",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": {
36
+ "require": "./dist/index.d.cts",
37
+ "default": "./dist/index.d.ts"
38
+ },
39
+ "default": {
40
+ "require": "./dist/index.cjs",
41
+ "default": "./dist/index.js"
42
+ }
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "files": [
47
+ "dist/",
48
+ "package.json"
49
+ ],
50
+ "devDependencies": {
51
+ "@types/node": "^22.17.0",
52
+ "tsdown": "^0.12.9",
53
+ "typescript": "^5.9.3"
54
+ },
55
+ "scripts": {
56
+ "build": "deno task codegen && tsdown",
57
+ "prepublish": "pnpm run build",
58
+ "test": "tsdown && node --experimental-transform-types --test"
59
+ }
60
+ }