@fedify/vocab-runtime 2.3.11-dev.2280 → 2.3.11

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.
Files changed (45) hide show
  1. package/deno.json +2 -2
  2. package/dist/mod.cjs +91 -4
  3. package/dist/mod.d.cts +30 -2
  4. package/dist/mod.d.ts +30 -2
  5. package/dist/mod.js +91 -5
  6. package/dist/tests/{body-Bkseh1QO.mjs → body-Cfk4XwBC.mjs} +1 -1
  7. package/dist/tests/{body-DuDpNk3l.cjs → body-kujxx7yw.cjs} +1 -1
  8. package/dist/tests/body.test.cjs +1 -1
  9. package/dist/tests/body.test.mjs +1 -1
  10. package/dist/tests/decimal.test.cjs +3 -2
  11. package/dist/tests/decimal.test.mjs +3 -2
  12. package/dist/tests/{docloader-BHCG9eDn.mjs → docloader-BeuDl5a0.mjs} +43 -3
  13. package/dist/tests/{docloader-56AWfLMh.cjs → docloader-Bx_LR9xG.cjs} +43 -3
  14. package/dist/tests/docloader.test.cjs +168 -43
  15. package/dist/tests/docloader.test.mjs +158 -34
  16. package/dist/tests/{langstr-DDLc4833.mjs → langstr-ABdAOKlF.mjs} +5 -2
  17. package/dist/tests/{langstr-BOQHfJDr.cjs → langstr-BsqFJupv.cjs} +5 -2
  18. package/dist/tests/langstr.test.cjs +8 -1
  19. package/dist/tests/langstr.test.mjs +9 -2
  20. package/dist/tests/langtag-DgySESmk.cjs +50 -0
  21. package/dist/tests/langtag-LDBqlxHv.mjs +45 -0
  22. package/dist/tests/langtag.test.cjs +46 -0
  23. package/dist/tests/langtag.test.d.cts +1 -0
  24. package/dist/tests/langtag.test.d.mts +1 -0
  25. package/dist/tests/langtag.test.mjs +46 -0
  26. package/dist/tests/multibase/multibase.test.cjs +7 -7
  27. package/dist/tests/multibase/multibase.test.mjs +5 -5
  28. package/dist/tests/{request-DWyJcJ_e.mjs → request-D6bjY2cF.mjs} +1 -1
  29. package/dist/tests/{request-CVg2A6dZ.cjs → request-DPh7-Q1i.cjs} +1 -1
  30. package/dist/tests/request.test.cjs +1 -1
  31. package/dist/tests/request.test.mjs +1 -1
  32. package/package.json +6 -3
  33. package/scripts/test-bun.mjs +18 -0
  34. package/src/contexts/fep-22cd.json +14 -0
  35. package/src/contexts/fep-6757.json +12 -0
  36. package/src/contexts/joinmastodon.json +1 -1
  37. package/src/contexts.ts +16 -0
  38. package/src/docloader.test.ts +222 -67
  39. package/src/docloader.ts +2 -0
  40. package/src/langstr.test.ts +18 -1
  41. package/src/langstr.ts +6 -2
  42. package/src/langtag.test.ts +53 -0
  43. package/src/langtag.ts +76 -0
  44. package/src/mod.ts +1 -0
  45. package/src/multibase/multibase.test.ts +5 -5
@@ -1,12 +1,13 @@
1
- import { t as FetchError } from "./request-DWyJcJ_e.mjs";
2
- import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-BHCG9eDn.mjs";
1
+ import { t as FetchError } from "./request-D6bjY2cF.mjs";
2
+ import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-BeuDl5a0.mjs";
3
3
  import { t as UrlError } from "./url-3fyJuqC0.mjs";
4
- import { test } from "node:test";
5
4
  import { gzipSync } from "node:zlib";
6
5
  import { configure, reset } from "@logtape/logtape";
7
6
  import { deepStrictEqual, ok, rejects } from "node:assert";
8
7
  import dns from "node:dns/promises";
8
+ import { test } from "@fedify/fixture";
9
9
  import { createServer } from "node:http";
10
+ import jsonld from "jsonld/dist/jsonld.esm.js";
10
11
  //#region \0rolldown/runtime.js
11
12
  var __create = Object.create;
12
13
  var __defProp = Object.defineProperty;
@@ -1228,6 +1229,9 @@ var esm_default = new class FetchMock {
1228
1229
  }
1229
1230
  }({ ...defaultFetchMockConfig });
1230
1231
  //#endregion
1232
+ //#region src/jsonld.ts
1233
+ var jsonld_default = jsonld;
1234
+ //#endregion
1231
1235
  //#region src/docloader.test.ts
