@fedify/vocab-runtime 2.4.0-dev.1962 → 2.4.0-dev.1988

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 (44) hide show
  1. package/deno.json +1 -1
  2. package/dist/mod.cjs +204 -152
  3. package/dist/mod.js +204 -152
  4. package/dist/tests/body-Cg5W_IdS.cjs +94 -0
  5. package/dist/tests/body-DRVriaJO.mjs +77 -0
  6. package/dist/tests/body.test.cjs +89 -0
  7. package/dist/tests/body.test.d.cts +1 -0
  8. package/dist/tests/body.test.d.mts +1 -0
  9. package/dist/tests/body.test.mjs +90 -0
  10. package/dist/tests/decimal.test.cjs +2 -2
  11. package/dist/tests/decimal.test.mjs +2 -2
  12. package/dist/tests/digest.test.cjs +1 -1
  13. package/dist/tests/digest.test.mjs +1 -1
  14. package/dist/tests/{docloader-cQ5_cV6R.mjs → docloader-ZSduXDDg.mjs} +61 -82
  15. package/dist/tests/{docloader-CbeAECB4.cjs → docloader-j_N6ZEZx.cjs} +61 -82
  16. package/dist/tests/docloader.test.cjs +84 -5
  17. package/dist/tests/docloader.test.mjs +84 -5
  18. package/dist/tests/internal/multicodec.test.cjs +1 -1
  19. package/dist/tests/internal/multicodec.test.mjs +1 -1
  20. package/dist/tests/jsonld-cache.test.cjs +1 -1
  21. package/dist/tests/jsonld-cache.test.mjs +1 -1
  22. package/dist/tests/key.test.cjs +1 -1
  23. package/dist/tests/key.test.mjs +1 -1
  24. package/dist/tests/langstr.test.cjs +1 -1
  25. package/dist/tests/langstr.test.mjs +1 -1
  26. package/dist/tests/link.test.cjs +1 -1
  27. package/dist/tests/link.test.mjs +1 -1
  28. package/dist/tests/multibase/multibase.test.cjs +1 -1
  29. package/dist/tests/multibase/multibase.test.mjs +1 -1
  30. package/dist/tests/{request-me46Cb_a.cjs → request-B3YkikPO.cjs} +1 -1
  31. package/dist/tests/{request-CywSvOoa.mjs → request-Bo1iqGDW.mjs} +1 -1
  32. package/dist/tests/request.test.cjs +2 -2
  33. package/dist/tests/request.test.mjs +2 -2
  34. package/dist/tests/temporal.test.cjs +1 -1
  35. package/dist/tests/temporal.test.mjs +1 -1
  36. package/dist/tests/url.test.cjs +1 -1
  37. package/dist/tests/url.test.mjs +1 -1
  38. package/package.json +1 -1
  39. package/src/body.test.ts +125 -0
  40. package/src/body.ts +95 -0
  41. package/src/contexts/fep-22cd.json +21 -0
  42. package/src/contexts.ts +2 -0
  43. package/src/docloader.test.ts +128 -3
  44. package/src/docloader.ts +52 -93
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.1962+8aaa5348",
3
+ "version": "2.4.0-dev.1988+c5c9d617",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
package/dist/mod.cjs CHANGED
@@ -4249,6 +4249,34 @@ const preloadedContexts = {
4249
4249
  "@type": "@id"
4250
4250
  }
4251
4251
  } },
4252
+ "https://w3id.org/fep/22cd": { "@context": {
4253
+ "fep-22cd": "https://w3id.org/fep/22cd#",
4254
+ "schema": "https://schema.org/",
4255
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
4256
+ "translations": {
4257
+ "@id": "fep-22cd:translations",
4258
+ "@container": "@set"
4259
+ },
4260
+ "Translation": "fep-22cd:Translation",
4261
+ "sourceUpdated": {
4262
+ "@id": "fep-22cd:sourceUpdated",
4263
+ "@type": "xsd:dateTime"
4264
+ },
4265
+ "translator": {
4266
+ "@id": "schema:translator",
4267
+ "@type": "@id",
4268
+ "@container": "@set"
4269
+ },
4270
+ "inLanguage": "schema:inLanguage",
4271
+ "translationOfWork": {
4272
+ "@id": "schema:translationOfWork",
4273
+ "@type": "@id"
4274
+ },
4275
+ "isBasedOn": {
4276
+ "@id": "schema:isBasedOn",
4277
+ "@type": "@id"
4278
+ }
4279
+ } },
4252
4280
  "https://w3id.org/fep/5711": { "@context": {
4253
4281
  "likesOf": {
4254
4282
  "@id": "https://w3id.org/fep/5711#likesOf",
@@ -4493,7 +4521,151 @@ const preloadedContexts = {
4493
4521
  //#endregion
4494
4522
  //#region deno.json
4495
4523
  var name = "@fedify/vocab-runtime";
4496
- var version = "2.4.0-dev.1962+8aaa5348";
4524
+ var version = "2.4.0-dev.1988+c5c9d617";
4525
+ //#endregion
4526
+ //#region src/request.ts
4527
+ /**
4528
+ * Error thrown when fetching a JSON-LD document failed.
4529
+ */
4530
+ var FetchError = class extends Error {
4531
+ /**
4532
+ * The URL that failed to fetch.
4533
+ */
4534
+ url;
4535
+ /**
4536
+ * The HTTP response that failed, if available.
4537
+ */
4538
+ response;
4539
+ /**
4540
+ * Constructs a new `FetchError`.
4541
+ *
4542
+ * @param url The URL that failed to fetch.
4543
+ * @param message Error message.
4544
+ * @param response The failed HTTP response, if available.
4545
+ */
4546
+ constructor(url, message, response) {
4547
+ super(message == null ? url.toString() : `${url}: ${message}`);
4548
+ this.name = "FetchError";
4549
+ this.url = typeof url === "string" ? new URL(url) : url;
4550
+ this.response = response;
4551
+ }
4552
+ };
4553
+ /**
4554
+ * Creates a request for the given URL.
4555
+ * @param url The URL to create the request for.
4556
+ * @param options The options for the request.
4557
+ * @returns The created request.
4558
+ * @internal
4559
+ */
4560
+ function createActivityPubRequest(url, options = {}) {
4561
+ return new Request(url, {
4562
+ headers: {
4563
+ Accept: "application/activity+json, application/ld+json",
4564
+ "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
4565
+ },
4566
+ redirect: "manual"
4567
+ });
4568
+ }
4569
+ /**
4570
+ * Gets the user agent string for the given application and URL.
4571
+ * @param options The options for making the user agent string.
4572
+ * @returns The user agent string.
4573
+ * @since 1.3.0
4574
+ */
4575
+ function getUserAgent({ software, url } = {}) {
4576
+ const fedify = `Fedify/${version}`;
4577
+ const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
4578
+ const userAgent = software == null ? [fedify] : [software, fedify];
4579
+ if (runtime != null) userAgent.push(runtime);
4580
+ if (url != null) userAgent.push(`+${url.toString()}`);
4581
+ return `${userAgent.shift()} (${userAgent.join("; ")})`;
4582
+ }
4583
+ /**
4584
+ * Logs the request.
4585
+ * @param request The request to log.
4586
+ * @internal
4587
+ */
4588
+ function logRequest(logger, request) {
4589
+ logger.debug("Fetching document: {method} {url} {headers}", {
4590
+ method: request.method,
4591
+ url: request.url,
4592
+ headers: Object.fromEntries(request.headers.entries())
4593
+ });
4594
+ }
4595
+ //#endregion
4596
+ //#region src/body.ts
4597
+ /** Decoded JSON body limit: 16 MiB. @internal */
4598
+ const MAX_BODY_SIZE = 16 * 1024 * 1024;
4599
+ /** A body exceeded the byte limit. @internal */
4600
+ var BodyTooLargeError = class extends FetchError {
4601
+ /** Creates an error for a body exceeding the given limit. */
4602
+ constructor(url, maxBytes) {
4603
+ super(url, `Body exceeds the limit of ${maxBytes} bytes`);
4604
+ this.name = "BodyTooLargeError";
4605
+ }
4606
+ };
4607
+ /** Validates a finite, positive byte limit. @internal */
4608
+ function validateBodySizeLimit(maxBytes) {
4609
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
4610
+ }
4611
+ /**
4612
+ * Reads UTF-8 text while limiting bytes received from the body stream.
4613
+ * Fetch responses are already decompressed, so the stream count is authoritative.
4614
+ * @param message The response or request to read.
4615
+ * @param maxBytes The maximum number of decoded bytes to read.
4616
+ * @param url The URL to include in errors and logs.
4617
+ * @returns The decoded body text.
4618
+ * @throws {BodyTooLargeError} If the body exceeds the limit.
4619
+ * @internal
4620
+ */
4621
+ async function readBoundedText(message, maxBytes, url) {
4622
+ validateBodySizeLimit(maxBytes);
4623
+ const reader = message.body?.getReader();
4624
+ const tooLarge = () => {
4625
+ (0, _logtape_logtape.getLogger)([
4626
+ "fedify",
4627
+ "runtime",
4628
+ "body"
4629
+ ]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
4630
+ url: url.toString(),
4631
+ maxBytes
4632
+ });
4633
+ throw new BodyTooLargeError(url, maxBytes);
4634
+ };
4635
+ try {
4636
+ const length = message.headers.get("Content-Length");
4637
+ const encoding = message.headers.get("Content-Encoding");
4638
+ if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
4639
+ if (reader == null) return "";
4640
+ let size = 0;
4641
+ const textReader = new ReadableStream({ async pull(controller) {
4642
+ const { done, value } = await reader.read();
4643
+ if (done) {
4644
+ controller.close();
4645
+ return;
4646
+ }
4647
+ size += value.byteLength;
4648
+ if (size > maxBytes) tooLarge();
4649
+ controller.enqueue(value);
4650
+ } }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
4651
+ try {
4652
+ const chunks = [];
4653
+ while (true) {
4654
+ const { done, value } = await textReader.read();
4655
+ if (done) break;
4656
+ chunks.push(value);
4657
+ }
4658
+ return chunks.join("");
4659
+ } finally {
4660
+ textReader.releaseLock();
4661
+ }
4662
+ } catch (error) {
4663
+ if (reader != null) reader.cancel(error).catch(() => {});
4664
+ throw error;
4665
+ } finally {
4666
+ reader?.releaseLock();
4667
+ }
4668
+ }
4497
4669
  //#endregion
4498
4670
  //#region src/link.ts
4499
4671
  const parametersNeedLowerCase = ["rel", "type"];
@@ -4676,76 +4848,6 @@ var HttpHeaderLink = class HttpHeaderLink {
4676
4848
  }
4677
4849
  };
4678
4850
  //#endregion
4679
- //#region src/request.ts
4680
- /**
4681
- * Error thrown when fetching a JSON-LD document failed.
4682
- */
4683
- var FetchError = class extends Error {
4684
- /**
4685
- * The URL that failed to fetch.
4686
- */
4687
- url;
4688
- /**
4689
- * The HTTP response that failed, if available.
4690
- */
4691
- response;
4692
- /**
4693
- * Constructs a new `FetchError`.
4694
- *
4695
- * @param url The URL that failed to fetch.
4696
- * @param message Error message.
4697
- * @param response The failed HTTP response, if available.
4698
- */
4699
- constructor(url, message, response) {
4700
- super(message == null ? url.toString() : `${url}: ${message}`);
4701
- this.name = "FetchError";
4702
- this.url = typeof url === "string" ? new URL(url) : url;
4703
- this.response = response;
4704
- }
4705
- };
4706
- /**
4707
- * Creates a request for the given URL.
4708
- * @param url The URL to create the request for.
4709
- * @param options The options for the request.
4710
- * @returns The created request.
4711
- * @internal
4712
- */
4713
- function createActivityPubRequest(url, options = {}) {
4714
- return new Request(url, {
4715
- headers: {
4716
- Accept: "application/activity+json, application/ld+json",
4717
- "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
4718
- },
4719
- redirect: "manual"
4720
- });
4721
- }
4722
- /**
4723
- * Gets the user agent string for the given application and URL.
4724
- * @param options The options for making the user agent string.
4725
- * @returns The user agent string.
4726
- * @since 1.3.0
4727
- */
4728
- function getUserAgent({ software, url } = {}) {
4729
- const fedify = `Fedify/${version}`;
4730
- const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
4731
- const userAgent = software == null ? [fedify] : [software, fedify];
4732
- if (runtime != null) userAgent.push(runtime);
4733
- if (url != null) userAgent.push(`+${url.toString()}`);
4734
- return `${userAgent.shift()} (${userAgent.join("; ")})`;
4735
- }
4736
- /**
4737
- * Logs the request.
4738
- * @param request The request to log.
4739
- * @internal
4740
- */
4741
- function logRequest(logger, request) {
4742
- logger.debug("Fetching document: {method} {url} {headers}", {
4743
- method: request.method,
4744
- url: request.url,
4745
- headers: Object.fromEntries(request.headers.entries())
4746
- });
4747
- }
4748
- //#endregion
4749
4851
  //#region src/docloader.ts
4750
4852
  const logger = (0, _logtape_logtape.getLogger)([
4751
4853
  "fedify",
@@ -4761,58 +4863,6 @@ function createResponseMetadata(response) {
4761
4863
  statusText: response.statusText
4762
4864
  });
4763
4865
  }
4764
- async function cancelResponseBody(response) {
4765
- if (response.body != null) await response.body.cancel();
4766
- }
4767
- async function readBoundedText(response, maxBytes) {
4768
- const contentLength = response.headers.get("Content-Length");
4769
- if (contentLength != null) {
4770
- const size = Number(contentLength);
4771
- if (size > maxBytes) {
4772
- await cancelResponseBody(response);
4773
- return {
4774
- text: "",
4775
- size,
4776
- tooLarge: true
4777
- };
4778
- }
4779
- }
4780
- if (response.body == null) return {
4781
- text: "",
4782
- size: 0,
4783
- tooLarge: false
4784
- };
4785
- const reader = response.body.getReader();
4786
- const decoder = new TextDecoder();
4787
- let text = "";
4788
- let size = 0;
4789
- try {
4790
- while (true) {
4791
- const result = await reader.read();
4792
- if (result.done) break;
4793
- const chunkSize = result.value.byteLength;
4794
- if (size + chunkSize > maxBytes) {
4795
- size += chunkSize;
4796
- await reader.cancel();
4797
- return {
4798
- text: "",
4799
- size,
4800
- tooLarge: true
4801
- };
4802
- }
4803
- size += chunkSize;
4804
- text += decoder.decode(result.value, { stream: true });
4805
- }
4806
- text += decoder.decode();
4807
- return {
4808
- text,
4809
- size,
4810
- tooLarge: false
4811
- };
4812
- } finally {
4813
- reader.releaseLock();
4814
- }
4815
- }
4816
4866
  /**
4817
4867
  * Gets a {@link RemoteDocument} from the given response.
4818
4868
  * @param url The URL of the document to load.
@@ -4868,42 +4918,44 @@ async function getRemoteDocument(url, response, fetch) {
4868
4918
  let document;
4869
4919
  if (!jsonLd && (contentType === "text/html" || contentType?.startsWith("text/html;") || contentType === "application/xhtml+xml" || contentType?.startsWith("application/xhtml+xml;"))) {
4870
4920
  const errorResponse = createResponseMetadata(response);
4871
- const html = await readBoundedText(response, MAX_HTML_SIZE);
4872
- if (html.tooLarge) {
4921
+ let html;
4922
+ try {
4923
+ html = await readBoundedText(response, MAX_HTML_SIZE, documentUrl);
4924
+ } catch (error) {
4925
+ if (!(error instanceof BodyTooLargeError)) throw error;
4873
4926
  logger.warn("HTML response too large, skipping alternate link discovery: {url}", {
4874
4927
  url: documentUrl,
4875
- size: html.size
4928
+ maxBytes: MAX_HTML_SIZE
4876
4929
  });
4877
4930
  throw new FetchError(documentUrl, `HTML document is too large to scan for an ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
4878
- } else {
4879
- const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
4880
- const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
4881
- let tagMatch;
4882
- while ((tagMatch = tagPattern.exec(html.text)) !== null) {
4883
- const tagContent = tagMatch[2];
4884
- let attrMatch;
4885
- const attribs = {};
4886
- attrPattern.lastIndex = 0;
4887
- while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
4888
- const key = attrMatch[1].toLowerCase();
4889
- attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
4890
- }
4891
- if (attribs.rel === "alternate" && "type" in attribs && (attribs.type === "application/activity+json" || attribs.type === "application/ld+json" || attribs.type.startsWith("application/ld+json;")) && "href" in attribs && new URL(attribs.href, docUrl).href !== docUrl.href) {
4892
- logger.debug("Found alternate document: {alternateUrl} from {url}", {
4893
- alternateUrl: attribs.href,
4894
- url: documentUrl
4895
- });
4896
- return await fetch(new URL(attribs.href, docUrl).href);
4897
- }
4931
+ }
4932
+ const tagPattern = /<(a|link)\s+([^>]*?)\s*\/?>/gi;
4933
+ const attrPattern = /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
4934
+ let tagMatch;
4935
+ while ((tagMatch = tagPattern.exec(html)) !== null) {
4936
+ const tagContent = tagMatch[2];
4937
+ let attrMatch;
4938
+ const attribs = {};
4939
+ attrPattern.lastIndex = 0;
4940
+ while ((attrMatch = attrPattern.exec(tagContent)) !== null) {
4941
+ const key = attrMatch[1].toLowerCase();
4942
+ attribs[key] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
4898
4943
  }
4899
- try {
4900
- document = JSON.parse(html.text);
4901
- } catch (error) {
4902
- if (!(error instanceof SyntaxError)) throw error;
4903
- throw new FetchError(documentUrl, `HTML document has no ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
4944
+ if (attribs.rel === "alternate" && "type" in attribs && (attribs.type === "application/activity+json" || attribs.type === "application/ld+json" || attribs.type.startsWith("application/ld+json;")) && "href" in attribs && new URL(attribs.href, docUrl).href !== docUrl.href) {
4945
+ logger.debug("Found alternate document: {alternateUrl} from {url}", {
4946
+ alternateUrl: attribs.href,
4947
+ url: documentUrl
4948
+ });
4949
+ return await fetch(new URL(attribs.href, docUrl).href);
4904
4950
  }
4905
4951
  }
4906
- } else document = await response.json();
4952
+ try {
4953
+ document = JSON.parse(html);
4954
+ } catch (error) {
4955
+ if (!(error instanceof SyntaxError)) throw error;
4956
+ throw new FetchError(documentUrl, `HTML document has no ActivityPub alternate link (Content-Type: ${contentType})`, errorResponse);
4957
+ }
4958
+ } else document = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, documentUrl));
4907
4959
  logger.debug("Fetched document: {status} {url} {headers}", {
4908
4960
  status: response.status,
4909
4961
  url: documentUrl,