@fedify/vocab-runtime 2.4.0-dev.2155 → 2.4.0-dev.2159

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 (57) hide show
  1. package/deno.json +1 -1
  2. package/dist/{docloader-oRpq1nQ8.d.cts → docloader-ClQraSWr.d.cts} +3 -0
  3. package/dist/{docloader-oRpq1nQ8.d.ts → docloader-ClQraSWr.d.ts} +3 -0
  4. package/dist/internal/jsonld-cache.cjs +1 -1
  5. package/dist/internal/jsonld-cache.d.cts +1 -1
  6. package/dist/internal/jsonld-cache.d.ts +1 -1
  7. package/dist/internal/jsonld-cache.js +1 -1
  8. package/dist/internal/portable-dereference.cjs +2 -2
  9. package/dist/internal/portable-dereference.d.cts +2 -2
  10. package/dist/internal/portable-dereference.d.ts +2 -2
  11. package/dist/internal/portable-dereference.js +2 -2
  12. package/dist/internal/signed-representation.cjs +1 -1
  13. package/dist/internal/signed-representation.js +1 -1
  14. package/dist/mod.cjs +18 -6
  15. package/dist/mod.d.cts +9 -2
  16. package/dist/mod.d.ts +9 -2
  17. package/dist/mod.js +18 -6
  18. package/dist/{portable-rwhiyMvY.d.ts → portable-DRtvOxTb.d.ts} +1 -1
  19. package/dist/{portable-Drfl02vZ.d.cts → portable-ilkFPcXa.d.cts} +1 -1
  20. package/dist/tests/{body-BJRBGr6n.mjs → body-BtHFjNgj.mjs} +1 -1
  21. package/dist/tests/{body-CLlfvGkr.cjs → body-qXqIp808.cjs} +1 -1
  22. package/dist/tests/body.test.cjs +1 -1
  23. package/dist/tests/body.test.mjs +1 -1
  24. package/dist/tests/decimal.test.cjs +2 -2
  25. package/dist/tests/decimal.test.mjs +2 -2
  26. package/dist/tests/{docloader-DsoAI6Tk.mjs → docloader-D6THKCoD.mjs} +19 -7
  27. package/dist/tests/{docloader-s_sA2gs3.cjs → docloader-DeoYwxVw.cjs} +19 -7
  28. package/dist/tests/docloader.test.cjs +241 -4
  29. package/dist/tests/docloader.test.mjs +240 -4
  30. package/dist/tests/internal/portable-dereference.test.cjs +3 -3
  31. package/dist/tests/internal/portable-dereference.test.mjs +3 -3
  32. package/dist/tests/{jsonld-cache-DnZS0z1G.mjs → jsonld-cache-B7PKo3Lu.mjs} +1 -1
  33. package/dist/tests/{jsonld-cache-9jap4yM0.cjs → jsonld-cache-BO-QXxIc.cjs} +1 -1
  34. package/dist/tests/jsonld-cache.test.cjs +2 -2
  35. package/dist/tests/jsonld-cache.test.mjs +2 -2
  36. package/dist/tests/{request-BzZ8Wn6I.mjs → request-1y3WTvsB.mjs} +1 -1
  37. package/dist/tests/{request-Dt_khD3j.cjs → request-nfOih0SA.cjs} +1 -1
  38. package/dist/tests/request.test.cjs +1 -1
  39. package/dist/tests/request.test.mjs +1 -1
  40. package/dist/tests/signed-representation.test.cjs +1 -1
  41. package/dist/tests/signed-representation.test.mjs +1 -1
  42. package/dist/tests/{url-DgJM9WJn.mjs → url-D7qRF-dF.mjs} +14 -4
  43. package/dist/tests/{url-BqlldzT8.cjs → url-gLKqAO1H.cjs} +15 -3
  44. package/dist/tests/url.test.cjs +36 -3
  45. package/dist/tests/url.test.mjs +37 -4
  46. package/dist/{url-CEhi4hb4.js → url-BHZYe8Vu.js} +14 -4
  47. package/dist/{url-Cke4irYD.cjs → url-P6l9s9dN.cjs} +15 -3
  48. package/package.json +1 -1
  49. package/src/contexts.ts +7 -1
  50. package/src/docloader.test.ts +307 -1
  51. package/src/docloader.ts +32 -7
  52. package/src/url.test.ts +29 -3
  53. package/src/url.ts +21 -5
  54. package/dist/{contexts-2hEbs3Qk.cjs → contexts-BK1CqBR5.cjs} +22 -22
  55. package/dist/{contexts-DoZUIW7y.js → contexts-DPuJ4UYL.js} +22 -22
  56. package/dist/tests/{contexts-BBX_qhBL.mjs → contexts-CIKsin4e.mjs} +9 -9
  57. package/dist/tests/{contexts-Ch3v2Cgu.cjs → contexts-DizzBjz4.cjs} +9 -9
@@ -1,12 +1,14 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request-Dt_khD3j.cjs");
3
- const require_contexts = require("./contexts-Ch3v2Cgu.cjs");
4
- const require_url = require("./url-BqlldzT8.cjs");
5
- const require_docloader = require("./docloader-s_sA2gs3.cjs");
2
+ const require_request = require("./request-nfOih0SA.cjs");
3
+ const require_contexts = require("./contexts-DizzBjz4.cjs");
4
+ const require_url = require("./url-gLKqAO1H.cjs");
5
+ const require_docloader = require("./docloader-DeoYwxVw.cjs");
6
6
  let node_test = require("node:test");
7
7
  let node_zlib = require("node:zlib");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let node_assert = require("node:assert");
10
+ let node_dns_promises = require("node:dns/promises");
11
+ node_dns_promises = require_rolldown_runtime.__toESM(node_dns_promises, 1);
10
12
  let node_http = require("node:http");
11
13
  //#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
12
14
  var require_glob_to_regexp = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
@@ -1565,6 +1567,37 @@ var esm_default = new class FetchMock {
1565
1567
  } }
1566
1568
  });
1567
1569
  });
1570
+ esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
1571
+ await t.test("preloaded FEP-7aa9 context", async () => {
1572
+ const url = "https://w3id.org/fep/7aa9";
1573
+ (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
1574
+ contextUrl: null,
1575
+ documentUrl: url,
1576
+ document: { "@context": {
1577
+ "FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
1578
+ "FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
1579
+ "FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
1580
+ "FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
1581
+ "topic": {
1582
+ "@id": "https://w3id.org/fep/7aa9#topic",
1583
+ "@type": "@id"
1584
+ },
1585
+ "featuredObject": {
1586
+ "@id": "https://w3id.org/fep/7aa9#featuredObject",
1587
+ "@type": "@id"
1588
+ },
1589
+ "canFeature": {
1590
+ "@id": "https://w3id.org/fep/7aa9#canFeature",
1591
+ "@type": "@id"
1592
+ },
1593
+ "featureAuthorization": {
1594
+ "@id": "https://w3id.org/fep/7aa9#featureAuthorization",
1595
+ "@type": "@id"
1596
+ }
1597
+ } }
1598
+ });
1599
+ (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
1600
+ });
1568
1601
  await t.test("deny non-HTTP/HTTPS", async () => {
1569
1602
  await (0, node_assert.rejects)(() => fetchDocumentLoader("ftp://localhost"), require_url.UrlError);
1570
1603
  });
@@ -1753,4 +1786,208 @@ var esm_default = new class FetchMock {
1753
1786
  (0, node_assert.deepStrictEqual)(canceled, true);
1754
1787
  (0, node_assert.deepStrictEqual)(pulls, 257);
1755
1788
  });
1789
+ (0, node_test.test)("getDocumentLoader() logs DNS failures as such", async (t) => {
1790
+ if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") {
1791
+ t.skip("requires the net permission");
1792
+ return;
1793
+ }
1794
+ const loader = require_docloader.getDocumentLoader();
1795
+ for (const result of [
1796
+ "throws",
1797
+ "empty",
1798
+ "private"
1799
+ ]) await t.test(result, async () => {
1800
+ const originalLookup = node_dns_promises.default.lookup;
1801
+ node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
1802
+ address: "127.0.0.1",
1803
+ family: 4
1804
+ }]));
1805
+ const records = [];
1806
+ await (0, _logtape_logtape.configure)({
1807
+ sinks: { buffer: (record) => records.push(record) },
1808
+ loggers: [{
1809
+ category: "fedify",
1810
+ sinks: ["buffer"],
1811
+ lowestLevel: "debug"
1812
+ }, {
1813
+ category: ["logtape", "meta"],
1814
+ sinks: []
1815
+ }],
1816
+ reset: true
1817
+ });
1818
+ try {
1819
+ const url = "https://dns-failure.invalid/object";
1820
+ let error;
1821
+ await (0, node_assert.rejects)(() => loader(url), (e) => {
1822
+ error = e;
1823
+ return true;
1824
+ });
1825
+ (0, node_assert.ok)(error instanceof require_url.UrlError);
1826
+ (0, node_assert.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
1827
+ (0, node_assert.deepStrictEqual)(records.map((r) => [
1828
+ r.level,
1829
+ r.rawMessage,
1830
+ r.properties.url
1831
+ ]), [result === "private" ? [
1832
+ "error",
1833
+ "Disallowed private URL: {url}",
1834
+ url
1835
+ ] : [
1836
+ "debug",
1837
+ "DNS lookup failed for {url}",
1838
+ url
1839
+ ]]);
1840
+ (0, node_assert.ok)(records[0].properties.error === error);
1841
+ } finally {
1842
+ await (0, _logtape_logtape.reset)();
1843
+ node_dns_promises.default.lookup = originalLookup;
1844
+ }
1845
+ });
1846
+ });
1847
+ (0, node_test.test)("getDocumentLoader() bounds alternate document chains", async (t) => {
1848
+ const base = "https://example.com/alternate-chain/";
1849
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1850
+ for (const mode of [
1851
+ "header",
1852
+ "html",
1853
+ "mixed"
1854
+ ]) for (const hops of [20, 21]) await t.test(`${mode}: ${hops} hops`, async () => {
1855
+ esm_default.mockGlobal();
1856
+ let requests = 0;
1857
+ esm_default.get(`begin:${base}`, ({ url }) => {
1858
+ requests++;
1859
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1860
+ if (index === hops || requests > 30) return Response.json({ done: true });
1861
+ const next = `${base}${index + 1}`;
1862
+ if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
1863
+ return alternate(next, mode === "html");
1864
+ });
1865
+ try {
1866
+ if (hops === 20) (0, node_assert.deepStrictEqual)((await loader(`${base}0`)).document, { done: true });
1867
+ else await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
1868
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
1869
+ (0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
1870
+ return true;
1871
+ });
1872
+ (0, node_assert.deepStrictEqual)(requests, 21);
1873
+ } finally {
1874
+ esm_default.hardReset();
1875
+ }
1876
+ });
1877
+ for (const mode of [
1878
+ "header",
1879
+ "html",
1880
+ "alternate-redirect",
1881
+ "redirect-alternate",
1882
+ "redirect-intermediate"
1883
+ ]) await t.test(`${mode}: cycle`, async () => {
1884
+ esm_default.mockGlobal();
1885
+ let requests = 0;
1886
+ esm_default.get(`begin:${base}`, ({ url }) => {
1887
+ requests++;
1888
+ if (requests > 30) return Response.json({ stopped: true });
1889
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1890
+ const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
1891
+ if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
1892
+ return alternate(next, mode === "html");
1893
+ });
1894
+ try {
1895
+ await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
1896
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
1897
+ (0, node_assert.ok)(error.message.includes("Redirect loop detected:"));
1898
+ return true;
1899
+ });
1900
+ (0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
1901
+ } finally {
1902
+ esm_default.hardReset();
1903
+ }
1904
+ });
1905
+ await t.test("alternate followed by 20 redirects shares the limit", async () => {
1906
+ esm_default.mockGlobal();
1907
+ let requests = 0;
1908
+ esm_default.get(`begin:${base}`, ({ url }) => {
1909
+ requests++;
1910
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1911
+ if (index === 21) return Response.json({ done: true });
1912
+ return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
1913
+ });
1914
+ try {
1915
+ await (0, node_assert.rejects)(loader(`${base}0`), (error) => {
1916
+ (0, node_assert.ok)(error instanceof require_request.FetchError);
1917
+ (0, node_assert.ok)(error.message.includes("Too many redirections (21)"));
1918
+ return true;
1919
+ });
1920
+ (0, node_assert.deepStrictEqual)(requests, 21);
1921
+ } finally {
1922
+ esm_default.hardReset();
1923
+ }
1924
+ });
1925
+ await t.test("relative alternate after redirect and isolated calls", async () => {
1926
+ esm_default.mockGlobal();
1927
+ esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
1928
+ esm_default.get(`${base}html`, alternate("./document", true));
1929
+ esm_default.get(`${base}document`, Response.json({ done: true }));
1930
+ try {
1931
+ for (let i = 0; i < 2; i++) {
1932
+ const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
1933
+ for (const result of results) {
1934
+ (0, node_assert.deepStrictEqual)(result.document, { done: true });
1935
+ (0, node_assert.deepStrictEqual)(result.documentUrl, `${base}document`);
1936
+ }
1937
+ }
1938
+ } finally {
1939
+ esm_default.hardReset();
1940
+ }
1941
+ });
1942
+ function alternate(next, html) {
1943
+ return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
1944
+ "Content-Type": "text/plain",
1945
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
1946
+ } });
1947
+ }
1948
+ });
1949
+ (0, node_test.test)("getDocumentLoader() preserves cancellation across alternates", async (t) => {
1950
+ const base = "https://example.com/alternate-abort/";
1951
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1952
+ for (const html of [false, true]) await t.test(html ? "HTML" : "Link header", async () => {
1953
+ esm_default.mockGlobal();
1954
+ const controller = new AbortController();
1955
+ let requests = 0;
1956
+ esm_default.get(`begin:${base}`, ({ url }) => {
1957
+ requests++;
1958
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1959
+ if (index === 2) return Response.json({ done: true });
1960
+ if (index === 1) controller.abort();
1961
+ const next = `${base}${index + 1}`;
1962
+ return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
1963
+ "Content-Type": "text/plain",
1964
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
1965
+ } });
1966
+ });
1967
+ try {
1968
+ await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
1969
+ (0, node_assert.deepStrictEqual)(requests, 2);
1970
+ } finally {
1971
+ esm_default.hardReset();
1972
+ }
1973
+ });
1974
+ });
1975
+ (0, node_test.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
1976
+ esm_default.mockGlobal();
1977
+ let requests = 0;
1978
+ const url = "https://example.com/pre-aborted-alternate";
1979
+ esm_default.get(url, () => {
1980
+ requests++;
1981
+ return Response.json({ done: true });
1982
+ });
1983
+ try {
1984
+ const loader = require_docloader.getDocumentLoader({ allowPrivateAddress: true });
1985
+ const controller = new AbortController();
1986
+ controller.abort();
1987
+ await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
1988
+ (0, node_assert.deepStrictEqual)(requests, 0);
1989
+ } finally {
1990
+ esm_default.hardReset();
1991
+ }
1992
+ });
1756
1993
  //#endregion
@@ -1,11 +1,12 @@
1
- import { t as FetchError } from "./request-BzZ8Wn6I.mjs";
2
- import { n as cid_v1_default, t as preloadedContexts } from "./contexts-BBX_qhBL.mjs";
3
- import { t as UrlError } from "./url-DgJM9WJn.mjs";
4
- import { n as getRemoteDocument, t as getDocumentLoader } from "./docloader-DsoAI6Tk.mjs";
1
+ import { t as FetchError } from "./request-1y3WTvsB.mjs";
2
+ import { n as cid_v1_default, t as preloadedContexts } from "./contexts-CIKsin4e.mjs";
3
+ import { t as UrlError } from "./url-D7qRF-dF.mjs";
4
+ import { n as getRemoteDocument, t as getDocumentLoader } from "./docloader-D6THKCoD.mjs";
5
5
  import { test } from "node:test";
6
6
  import { gzipSync } from "node:zlib";
7
7
  import { configure, reset } from "@logtape/logtape";
8
8
  import { deepStrictEqual, ok, rejects } from "node:assert";
9
+ import dns from "node:dns/promises";
9
10
  import { createServer } from "node:http";
10
11
  //#region \0rolldown/runtime.js
11
12
  var __create = Object.create;
@@ -1584,6 +1585,37 @@ test("getDocumentLoader()", async (t) => {
1584
1585
  } }
1585
1586
  });
1586
1587
  });
1588
+ esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
1589
+ await t.test("preloaded FEP-7aa9 context", async () => {
1590
+ const url = "https://w3id.org/fep/7aa9";
1591
+ deepStrictEqual(await fetchDocumentLoader(url), {
1592
+ contextUrl: null,
1593
+ documentUrl: url,
1594
+ document: { "@context": {
1595
+ "FeaturedCollection": "https://w3id.org/fep/7aa9#FeaturedCollection",
1596
+ "FeaturedItem": "https://w3id.org/fep/7aa9#FeaturedItem",
1597
+ "FeatureRequest": "https://w3id.org/fep/7aa9#FeatureRequest",
1598
+ "FeatureAuthorization": "https://w3id.org/fep/7aa9#FeatureAuthorization",
1599
+ "topic": {
1600
+ "@id": "https://w3id.org/fep/7aa9#topic",
1601
+ "@type": "@id"
1602
+ },
1603
+ "featuredObject": {
1604
+ "@id": "https://w3id.org/fep/7aa9#featuredObject",
1605
+ "@type": "@id"
1606
+ },
1607
+ "canFeature": {
1608
+ "@id": "https://w3id.org/fep/7aa9#canFeature",
1609
+ "@type": "@id"
1610
+ },
1611
+ "featureAuthorization": {
1612
+ "@id": "https://w3id.org/fep/7aa9#featureAuthorization",
1613
+ "@type": "@id"
1614
+ }
1615
+ } }
1616
+ });
1617
+ deepStrictEqual(esm_default.callHistory.calls(url).length, 0);
1618
+ });
1587
1619
  await t.test("deny non-HTTP/HTTPS", async () => {
1588
1620
  await rejects(() => fetchDocumentLoader("ftp://localhost"), UrlError);
1589
1621
  });
@@ -1772,5 +1804,209 @@ test("getRemoteDocument() bounds JSON by default", async () => {
1772
1804
  deepStrictEqual(canceled, true);
1773
1805
  deepStrictEqual(pulls, 257);
1774
1806
  });
1807
+ test("getDocumentLoader() logs DNS failures as such", async (t) => {
1808
+ if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") {
1809
+ t.skip("requires the net permission");
1810
+ return;
1811
+ }
1812
+ const loader = getDocumentLoader();
1813
+ for (const result of [
1814
+ "throws",
1815
+ "empty",
1816
+ "private"
1817
+ ]) await t.test(result, async () => {
1818
+ const originalLookup = dns.lookup;
1819
+ dns.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
1820
+ address: "127.0.0.1",
1821
+ family: 4
1822
+ }]));
1823
+ const records = [];
1824
+ await configure({
1825
+ sinks: { buffer: (record) => records.push(record) },
1826
+ loggers: [{
1827
+ category: "fedify",
1828
+ sinks: ["buffer"],
1829
+ lowestLevel: "debug"
1830
+ }, {
1831
+ category: ["logtape", "meta"],
1832
+ sinks: []
1833
+ }],
1834
+ reset: true
1835
+ });
1836
+ try {
1837
+ const url = "https://dns-failure.invalid/object";
1838
+ let error;
1839
+ await rejects(() => loader(url), (e) => {
1840
+ error = e;
1841
+ return true;
1842
+ });
1843
+ ok(error instanceof UrlError);
1844
+ deepStrictEqual(error.reason, result === "private" ? "disallowed" : "dns");
1845
+ deepStrictEqual(records.map((r) => [
1846
+ r.level,
1847
+ r.rawMessage,
1848
+ r.properties.url
1849
+ ]), [result === "private" ? [
1850
+ "error",
1851
+ "Disallowed private URL: {url}",
1852
+ url
1853
+ ] : [
1854
+ "debug",
1855
+ "DNS lookup failed for {url}",
1856
+ url
1857
+ ]]);
1858
+ ok(records[0].properties.error === error);
1859
+ } finally {
1860
+ await reset();
1861
+ dns.lookup = originalLookup;
1862
+ }
1863
+ });
1864
+ });
1865
+ test("getDocumentLoader() bounds alternate document chains", async (t) => {
1866
+ const base = "https://example.com/alternate-chain/";
1867
+ const loader = getDocumentLoader({ allowPrivateAddress: true });
1868
+ for (const mode of [
1869
+ "header",
1870
+ "html",
1871
+ "mixed"
1872
+ ]) for (const hops of [20, 21]) await t.test(`${mode}: ${hops} hops`, async () => {
1873
+ esm_default.mockGlobal();
1874
+ let requests = 0;
1875
+ esm_default.get(`begin:${base}`, ({ url }) => {
1876
+ requests++;
1877
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1878
+ if (index === hops || requests > 30) return Response.json({ done: true });
1879
+ const next = `${base}${index + 1}`;
1880
+ if (mode === "mixed" && index % 2 === 1) return Response.redirect(next, 302);
1881
+ return alternate(next, mode === "html");
1882
+ });
1883
+ try {
1884
+ if (hops === 20) deepStrictEqual((await loader(`${base}0`)).document, { done: true });
1885
+ else await rejects(loader(`${base}0`), (error) => {
1886
+ ok(error instanceof FetchError);
1887
+ ok(error.message.includes("Too many redirections (21)"));
1888
+ return true;
1889
+ });
1890
+ deepStrictEqual(requests, 21);
1891
+ } finally {
1892
+ esm_default.hardReset();
1893
+ }
1894
+ });
1895
+ for (const mode of [
1896
+ "header",
1897
+ "html",
1898
+ "alternate-redirect",
1899
+ "redirect-alternate",
1900
+ "redirect-intermediate"
1901
+ ]) await t.test(`${mode}: cycle`, async () => {
1902
+ esm_default.mockGlobal();
1903
+ let requests = 0;
1904
+ esm_default.get(`begin:${base}`, ({ url }) => {
1905
+ requests++;
1906
+ if (requests > 30) return Response.json({ stopped: true });
1907
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1908
+ const next = `${base}${mode === "redirect-intermediate" ? index === 0 ? 1 : index === 1 ? 2 : 1 : 1 - index}`;
1909
+ if (mode === "alternate-redirect" && index === 1 || (mode === "redirect-alternate" || mode === "redirect-intermediate") && index === 0) return Response.redirect(next, 302);
1910
+ return alternate(next, mode === "html");
1911
+ });
1912
+ try {
1913
+ await rejects(loader(`${base}0`), (error) => {
1914
+ ok(error instanceof FetchError);
1915
+ ok(error.message.includes("Redirect loop detected:"));
1916
+ return true;
1917
+ });
1918
+ deepStrictEqual(requests, mode === "redirect-intermediate" ? 3 : 2);
1919
+ } finally {
1920
+ esm_default.hardReset();
1921
+ }
1922
+ });
1923
+ await t.test("alternate followed by 20 redirects shares the limit", async () => {
1924
+ esm_default.mockGlobal();
1925
+ let requests = 0;
1926
+ esm_default.get(`begin:${base}`, ({ url }) => {
1927
+ requests++;
1928
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1929
+ if (index === 21) return Response.json({ done: true });
1930
+ return index === 0 ? alternate(`${base}1`, false) : Response.redirect(`${base}${index + 1}`, 302);
1931
+ });
1932
+ try {
1933
+ await rejects(loader(`${base}0`), (error) => {
1934
+ ok(error instanceof FetchError);
1935
+ ok(error.message.includes("Too many redirections (21)"));
1936
+ return true;
1937
+ });
1938
+ deepStrictEqual(requests, 21);
1939
+ } finally {
1940
+ esm_default.hardReset();
1941
+ }
1942
+ });
1943
+ await t.test("relative alternate after redirect and isolated calls", async () => {
1944
+ esm_default.mockGlobal();
1945
+ esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
1946
+ esm_default.get(`${base}html`, alternate("./document", true));
1947
+ esm_default.get(`${base}document`, Response.json({ done: true }));
1948
+ try {
1949
+ for (let i = 0; i < 2; i++) {
1950
+ const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
1951
+ for (const result of results) {
1952
+ deepStrictEqual(result.document, { done: true });
1953
+ deepStrictEqual(result.documentUrl, `${base}document`);
1954
+ }
1955
+ }
1956
+ } finally {
1957
+ esm_default.hardReset();
1958
+ }
1959
+ });
1960
+ function alternate(next, html) {
1961
+ return html ? new Response(`<link rel="alternate" type="application/activity+json" href="${next}">`, { headers: { "Content-Type": "text/html" } }) : new Response("not JSON", { headers: {
1962
+ "Content-Type": "text/plain",
1963
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
1964
+ } });
1965
+ }
1966
+ });
1967
+ test("getDocumentLoader() preserves cancellation across alternates", async (t) => {
1968
+ const base = "https://example.com/alternate-abort/";
1969
+ const loader = getDocumentLoader({ allowPrivateAddress: true });
1970
+ for (const html of [false, true]) await t.test(html ? "HTML" : "Link header", async () => {
1971
+ esm_default.mockGlobal();
1972
+ const controller = new AbortController();
1973
+ let requests = 0;
1974
+ esm_default.get(`begin:${base}`, ({ url }) => {
1975
+ requests++;
1976
+ const index = Number(new URL(url).pathname.split("/").at(-1));
1977
+ if (index === 2) return Response.json({ done: true });
1978
+ if (index === 1) controller.abort();
1979
+ const next = `${base}${index + 1}`;
1980
+ return new Response(html ? `<link rel="alternate" type="application/activity+json" href="${next}">` : "not JSON", { headers: html ? { "Content-Type": "text/html" } : {
1981
+ "Content-Type": "text/plain",
1982
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
1983
+ } });
1984
+ });
1985
+ try {
1986
+ await rejects(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
1987
+ deepStrictEqual(requests, 2);
1988
+ } finally {
1989
+ esm_default.hardReset();
1990
+ }
1991
+ });
1992
+ });
1993
+ test("getDocumentLoader() rejects cancellation before fetching", async () => {
1994
+ esm_default.mockGlobal();
1995
+ let requests = 0;
1996
+ const url = "https://example.com/pre-aborted-alternate";
1997
+ esm_default.get(url, () => {
1998
+ requests++;
1999
+ return Response.json({ done: true });
2000
+ });
2001
+ try {
2002
+ const loader = getDocumentLoader({ allowPrivateAddress: true });
2003
+ const controller = new AbortController();
2004
+ controller.abort();
2005
+ await rejects(loader(url, { signal: controller.signal }), { name: "AbortError" });
2006
+ deepStrictEqual(requests, 0);
2007
+ } finally {
2008
+ esm_default.hardReset();
2009
+ }
2010
+ });
1775
2011
  //#endregion
1776
2012
  export {};
@@ -1,6 +1,6 @@
1
- const require_contexts = require("../contexts-Ch3v2Cgu.cjs");
2
- const require_url = require("../url-BqlldzT8.cjs");
3
- const require_jsonld_cache = require("../jsonld-cache-9jap4yM0.cjs");
1
+ const require_contexts = require("../contexts-DizzBjz4.cjs");
2
+ const require_url = require("../url-gLKqAO1H.cjs");
3
+ const require_jsonld_cache = require("../jsonld-cache-BO-QXxIc.cjs");
4
4
  let node_assert_strict = require("node:assert/strict");
5
5
  let node_test = require("node:test");
6
6
  let _logtape_logtape = require("@logtape/logtape");
@@ -1,6 +1,6 @@
1
- import { t as preloadedContexts } from "../contexts-BBX_qhBL.mjs";
2
- import { a as formatIri, m as parseIri } from "../url-DgJM9WJn.mjs";
3
- import { n as createScopedContextLoader, o as registerDocumentLoaderWrapper, s as unwrapReleasedDocumentLoader } from "../jsonld-cache-DnZS0z1G.mjs";
1
+ import { t as preloadedContexts } from "../contexts-CIKsin4e.mjs";
2
+ import { a as formatIri, m as parseIri } from "../url-D7qRF-dF.mjs";
3
+ import { n as createScopedContextLoader, o as registerDocumentLoaderWrapper, s as unwrapReleasedDocumentLoader } from "../jsonld-cache-B7PKo3Lu.mjs";
4
4
  import { deepStrictEqual, ok, throws } from "node:assert/strict";
5
5
  import { test } from "node:test";
6
6
  import { getLogger } from "@logtape/logtape";
@@ -1,4 +1,4 @@
1
- import { a as formatIri, c as haveSameFe34Origin, l as haveSameIriOrigin } from "./url-DgJM9WJn.mjs";
1
+ import { a as formatIri, c as haveSameFe34Origin, l as haveSameIriOrigin } from "./url-D7qRF-dF.mjs";
2
2
  import jsonld from "jsonld/dist/jsonld.esm.js";
3
3
  //#region src/jsonld.ts
4
4
  var jsonld_default = jsonld;
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_url = require("./url-BqlldzT8.cjs");
2
+ const require_url = require("./url-gLKqAO1H.cjs");
3
3
  let jsonld_dist_jsonld_esm_js = require("jsonld/dist/jsonld.esm.js");
4
4
  jsonld_dist_jsonld_esm_js = require_rolldown_runtime.__toESM(jsonld_dist_jsonld_esm_js, 1);
5
5
  //#region src/jsonld.ts
@@ -1,5 +1,5 @@
1
- const require_url = require("./url-BqlldzT8.cjs");
2
- const require_jsonld_cache = require("./jsonld-cache-9jap4yM0.cjs");
1
+ const require_url = require("./url-gLKqAO1H.cjs");
2
+ const require_jsonld_cache = require("./jsonld-cache-BO-QXxIc.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_assert = require("node:assert");
5
5
  //#region src/jsonld-cache.test.ts
@@ -1,5 +1,5 @@
1
- import { m as parseIri } from "./url-DgJM9WJn.mjs";
2
- import { a as normalizeJsonLdIris, c as jsonld_default, i as isTrustedIriOrigin, r as getJsonLdContext, t as compactJsonLdCache } from "./jsonld-cache-DnZS0z1G.mjs";
1
+ import { m as parseIri } from "./url-D7qRF-dF.mjs";
2
+ import { a as normalizeJsonLdIris, c as jsonld_default, i as isTrustedIriOrigin, r as getJsonLdContext, t as compactJsonLdCache } from "./jsonld-cache-B7PKo3Lu.mjs";
3
3
  import { test } from "node:test";
4
4
  import { deepStrictEqual, ok, strictEqual } from "node:assert";
5
5
  //#region src/jsonld-cache.test.ts
@@ -1,7 +1,7 @@
1
1
  import process from "node:process";
2
2
  //#region deno.json
3
3
  var name = "@fedify/vocab-runtime";
4
- var version = "2.4.0-dev.2155+2ccc5085";
4
+ var version = "2.4.0-dev.2159+bb072eda";
5
5
  //#endregion
6
6
  //#region src/request.ts
7
7
  /**
@@ -3,7 +3,7 @@ let node_process = require("node:process");
3
3
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
4
4
  //#region deno.json
5
5
  var name = "@fedify/vocab-runtime";
6
- var version = "2.4.0-dev.2155+2ccc5085";
6
+ var version = "2.4.0-dev.2159+bb072eda";
7
7
  //#endregion
8
8
  //#region src/request.ts
9
9
  /**
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request-Dt_khD3j.cjs");
2
+ const require_request = require("./request-nfOih0SA.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_process = require("node:process");
5
5
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
@@ -1,4 +1,4 @@
1
- import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-BzZ8Wn6I.mjs";
1
+ import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-1y3WTvsB.mjs";
2
2
  import { test } from "node:test";
3
3
  import process from "node:process";
4
4
  import { deepStrictEqual } from "node:assert";
@@ -1,4 +1,4 @@
1
- const require_contexts = require("./contexts-Ch3v2Cgu.cjs");
1
+ const require_contexts = require("./contexts-DizzBjz4.cjs");
2
2
  let node_test = require("node:test");
3
3
  let node_assert = require("node:assert");
4
4
  //#region src/internal/signed-representation.ts
@@ -1,4 +1,4 @@
1
- import { t as preloadedContexts } from "./contexts-BBX_qhBL.mjs";
1
+ import { t as preloadedContexts } from "./contexts-CIKsin4e.mjs";
2
2
  import { test } from "node:test";
3
3
  import { deepStrictEqual, ok, strictEqual, throws } from "node:assert";
4
4
  //#region src/internal/signed-representation.ts