1232
1236
  test("new FetchError()", () => {
1233
1237
  const e = new FetchError("https://example.com/", "An error message.");
@@ -1251,7 +1255,7 @@ test("getDocumentLoader()", async (t) => {
1251
1255
  name: "Fetched object",
1252
1256
  type: "Object"
1253
1257
  } });
1254
- await t.test("ok", async () => {
1258
+ await t.step("ok", async () => {
1255
1259
  deepStrictEqual(await fetchDocumentLoader("https://example.com/object"), {
1256
1260
  contextUrl: null,
1257
1261
  documentUrl: "https://example.com/object",
@@ -1294,7 +1298,7 @@ test("getDocumentLoader()", async (t) => {
1294
1298
  Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\""
1295
1299
  }
1296
1300
  });
1297
- await t.test("Link header", async () => {
1301
+ await t.step("Link header", async () => {
1298
1302
  deepStrictEqual(await fetchDocumentLoader("https://example.com/link-ctx"), {
1299
1303
  contextUrl: "https://www.w3.org/ns/activitystreams",
1300
1304
  documentUrl: "https://example.com/link-ctx",
@@ -1315,7 +1319,7 @@ test("getDocumentLoader()", async (t) => {
1315
1319
  }
1316
1320
  });
1317
1321
  });
1318
- await t.test("Link header relative url", async () => {
1322
+ await t.step("Link header relative url", async () => {
1319
1323
  deepStrictEqual(await fetchDocumentLoader("https://example.com/link-ctx"), {
1320
1324
  contextUrl: "https://www.w3.org/ns/activitystreams",
1321
1325
  documentUrl: "https://example.com/link-ctx",
@@ -1336,7 +1340,7 @@ test("getDocumentLoader()", async (t) => {
1336
1340
  }
1337
1341
  });
1338
1342
  });
1339
- await t.test("wrong Link header syntax", async () => {
1343
+ await t.step("wrong Link header syntax", async () => {
1340
1344
  deepStrictEqual(await fetchDocumentLoader("https://example.com/obj-w-wrong-link"), {
1341
1345
  contextUrl: null,
1342
1346
  documentUrl: "https://example.com/obj-w-wrong-link",
@@ -1360,7 +1364,7 @@ test("getDocumentLoader()", async (t) => {
1360
1364
  </html>`,
1361
1365
  headers: { "Content-Type": "text/html; charset=utf-8" }
1362
1366
  });
1363
- await t.test("HTML <link>", async () => {
1367
+ await t.step("HTML <link>", async () => {
1364
1368
  deepStrictEqual(await fetchDocumentLoader("https://example.com/html-link"), {
1365
1369
  contextUrl: null,
1366
1370
  documentUrl: "https://example.com/object",
@@ -1384,7 +1388,7 @@ test("getDocumentLoader()", async (t) => {
1384
1388
  </html>`,
1385
1389
  headers: { "Content-Type": "application/xhtml+xml; charset=utf-8" }
1386
1390
  });
1387
- await t.test("XHTML <link>", async () => {
1391
+ await t.step("XHTML <link>", async () => {
1388
1392
  deepStrictEqual(await fetchDocumentLoader("https://example.com/xhtml-link"), {
1389
1393
  contextUrl: null,
1390
1394
  documentUrl: "https://example.com/object",
@@ -1410,7 +1414,7 @@ test("getDocumentLoader()", async (t) => {
1410
1414
  </html>`,
1411
1415
  headers: { "Content-Type": "text/html; charset=utf-8" }
1412
1416
  });
1413
- await t.test("HTML <a>", async () => {
1417
+ await t.step("HTML <a>", async () => {
1414
1418
  deepStrictEqual(await fetchDocumentLoader("https://example.com/html-a"), {
1415
1419
  contextUrl: null,
1416
1420
  documentUrl: "https://example.com/object",
@@ -1431,7 +1435,7 @@ test("getDocumentLoader()", async (t) => {
1431
1435
  },
1432
1436
  headers: { "Content-Type": "text/html; charset=utf-8" }
1433
1437
  });
1434
- await t.test("Wrong Content-Type", async () => {
1438
+ await t.step("Wrong Content-Type", async () => {
1435
1439
  deepStrictEqual(await fetchDocumentLoader("https://example.com/wrong-content-type"), {
1436
1440
  contextUrl: null,
1437
1441
  documentUrl: "https://example.com/wrong-content-type",
@@ -1444,18 +1448,33 @@ test("getDocumentLoader()", async (t) => {
1444
1448
  });
1445
1449
  });
1446
1450
  esm_default.get("https://example.com/404", { status: 404 });
1447
- await t.test("not ok", async () => {
1451
+ await t.step("not ok", async () => {
1448
1452
  await rejects(() => fetchDocumentLoader("https://example.com/404"), FetchError, "HTTP 404: https://example.com/404");
1449
1453
  });
1450
- await t.test("preloaded contexts", async () => {
1454
+ await t.step("preloaded contexts", async () => {
1451
1455
  for (const [url, document] of Object.entries(preloadedContexts)) deepStrictEqual(await fetchDocumentLoader(url), {
1452
1456
  contextUrl: null,
1453
1457
  documentUrl: url,
1454
1458
  document
1455
1459
  });
1456
1460
  });
1461
+ await t.step("preloaded Mastodon attribution domains", async () => {
1462
+ const context = ["https://www.w3.org/ns/activitystreams", "http://joinmastodon.org/ns"];
1463
+ const expanded = await jsonld_default.expand({
1464
+ "@context": context,
1465
+ id: "https://social.example/users/alice",
1466
+ type: "Person",
1467
+ attributionDomains: ["blog.example"]
1468
+ }, { documentLoader: fetchDocumentLoader });
1469
+ deepStrictEqual(expanded, [{
1470
+ "@id": "https://social.example/users/alice",
1471
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
1472
+ "http://joinmastodon.org/ns#attributionDomains": [{ "@value": "blog.example" }]
1473
+ }]);
1474
+ deepStrictEqual((await jsonld_default.compact(expanded, context, { documentLoader: fetchDocumentLoader })).attributionDomains, ["blog.example"]);
1475
+ });
1457
1476
  esm_default.get("https://www.w3.org/ns/cid/v1", { status: 503 });
1458
- await t.test("preloaded CID v1 context", async () => {
1477
+ await t.step("preloaded CID v1 context", async () => {
1459
1478
  const url = "https://www.w3.org/ns/cid/v1";
1460
1479
  deepStrictEqual(await fetchDocumentLoader(url), {
1461
1480
  contextUrl: null,
@@ -1463,7 +1482,7 @@ test("getDocumentLoader()", async (t) => {
1463
1482
  document: cid_v1_default
1464
1483
  });
1465
1484
  });
1466
- await t.test("preloaded FEP-ef61 context", async () => {
1485
+ await t.step("preloaded FEP-ef61 context", async () => {
1467
1486
  const url = "https://w3id.org/fep/ef61";
1468
1487
  ok(url in preloadedContexts);
1469
1488
  deepStrictEqual(await fetchDocumentLoader(url), {
@@ -1480,7 +1499,7 @@ test("getDocumentLoader()", async (t) => {
1480
1499
  });
1481
1500
  });
1482
1501
  esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
1483
- await t.test("preloaded FEP-7aa9 context", async () => {
1502
+ await t.step("preloaded FEP-7aa9 context", async () => {
1484
1503
  const url = "https://w3id.org/fep/7aa9";
1485
1504
  deepStrictEqual(await fetchDocumentLoader(url), {
1486
1505
  contextUrl: null,
@@ -1510,7 +1529,115 @@ test("getDocumentLoader()", async (t) => {
1510
1529
  });
1511
1530
  deepStrictEqual(esm_default.callHistory.calls(url).length, 0);
1512
1531
  });
1513
- await t.test("deny non-HTTP/HTTPS", async () => {
1532
+ esm_default.get("https://w3id.org/fep/6757", { status: 502 });
1533
+ await t.step("preloaded FEP-6757 context", async () => {
1534
+ const url = "https://w3id.org/fep/6757";
1535
+ deepStrictEqual(await fetchDocumentLoader(url), {
1536
+ contextUrl: null,
1537
+ documentUrl: url,
1538
+ document: { "@context": {
1539
+ license: {
1540
+ "@id": "http://purl.org/dc/terms/license",
1541
+ "@type": "@id"
1542
+ },
1543
+ preferredLicense: {
1544
+ "@id": "https://w3id.org/fep/6757#preferredLicense",
1545
+ "@type": "@id"
1546
+ }
1547
+ } }
1548
+ });
1549
+ deepStrictEqual(await jsonld_default.expand({
1550
+ "@context": ["https://www.w3.org/ns/activitystreams", url],
1551
+ type: "Person",
1552
+ license: "https://creativecommons.org/licenses/by/4.0/",
1553
+ preferredLicense: "https://creativecommons.org/licenses/by-nc/4.0/"
1554
+ }, { documentLoader: fetchDocumentLoader }), [{
1555
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
1556
+ "http://purl.org/dc/terms/license": [{ "@id": "https://creativecommons.org/licenses/by/4.0/" }],
1557
+ "https://w3id.org/fep/6757#preferredLicense": [{ "@id": "https://creativecommons.org/licenses/by-nc/4.0/" }]
1558
+ }]);
1559
+ deepStrictEqual(esm_default.callHistory.calls(url).length, 0);
1560
+ });
1561
+ esm_default.get("https://w3id.org/fep/22cd", { status: 502 });
1562
+ await t.step("preloaded FEP-22cd context", async () => {
1563
+ const url = "https://w3id.org/fep/22cd";
1564
+ const articleId = "https://example.com/articles/1";
1565
+ const translatorId = "https://example.com/users/alice";
1566
+ const revisionId = "https://example.com/articles/1/revisions/1";
1567
+ deepStrictEqual(await fetchDocumentLoader(url), {
1568
+ contextUrl: null,
1569
+ documentUrl: url,
1570
+ document: { "@context": {
1571
+ "fep-22cd": "https://w3id.org/fep/22cd#",
1572
+ schema: "https://schema.org/",
1573
+ xsd: "http://www.w3.org/2001/XMLSchema#",
1574
+ translations: {
1575
+ "@id": "fep-22cd:translations",
1576
+ "@container": "@set"
1577
+ },
1578
+ Translation: "fep-22cd:Translation",
1579
+ sourceUpdated: {
1580
+ "@id": "fep-22cd:sourceUpdated",
1581
+ "@type": "xsd:dateTime"
1582
+ },
1583
+ translator: {
1584
+ "@id": "schema:translator",
1585
+ "@type": "@id",
1586
+ "@container": "@set"
1587
+ },
1588
+ inLanguage: "schema:inLanguage",
1589
+ translationOfWork: {
1590
+ "@id": "schema:translationOfWork",
1591
+ "@type": "@id"
1592
+ },
1593
+ isBasedOn: {
1594
+ "@id": "schema:isBasedOn",
1595
+ "@type": "@id"
1596
+ }
1597
+ } }
1598
+ });
1599
+ deepStrictEqual(await jsonld_default.expand({
1600
+ "@context": ["https://www.w3.org/ns/activitystreams", url],
1601
+ id: articleId,
1602
+ type: "Article",
1603
+ contentMap: {
1604
+ en: "Original",
1605
+ ko: "Translation"
1606
+ },
1607
+ translations: [{
1608
+ type: "Translation",
1609
+ inLanguage: "ko",
1610
+ translator: [translatorId],
1611
+ translationOfWork: articleId,
1612
+ sourceUpdated: "2026-09-01T00:00:00Z",
1613
+ isBasedOn: revisionId
1614
+ }]
1615
+ }, { documentLoader: fetchDocumentLoader }), [{
1616
+ "@id": articleId,
1617
+ "@type": ["https://www.w3.org/ns/activitystreams#Article"],
1618
+ "https://www.w3.org/ns/activitystreams#content": [{
1619
+ "@language": "en",
1620
+ "@value": "Original"
1621
+ }, {
1622
+ "@language": "ko",
1623
+ "@value": "Translation"
1624
+ }],
1625
+ "https://w3id.org/fep/22cd#translations": [{
1626
+ "@type": ["https://w3id.org/fep/22cd#Translation"],
1627
+ "https://schema.org/inLanguage": [{ "@value": "ko" }],
1628
+ "https://schema.org/translator": [{ "@id": translatorId }],
1629
+ "https://schema.org/translationOfWork": [{ "@id": articleId }],
1630
+ "https://w3id.org/fep/22cd#sourceUpdated": [{
1631
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
1632
+ "@value": "2026-09-01T00:00:00Z"
1633
+ }],
1634
+ "https://schema.org/isBasedOn": [{ "@id": revisionId }]
1635
+ }]
1636
+ }]);
1637
+ deepStrictEqual(esm_default.callHistory.calls(url).length, 0);
1638
+ deepStrictEqual(esm_default.callHistory.calls("https://www.w3.org/ns/activitystreams").length, 0);
1639
+ });
1640
+ await t.step("deny non-HTTP/HTTPS", async () => {
1514
1641
  await rejects(() => fetchDocumentLoader("ftp://localhost"), UrlError);
1515
1642
  });
1516
1643
  esm_default.get("https://example.com/localhost-redirect", {
@@ -1535,13 +1662,13 @@ test("getDocumentLoader()", async (t) => {
1535
1662
  name: "Fetched object",
1536
1663
  type: "Object"
1537
1664
  } });
1538
- await t.test("allowPrivateAddress: false", async () => {
1665
+ await t.step("allowPrivateAddress: false", async () => {
1539
1666
  await rejects(() => fetchDocumentLoader("https://localhost/object"), UrlError);
1540
1667
  await rejects(() => fetchDocumentLoader("https://example.com/localhost-redirect"), UrlError);
1541
1668
  await rejects(() => fetchDocumentLoader("https://example.com/localhost-link"), UrlError);
1542
1669
  });
1543
1670
  const fetchDocumentLoader2 = getDocumentLoader({ allowPrivateAddress: true });
1544
- await t.test("allowPrivateAddress: true", async () => {
1671
+ await t.step("allowPrivateAddress: true", async () => {
1545
1672
  const expected = {
1546
1673
  contextUrl: null,
1547
1674
  documentUrl: "https://localhost/object",
@@ -1564,12 +1691,12 @@ test("getDocumentLoader()", async (t) => {
1564
1691
  headers: { Location: `https://example.com/too-many-redirects/${Number(cl.url.split("/").at(-1)) + 1}` }
1565
1692
  };
1566
1693
  });
1567
- await t.test("too many redirects", async () => {
1694
+ await t.step("too many redirects", async () => {
1568
1695
  redirectAttempts = 0;
1569
1696
  await rejects(() => fetchDocumentLoader("https://example.com/too-many-redirects/0"), FetchError, "Too many redirections");
1570
1697
  deepStrictEqual(redirectAttempts, 21);
1571
1698
  });
1572
- await t.test("custom max redirection", async () => {
1699
+ await t.step("custom max redirection", async () => {
1573
1700
  redirectAttempts = 0;
1574
1701
  const loader = getDocumentLoader({ maxRedirection: 1 });
1575
1702
  await rejects(() => loader("https://example.com/too-many-redirects/0"), FetchError, "Too many redirections");
@@ -1590,7 +1717,7 @@ test("getDocumentLoader()", async (t) => {
1590
1717
  headers: { Location: "https://example.com/redirect-loop-a" }
1591
1718
  };
1592
1719
  });
1593
- await t.test("redirect loop", async () => {
1720
+ await t.step("redirect loop", async () => {
1594
1721
  loopAttempts = 0;
1595
1722
  await rejects(() => fetchDocumentLoader("https://example.com/redirect-loop-a"), FetchError, "Redirect loop detected");
1596
1723
  deepStrictEqual(loopAttempts, 2);
@@ -1603,7 +1730,7 @@ test("getDocumentLoader()", async (t) => {
1603
1730
  headers: { Location: "/redirect-loop-relative" }
1604
1731
  };
1605
1732
  });
1606
- await t.test("redirect loop with relative location", async () => {
1733
+ await t.step("redirect loop with relative location", async () => {
1607
1734
  relativeLoopAttempts = 0;
1608
1735
  await rejects(() => fetchDocumentLoader("https://example.com/redirect-loop-relative"), FetchError, "Redirect loop detected");
1609
1736
  deepStrictEqual(relativeLoopAttempts, 1);
@@ -1613,7 +1740,7 @@ test("getDocumentLoader()", async (t) => {
1613
1740
  body: maliciousPayload,
1614
1741
  headers: { "Content-Type": "text/html; charset=utf-8" }
1615
1742
  });
1616
- await t.test("ReDoS resistance (CVE-2025-68475)", async () => {
1743
+ await t.step("ReDoS resistance (CVE-2025-68475)", async () => {
1617
1744
  const start = performance.now();
1618
1745
  await rejects(() => fetchDocumentLoader("https://example.com/redos"), SyntaxError);
1619
1746
  const elapsed = performance.now() - start;
@@ -1699,16 +1826,13 @@ test("getRemoteDocument() bounds JSON by default", async () => {
1699
1826
  deepStrictEqual(pulls, 257);
1700
1827
  });
1701
1828
  test("getDocumentLoader() logs DNS failures as such", async (t) => {
1702
- if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") {
1703
- t.skip("requires the net permission");
1704
- return;
1705
- }
1829
+ if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
1706
1830
  const loader = getDocumentLoader();
1707
1831
  for (const result of [
1708
1832
  "throws",
1709
1833
  "empty",
1710
1834
  "private"
1711
- ]) await t.test(result, async () => {
1835
+ ]) await t.step(result, async () => {
1712
1836
  const originalLookup = dns.lookup;
1713
1837
  dns.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
1714
1838
  address: "127.0.0.1",
@@ -1763,7 +1887,7 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
1763
1887
  "header",
1764
1888
  "html",
1765
1889
  "mixed"
1766
- ]) for (const hops of [20, 21]) await t.test(`${mode}: ${hops} hops`, async () => {
1890
+ ]) for (const hops of [20, 21]) await t.step(`${mode}: ${hops} hops`, async () => {
1767
1891
  esm_default.mockGlobal();
1768
1892
  let requests = 0;
1769
1893
  esm_default.get(`begin:${base}`, ({ url }) => {
@@ -1792,7 +1916,7 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
1792
1916
  "alternate-redirect",
1793
1917
  "redirect-alternate",
1794
1918
  "redirect-intermediate"
1795
- ]) await t.test(`${mode}: cycle`, async () => {
1919
+ ]) await t.step(`${mode}: cycle`, async () => {
1796
1920
  esm_default.mockGlobal();
1797
1921
  let requests = 0;
1798
1922
  esm_default.get(`begin:${base}`, ({ url }) => {
@@ -1814,7 +1938,7 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
1814
1938
  esm_default.hardReset();
1815
1939
  }
1816
1940
  });
1817
- await t.test("alternate followed by 20 redirects shares the limit", async () => {
1941
+ await t.step("alternate followed by 20 redirects shares the limit", async () => {
1818
1942
  esm_default.mockGlobal();
1819
1943
  let requests = 0;
1820
1944
  esm_default.get(`begin:${base}`, ({ url }) => {
@@ -1834,7 +1958,7 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
1834
1958
  esm_default.hardReset();
1835
1959
  }
1836
1960
  });
1837
- await t.test("relative alternate after redirect and isolated calls", async () => {
1961
+ await t.step("relative alternate after redirect and isolated calls", async () => {
1838
1962
  esm_default.mockGlobal();
1839
1963
  esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
1840
1964
  esm_default.get(`${base}html`, alternate("./document", true));
@@ -1861,7 +1985,7 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
1861
1985
  test("getDocumentLoader() preserves cancellation across alternates", async (t) => {
1862
1986
  const base = "https://example.com/alternate-abort/";
1863
1987
  const loader = getDocumentLoader({ allowPrivateAddress: true });
1864
- for (const html of [false, true]) await t.test(html ? "HTML" : "Link header", async () => {
1988
+ for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
1865
1989
  esm_default.mockGlobal();
1866
1990
  const controller = new AbortController();
1867
1991
  let requests = 0;
@@ -1,3 +1,4 @@
1
+ import { t as normalizeLanguageTag } from "./langtag-LDBqlxHv.mjs";
1
2
  //#region src/langstr.ts
2
3
  /**
3
4
  * A language-tagged string which corresponds to the `rdf:langString` type.
@@ -12,11 +13,13 @@ var LanguageString = class extends String {
12
13
  * Constructs a new `LanguageString`.
13
14
  * @param value A string value written in the given language.
14
15
  * @param language The language of the string. If a string is given, it will
15
- * be parsed as a `Intl.Locale` object.
16
+ * be parsed as a `Intl.Locale` object. Extended language
17
+ * subtags are normalized first, e.g., `zh-yue` is parsed as
18
+ * `yue`.
16
19
  */
17
20
  constructor(value, language) {
18
21
  super(value);
19
- this.locale = typeof language === "string" ? new Intl.Locale(language) : language;
22
+ this.locale = typeof language === "string" ? new Intl.Locale(normalizeLanguageTag(language)) : language;
20
23
  }
21
24
  };
22
25
  LanguageString.prototype[Symbol.for("Deno.customInspect")] = function(inspect, options) {
@@ -1,3 +1,4 @@
1
+ const require_langtag = require("./langtag-DgySESmk.cjs");
1
2
  //#region src/langstr.ts
2
3
  /**
3
4
  * A language-tagged string which corresponds to the `rdf:langString` type.
@@ -12,11 +13,13 @@ var LanguageString = class extends String {
12
13
  * Constructs a new `LanguageString`.
13
14
  * @param value A string value written in the given language.
14
15
  * @param language The language of the string. If a string is given, it will
15
- * be parsed as a `Intl.Locale` object.
16
+ * be parsed as a `Intl.Locale` object. Extended language
17
+ * subtags are normalized first, e.g., `zh-yue` is parsed as
18
+ * `yue`.
16
19
  */
17
20
  constructor(value, language) {
18
21
  super(value);
19
- this.locale = typeof language === "string" ? new Intl.Locale(language) : language;
22
+ this.locale = typeof language === "string" ? new Intl.Locale(require_langtag.normalizeLanguageTag(language)) : language;
20
23
  }
21
24
  };
22
25
  LanguageString.prototype[Symbol.for("Deno.customInspect")] = function(inspect, options) {
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-C2EiDwsr.cjs");
2
- const require_langstr = require("./langstr-BOQHfJDr.cjs");
2
+ const require_langstr = require("./langstr-BsqFJupv.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_assert = require("node:assert");
5
5
  let node_util = require("node:util");
@@ -11,6 +11,13 @@ node_util = require_chunk.__toESM(node_util, 1);
11
11
  (0, node_assert.deepStrictEqual)(langStr.locale, new Intl.Locale("en"));
12
12
  (0, node_assert.deepStrictEqual)(new require_langstr.LanguageString("Hello", new Intl.Locale("en")), langStr);
13
13
  });
14
+ (0, node_test.test)("new LanguageString() with extlang tags", () => {
15
+ (0, node_assert.deepStrictEqual)(new require_langstr.LanguageString("你好", "zh-YUE").locale.baseName, "yue");
16
+ (0, node_assert.deepStrictEqual)(new require_langstr.LanguageString("你好", "zh-yue-HK").locale.baseName, "yue-HK");
17
+ (0, node_assert.deepStrictEqual)(new require_langstr.LanguageString("你好", "zh-cmn-Hant").locale.baseName, new Intl.Locale("cmn-Hant").baseName);
18
+ (0, node_assert.throws)(() => new require_langstr.LanguageString("Hello", "en-USA"), RangeError);
19
+ (0, node_assert.throws)(() => new require_langstr.LanguageString("你好", "zh-yue-"), RangeError);
20
+ });
14
21
  (0, node_test.test)("Deno.inspect(LanguageString)", () => {
15
22
  const langStr = new require_langstr.LanguageString("Hello, 'world'", "en");
16
23
  (0, node_assert.deepStrictEqual)(node_util.default.inspect(langStr, { colors: false }), "<en> \"Hello, 'world'\"");
@@ -1,6 +1,6 @@
1
- import { t as LanguageString } from "./langstr-DDLc4833.mjs";
1
+ import { t as LanguageString } from "./langstr-ABdAOKlF.mjs";
2
2
  import { test } from "node:test";
3
- import { deepStrictEqual } from "node:assert";
3
+ import { deepStrictEqual, throws } from "node:assert";
4
4
  import util from "node:util";
5
5
  //#region src/langstr.test.ts
6
6
  test("new LanguageString()", () => {
@@ -9,6 +9,13 @@ test("new LanguageString()", () => {
9
9
  deepStrictEqual(langStr.locale, new Intl.Locale("en"));
10
10
  deepStrictEqual(new LanguageString("Hello", new Intl.Locale("en")), langStr);
11
11
  });
12
+ test("new LanguageString() with extlang tags", () => {
13
+ deepStrictEqual(new LanguageString("你好", "zh-YUE").locale.baseName, "yue");
14
+ deepStrictEqual(new LanguageString("你好", "zh-yue-HK").locale.baseName, "yue-HK");
15
+ deepStrictEqual(new LanguageString("你好", "zh-cmn-Hant").locale.baseName, new Intl.Locale("cmn-Hant").baseName);
16
+ throws(() => new LanguageString("Hello", "en-USA"), RangeError);
17
+ throws(() => new LanguageString("你好", "zh-yue-"), RangeError);
18
+ });
12
19
  test("Deno.inspect(LanguageString)", () => {
13
20
  const langStr = new LanguageString("Hello, 'world'", "en");
14
21
  deepStrictEqual(util.inspect(langStr, { colors: false }), "<en> \"Hello, 'world'\"");
@@ -0,0 +1,50 @@
1
+ //#region src/langtag.ts
2
+ const EXTLANG_PAIRS = new Set(Object.entries({
3
+ ar: "aao abh abv acm acq acw acx acy adf aeb aec afb ajp apc apd arb arq ars ary arz auz avl ayh ayl ayn ayp bbz pga shu ssh",
4
+ kok: "gom knn",
5
+ lv: "ltg lvs",
6
+ ms: "bjn btj bve bvu coa dup hji jak jax kvb kvr kxd lce lcf liw max meo mfa mfb min mqg msi mui orn ors pel pse tmw urk vkk vkt xmm zlm zmi zsm",
7
+ sgn: "ads aed aen afg ajs ase asf asp asq asw bfi bfk bog bqn bqy bvl bzs cds csc csd cse csf csg csl csn csq csr csx doq dse dsl dsz dyl ecs ehs esl esn eso eth fcs fse fsl fss gds gse gsg gsm gss gus hab haf hds hks hos hps hsh hsl icl iks ils inl ins ise isg isr jcs jhs jks jls jos jsl jus kgi kvk lbs lgs lls lsb lsc lsg lsl lsn lso lsp lst lsv lsw lsy lws mdl mfs mre msd msr mzc mzg mzy nbs ncs nsi nsl nsp nsr nzs okl pgz pks prl prz psc psd psg psl pso psp psr pys rib rms rnb rsi rsl rsm rsn sdl sfb sfs sgg sgx slf sls sqk sqs sqx ssp ssr svk swl syy szs tse tsm tsq tss tsy tza ugn ugy ukl uks vgt vsi vsl vsv wbs xki xml xms yds ygs yhs ysl ysm zhk zib zsl",
8
+ sw: "swc swh",
9
+ uz: "uzn uzs",
10
+ zh: "cdo cjy cmn cnp cpx csp czh czo gan hak hnm hsn luh lzh mnp nan sjc wuu yue"
11
+ }).flatMap(([prefix, extlangs]) => extlangs.split(" ").map((extlang) => `${prefix}-${extlang}`)));
12
+ const PREFIX_PATTERN = /^[A-Za-z]{2,3}$/;
13
+ const EXTLANG_PATTERN = /^[A-Za-z]{3}$/;
14
+ /**
15
+ * Normalizes a BCP 47 language tag that has an extended language subtag
16
+ * (extlang), such as `zh-yue`, into its canonical form, such as `yue`, as
17
+ * described in [RFC 5646, Section 4.5][1]. `Intl.Locale` rejects extlang
18
+ * tags, so tags should be normalized before being passed to it.
19
+ *
20
+ * Only registered extlangs that follow their registered prefix are removed;
21
+ * any other tag is returned unchanged, so the result still needs to be
22
+ * validated, e.g., by `Intl.Locale`.
23
+ *
24
+ * NOTE: This function is marked as internal in the 2.0.x–2.4.x patch
25
+ * releases. When merging it into the *main* branch, remove the `@internal`
26
+ * tag, formalize it as a public API (`@since 2.5.0`), and add it to
27
+ * the changelog fragment.
28
+ *
29
+ * [1]: https://www.rfc-editor.org/rfc/rfc5646.html#section-4.5
30
+ *
31
+ * @param tag The BCP 47 language tag to normalize.
32
+ * @returns The normalized language tag.
33
+ * @internal
34
+ */
35
+ function normalizeLanguageTag(tag) {
36
+ const subtags = tag.split("-");
37
+ if (subtags.length < 2) return tag;
38
+ const [prefix, extlang, ...rest] = subtags;
39
+ if (!PREFIX_PATTERN.test(prefix) || !EXTLANG_PATTERN.test(extlang)) return tag;
40
+ const language = extlang.toLowerCase();
41
+ if (!EXTLANG_PAIRS.has(`${prefix.toLowerCase()}-${language}`)) return tag;
42
+ return [language, ...rest].join("-");
43
+ }
44
+ //#endregion
45
+ Object.defineProperty(exports, "normalizeLanguageTag", {
46
+ enumerable: true,
47
+ get: function() {
48
+ return normalizeLanguageTag;
49
+ }
50
+ });
@@ -0,0 +1,45 @@
1
+ //#region src/langtag.ts
2
+ const EXTLANG_PAIRS = new Set(Object.entries({
3
+ ar: "aao abh abv acm acq acw acx acy adf aeb aec afb ajp apc apd arb arq ars ary arz auz avl ayh ayl ayn ayp bbz pga shu ssh",
4
+ kok: "gom knn",
5
+ lv: "ltg lvs",
6
+ ms: "bjn btj bve bvu coa dup hji jak jax kvb kvr kxd lce lcf liw max meo mfa mfb min mqg msi mui orn ors pel pse tmw urk vkk vkt xmm zlm zmi zsm",
7
+ sgn: "ads aed aen afg ajs ase asf asp asq asw bfi bfk bog bqn bqy bvl bzs cds csc csd cse csf csg csl csn csq csr csx doq dse dsl dsz dyl ecs ehs esl esn eso eth fcs fse fsl fss gds gse gsg gsm gss gus hab haf hds hks hos hps hsh hsl icl iks ils inl ins ise isg isr jcs jhs jks jls jos jsl jus kgi kvk lbs lgs lls lsb lsc lsg lsl lsn lso lsp lst lsv lsw lsy lws mdl mfs mre msd msr mzc mzg mzy nbs ncs nsi nsl nsp nsr nzs okl pgz pks prl prz psc psd psg psl pso psp psr pys rib rms rnb rsi rsl rsm rsn sdl sfb sfs sgg sgx slf sls sqk sqs sqx ssp ssr svk swl syy szs tse tsm tsq tss tsy tza ugn ugy ukl uks vgt vsi vsl vsv wbs xki xml xms yds ygs yhs ysl ysm zhk zib zsl",
8
+ sw: "swc swh",
9
+ uz: "uzn uzs",
10
+ zh: "cdo cjy cmn cnp cpx csp czh czo gan hak hnm hsn luh lzh mnp nan sjc wuu yue"
11
+ }).flatMap(([prefix, extlangs]) => extlangs.split(" ").map((extlang) => `${prefix}-${extlang}`)));
12
+ const PREFIX_PATTERN = /^[A-Za-z]{2,3}$/;
13
+ const EXTLANG_PATTERN = /^[A-Za-z]{3}$/;
14
+ /**
15
+ * Normalizes a BCP 47 language tag that has an extended language subtag
16
+ * (extlang), such as `zh-yue`, into its canonical form, such as `yue`, as
17
+ * described in [RFC 5646, Section 4.5][1]. `Intl.Locale` rejects extlang
18
+ * tags, so tags should be normalized before being passed to it.
19
+ *
20
+ * Only registered extlangs that follow their registered prefix are removed;
21
+ * any other tag is returned unchanged, so the result still needs to be
22
+ * validated, e.g., by `Intl.Locale`.
23
+ *
24
+ * NOTE: This function is marked as internal in the 2.0.x–2.4.x patch
25
+ * releases. When merging it into the *main* branch, remove the `@internal`
26
+ * tag, formalize it as a public API (`@since 2.5.0`), and add it to
27
+ * the changelog fragment.
28
+ *
29
+ * [1]: https://www.rfc-editor.org/rfc/rfc5646.html#section-4.5
30
+ *
31
+ * @param tag The BCP 47 language tag to normalize.
32
+ * @returns The normalized language tag.
33
+ * @internal
34
+ */
35
+ function normalizeLanguageTag(tag) {
36
+ const subtags = tag.split("-");
37
+ if (subtags.length < 2) return tag;
38
+ const [prefix, extlang, ...rest] = subtags;
39
+ if (!PREFIX_PATTERN.test(prefix) || !EXTLANG_PATTERN.test(extlang)) return tag;
40
+ const language = extlang.toLowerCase();
41
+ if (!EXTLANG_PAIRS.has(`${prefix.toLowerCase()}-${language}`)) return tag;
42
+ return [language, ...rest].join("-");
43
+ }
44
+ //#endregion
45
+ export { normalizeLanguageTag as t };
@@ -0,0 +1,46 @@
1
+ require("./chunk-C2EiDwsr.cjs");
2
+ const require_langtag = require("./langtag-DgySESmk.cjs");
3
+ let node_test = require("node:test");
4
+ let node_assert = require("node:assert");
5
+ //#region src/langtag.test.ts
6
+ (0, node_test.test)("normalizeLanguageTag() removes extlang subtags", () => {
7
+ for (const [input, expected] of [
8
+ ["zh-yue", "yue"],
9
+ ["zh-YUE", "yue"],
10
+ ["ZH-Yue-hk", "yue-hk"],
11
+ ["zh-yue-HK", "yue-HK"],
12
+ ["zh-cmn-Hant", "cmn-Hant"],
13
+ ["zh-yue-u-nu-hanidec", "yue-u-nu-hanidec"],
14
+ ["sgn-ase", "ase"],
15
+ ["ar-arz", "arz"],
16
+ ["ms-zlm", "zlm"],
17
+ ["kok-gom", "gom"],
18
+ ["koK-gom", "gom"],
19
+ ["lv-ltg", "ltg"],
20
+ ["sw-swc", "swc"],
21
+ ["uz-uzs", "uzs"],
22
+ ["zh-yue-cmn", "yue-cmn"],
23
+ ["zh-yue-", "yue-"]
24
+ ]) (0, node_assert.deepStrictEqual)(require_langtag.normalizeLanguageTag(input), expected, input);
25
+ });
26
+ (0, node_test.test)("normalizeLanguageTag() leaves other tags unchanged", () => {
27
+ for (const tag of [
28
+ "",
29
+ "en",
30
+ "en-US",
31
+ "en-USA",
32
+ "en-yue",
33
+ "zh",
34
+ "zh-Hant-TW",
35
+ "yue",
36
+ "yue-HK",
37
+ "x-private",
38
+ "i-klingon",
39
+ "zh-min-nan",
40
+ "koK-gom",
41
+ "zh-yuK",
42
+ "constructor-yue",
43
+ "toString-yue"
44
+ ]) (0, node_assert.deepStrictEqual)(require_langtag.normalizeLanguageTag(tag), tag, tag);
45
+ });
46
+ //#endregion
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };