@fedify/fedify 2.4.0-dev.1951 → 2.4.0-dev.1962

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 (70) hide show
  1. package/README.md +2 -0
  2. package/dist/{builder-P44yYQ5Z.mjs → builder-CWYQXC2W.mjs} +2 -2
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{context-BOnn653X.d.cts → context-COLwHogJ.d.cts} +27 -0
  7. package/dist/{context-CmjP-cgJ.d.ts → context-DiiCt4S7.d.ts} +27 -0
  8. package/dist/{deno-U6Pe1U6h.mjs → deno-CfLMX4HF.mjs} +1 -1
  9. package/dist/{docloader-DantBD_9.mjs → docloader-CDmmYxYb.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/handler.test.mjs +3 -3
  12. package/dist/federation/idempotency.test.mjs +2 -2
  13. package/dist/federation/keycache.test.mjs +30 -4
  14. package/dist/federation/metrics.test.mjs +1 -1
  15. package/dist/federation/middleware.test.mjs +195 -8
  16. package/dist/federation/mod.cjs +1 -1
  17. package/dist/federation/mod.d.cts +2 -2
  18. package/dist/federation/mod.d.ts +2 -2
  19. package/dist/federation/mod.js +1 -1
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/tasks/enqueue.test.mjs +3 -2
  22. package/dist/federation/tasks/tasks.test.mjs +4 -3
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +1 -1
  25. package/dist/{federation-BAtfz-C3.cjs → federation-CIeDMHxZ.cjs} +32 -13
  26. package/dist/{http-Cssq59HU.mjs → http-BR190IDB.mjs} +3 -3
  27. package/dist/{http-BTD1L4Sv.cjs → http-C3kkrvAE.cjs} +1 -1
  28. package/dist/{http-CnBZmc4J.js → http-DWZZicb2.js} +1 -1
  29. package/dist/{key-GeKuVU-k.mjs → key-BcA-AzPp.mjs} +2 -2
  30. package/dist/{keycache-0Jk78XmB.mjs → keycache-ZLqIfrkL.mjs} +3 -1
  31. package/dist/kv-DBRWVx3Q.mjs +104 -0
  32. package/dist/{kv-cache-CMUnx7rx.js → kv-cache-5d2HgB_-.js} +1 -1
  33. package/dist/{kv-cache-D3gKJwUK.cjs → kv-cache-DCItgZUM.cjs} +1 -1
  34. package/dist/{kv-cache-CoMnysg5.mjs → kv-cache-m962_cok.mjs} +1 -1
  35. package/dist/{ld-BLl_mzo2.mjs → ld-QpPkmUjy.mjs} +3 -3
  36. package/dist/{metrics-KDlQIBxX.mjs → metrics-Dkyzq7ml.mjs} +1 -1
  37. package/dist/{middleware-aheUmEPU.js → middleware-BNktRUnj.js} +32 -13
  38. package/dist/{middleware-D-vIOT8Y.mjs → middleware-BaZ32nCi.mjs} +39 -22
  39. package/dist/{middleware-CIlcGav-.mjs → middleware-BdKtWJK1.mjs} +1 -1
  40. package/dist/{mod-kUSY5n3f.d.cts → mod-BZldCEU0.d.cts} +1 -1
  41. package/dist/{mod-BYuZ0WXt.d.ts → mod-CSH1Ee-I.d.ts} +1 -1
  42. package/dist/mod.cjs +4 -4
  43. package/dist/mod.d.cts +2 -2
  44. package/dist/mod.d.ts +2 -2
  45. package/dist/mod.js +4 -4
  46. package/dist/nodeinfo/handler.test.mjs +1 -1
  47. package/dist/{owner-C111mU7b.mjs → owner-B795xXwU.mjs} +2 -2
  48. package/dist/{proof-B_yPn2BI.cjs → proof-CYdLcTFg.cjs} +1 -1
  49. package/dist/{proof-CLZurGKe.js → proof-DrWB_vel.js} +1 -1
  50. package/dist/{proof-B_5-ezjh.mjs → proof-Mov6-gSw.mjs} +4 -4
  51. package/dist/{send-r_mRJ1qS.mjs → send-DB1mkFKO.mjs} +3 -3
  52. package/dist/sig/http.test.mjs +2 -2
  53. package/dist/sig/key.test.mjs +1 -1
  54. package/dist/sig/ld.test.mjs +2 -2
  55. package/dist/sig/mod.cjs +2 -2
  56. package/dist/sig/mod.js +2 -2
  57. package/dist/sig/owner.test.mjs +1 -1
  58. package/dist/sig/proof.test.mjs +1 -1
  59. package/dist/{testing-D13ymFKy.mjs → tasks-dbCShlCb.mjs} +0 -1
  60. package/dist/{temporal-CIBMJUty.mjs → temporal-Yi0JAV9z.mjs} +1 -1
  61. package/dist/testing/kv.test.d.mts +2 -0
  62. package/dist/testing/kv.test.mjs +101 -0
  63. package/dist/testing/mod.d.mts +70 -1
  64. package/dist/testing/mod.mjs +4 -2
  65. package/dist/testing-hbky2gMN.mjs +8 -0
  66. package/dist/utils/docloader.test.mjs +2 -2
  67. package/dist/utils/kv-cache.test.mjs +1 -1
  68. package/dist/utils/mod.cjs +1 -1
  69. package/dist/utils/mod.js +1 -1
  70. package/package.json +6 -6
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-BTD1L4Sv.cjs");
5
- const require_proof = require("./proof-B_yPn2BI.cjs");
4
+ const require_http = require("./http-C3kkrvAE.cjs");
5
+ const require_proof = require("./proof-CYdLcTFg.cjs");
6
6
  const require_types = require("./types-RXm5YxpC.cjs");
7
- const require_kv_cache = require("./kv-cache-D3gKJwUK.cjs");
7
+ const require_kv_cache = require("./kv-cache-DCItgZUM.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -1463,12 +1463,14 @@ var KvKeyCache = class {
1463
1463
  prefix;
1464
1464
  options;
1465
1465
  unavailableKeyTtl;
1466
+ keyTtl;
1466
1467
  nullKeys;
1467
1468
  constructor(kv, prefix, options = {}) {
1468
1469
  this.kv = kv;
1469
1470
  this.prefix = prefix;
1470
1471
  this.options = options;
1471
1472
  this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal$8.Duration.from({ minutes: 10 });
1473
+ this.keyTtl = options.keyTtl ?? Temporal$8.Duration.from({ days: 30 });
1472
1474
  this.nullKeys = /* @__PURE__ */ new Map();
1473
1475
  }
1474
1476
  #getFetchErrorKey(keyId) {
@@ -1509,7 +1511,7 @@ var KvKeyCache = class {
1509
1511
  }
1510
1512
  this.nullKeys.delete(keyId.href);
1511
1513
  const serialized = await key.toJsonLd(this.options);
1512
- await this.kv.set([...this.prefix, keyId.href], serialized);
1514
+ await this.kv.set([...this.prefix, keyId.href], serialized, { ttl: this.keyTtl });
1513
1515
  }
1514
1516
  async getFetchError(keyId) {
1515
1517
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
@@ -2467,7 +2469,7 @@ async function handleInbox(request, options) {
2467
2469
  * @returns A promise that resolves to an HTTP response.
2468
2470
  */
2469
2471
  async function handleInboxInternal(request, parameters, span) {
2470
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider } = parameters;
2472
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, publicKeyTtl, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider } = parameters;
2471
2473
  const logger = (0, _logtape_logtape.getLogger)([
2472
2474
  "fedify",
2473
2475
  "federation",
@@ -2538,7 +2540,12 @@ async function handleInboxInternal(request, parameters, span) {
2538
2540
  headers: { "Content-Type": "text/plain; charset=utf-8" }
2539
2541
  });
2540
2542
  }
2541
- const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
2543
+ const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, {
2544
+ documentLoader: ctx.documentLoader,
2545
+ contextLoader: ctx.contextLoader,
2546
+ tracerProvider,
2547
+ keyTtl: publicKeyTtl
2548
+ });
2542
2549
  const jsonWithoutSig = require_proof.detachSignature(json);
2543
2550
  const hasLdSignature = require_proof.hasSignature(json);
2544
2551
  const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
@@ -4763,6 +4770,8 @@ function createFederation(options) {
4763
4770
  var FederationImpl = class extends FederationBuilderImpl {
4764
4771
  kv;
4765
4772
  kvPrefixes;
4773
+ publicKeyTtl;
4774
+ httpMessageSignaturesSpecTtl;
4766
4775
  inboxQueue;
4767
4776
  outboxQueue;
4768
4777
  fanoutQueue;
@@ -4828,6 +4837,8 @@ var FederationImpl = class extends FederationBuilderImpl {
4828
4837
  taskDeduplication: ["_fedify", "taskDeduplication"],
4829
4838
  ...options.kvPrefixes ?? {}
4830
4839
  };
4840
+ this.publicKeyTtl = Temporal$2.Duration.from(options.publicKeyTtl ?? { days: 30 });
4841
+ this.httpMessageSignaturesSpecTtl = Temporal$2.Duration.from(options.httpMessageSignaturesSpecTtl ?? { days: 90 });
4831
4842
  if (options.queue == null) {
4832
4843
  this.inboxQueue = void 0;
4833
4844
  this.outboxQueue = void 0;
@@ -4930,7 +4941,7 @@ var FederationImpl = class extends FederationBuilderImpl {
4930
4941
  this.authenticatedDocumentLoaderFactory = (identity, factoryOpts) => require_http.instrumentDocumentLoader(userAuthFactory != null ? userAuthFactory(identity, factoryOpts) : require_kv_cache.getAuthenticatedDocumentLoader(identity, {
4931
4942
  allowPrivateAddress: factoryOpts?.allowPrivateAddress ?? allowPrivateAddress,
4932
4943
  userAgent: factoryOpts?.userAgent ?? userAgent,
4933
- specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, options.firstKnock),
4944
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, options.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
4934
4945
  tracerProvider: this.tracerProvider
4935
4946
  }), {
4936
4947
  meterProvider: this._meterProvider,
@@ -5305,7 +5316,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5305
5316
  inbox,
5306
5317
  sharedInbox: message.sharedInbox,
5307
5318
  headers: new Headers(message.headers),
5308
- specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock),
5319
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
5309
5320
  meterProvider: this.meterProvider,
5310
5321
  tracerProvider: this.tracerProvider
5311
5322
  });
@@ -5926,7 +5937,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5926
5937
  inbox: new URL(inbox),
5927
5938
  sharedInbox: inboxes[inbox].sharedInbox,
5928
5939
  headers: collectionSync == null ? void 0 : new Headers({ "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) }),
5929
- specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock),
5940
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
5930
5941
  meterProvider: this.meterProvider,
5931
5942
  tracerProvider: this.tracerProvider
5932
5943
  }));
@@ -6239,6 +6250,7 @@ var FederationImpl = class extends FederationBuilderImpl {
6239
6250
  inboxContextFactory,
6240
6251
  kv: this.kv,
6241
6252
  kvPrefixes: this.kvPrefixes,
6253
+ publicKeyTtl: this.publicKeyTtl,
6242
6254
  queue: this.inboxQueue,
6243
6255
  actorDispatcher: this.actorCallbacks?.dispatcher,
6244
6256
  inboxListeners: this.inboxListeners,
@@ -6980,7 +6992,12 @@ var ContextImpl = class ContextImpl {
6980
6992
  ]);
6981
6993
  const contextLoader = options.contextLoader ?? this.contextLoader;
6982
6994
  const json = await activity.toJsonLd({ contextLoader });
6983
- const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, this);
6995
+ const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
6996
+ documentLoader: this.documentLoader,
6997
+ contextLoader: this.contextLoader,
6998
+ tracerProvider: this.tracerProvider,
6999
+ keyTtl: this.federation.publicKeyTtl
7000
+ });
6984
7001
  if (await require_proof.verifyObject(_fedify_vocab.Activity, json, {
6985
7002
  contextLoader,
6986
7003
  documentLoader: options.documentLoader ?? this.documentLoader,
@@ -7255,7 +7272,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
7255
7272
  sharedInbox: inboxes[inbox].sharedInbox,
7256
7273
  meterProvider: ctx.meterProvider,
7257
7274
  tracerProvider: ctx.tracerProvider,
7258
- specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock)
7275
+ specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock, { specTtl: ctx.federation.httpMessageSignaturesSpecTtl })
7259
7276
  }));
7260
7277
  await Promise.all(promises);
7261
7278
  return true;
@@ -7426,16 +7443,18 @@ var KvSpecDeterminer = class {
7426
7443
  kv;
7427
7444
  prefix;
7428
7445
  defaultSpec;
7429
- constructor(kv, prefix, defaultSpec = "rfc9421") {
7446
+ specTtl;
7447
+ constructor(kv, prefix, defaultSpec = "rfc9421", options = {}) {
7430
7448
  this.kv = kv;
7431
7449
  this.prefix = prefix;
7432
7450
  this.defaultSpec = defaultSpec;
7451
+ this.specTtl = options.specTtl ?? Temporal$2.Duration.from({ days: 90 });
7433
7452
  }
7434
7453
  async determineSpec(origin) {
7435
7454
  return await this.kv.get([...this.prefix, origin]) ?? this.defaultSpec;
7436
7455
  }
7437
7456
  async rememberSpec(origin, spec) {
7438
- await this.kv.set([...this.prefix, origin], spec);
7457
+ await this.kv.set([...this.prefix, origin], spec, { ttl: this.specTtl });
7439
7458
  }
7440
7459
  };
7441
7460
  function notFound(_request) {
@@ -1,10 +1,10 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-U6Pe1U6h.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-KDlQIBxX.mjs";
4
+ import { n as version, t as name } from "./deno-CfLMX4HF.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Dkyzq7ml.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-GeKuVU-k.mjs";
7
+ import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-BcA-AzPp.mjs";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { CryptographicKey } from "@fedify/vocab";
10
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -9,7 +9,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
9
9
  let byte_encodings_base64 = require("byte-encodings/base64");
10
10
  //#region deno.json
11
11
  var name = "@fedify/fedify";
12
- var version = "2.4.0-dev.1951+5c417eb8";
12
+ var version = "2.4.0-dev.1962+8aaa5348";
13
13
  //#endregion
14
14
  //#region ../../node_modules/.pnpm/temporal-polyfill@1.0.1/node_modules/temporal-polyfill/chunks/root.js
15
15
  const NativeTemporal = globalThis.Temporal;
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.4.0-dev.1951+5c417eb8";
13
+ var version = "2.4.0-dev.1962+8aaa5348";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-U6Pe1U6h.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-KDlQIBxX.mjs";
4
+ import { n as version, t as name } from "./deno-CfLMX4HF.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-Dkyzq7ml.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -8,12 +8,14 @@ var KvKeyCache = class {
8
8
  prefix;
9
9
  options;
10
10
  unavailableKeyTtl;
11
+ keyTtl;
11
12
  nullKeys;
12
13
  constructor(kv, prefix, options = {}) {
13
14
  this.kv = kv;
14
15
  this.prefix = prefix;
15
16
  this.options = options;
16
17
  this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal.Duration.from({ minutes: 10 });
18
+ this.keyTtl = options.keyTtl ?? Temporal.Duration.from({ days: 30 });
17
19
  this.nullKeys = /* @__PURE__ */ new Map();
18
20
  }
19
21
  #getFetchErrorKey(keyId) {
@@ -54,7 +56,7 @@ var KvKeyCache = class {
54
56
  }
55
57
  this.nullKeys.delete(keyId.href);
56
58
  const serialized = await key.toJsonLd(this.options);
57
- await this.kv.set([...this.prefix, keyId.href], serialized);
59
+ await this.kv.set([...this.prefix, keyId.href], serialized, { ttl: this.keyTtl });
58
60
  }
59
61
  async getFetchError(keyId) {
60
62
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
@@ -0,0 +1,104 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as MemoryKvStore } from "./kv-DCHkaJ9-.mjs";
5
+ //#region src/testing/kv.ts
6
+ /**
7
+ * A {@link KvStore} decorator whose entry expiry is driven by a virtual clock
8
+ * instead of wall-clock time.
9
+ *
10
+ * {@link MemoryKvStore} decides expiry by comparing timestamps rather than by
11
+ * scheduling timers, so a test that wants to observe a TTL elapsing would
12
+ * otherwise have to sleep for longer than the TTL and hope the runner keeps
13
+ * up. That makes such tests both slow and load-sensitive.
14
+ *
15
+ * This decorator keeps the TTLs itself and never passes them to the store it
16
+ * wraps, so nothing expires until {@link advance} moves the clock. Cache
17
+ * hits, expiry, and repopulation therefore all become deterministic, without
18
+ * patching any global and without a test-only entry point in the library
19
+ * itself. The clock starts at the Unix epoch and only ever moves forward.
20
+ */
21
+ var ManualClockKvStore = class {
22
+ #inner;
23
+ #expirations = /* @__PURE__ */ new Map();
24
+ #now = Temporal.Instant.fromEpochMilliseconds(0);
25
+ /**
26
+ * Present only when the wrapped store has its own `cas()`. Callers such as
27
+ * the task deduplication planner test `kv.cas != null` to decide whether a
28
+ * conditional write is available, so the decorator must not advertise a
29
+ * capability the wrapped store lacks.
30
+ */
31
+ cas;
32
+ constructor(inner = new MemoryKvStore()) {
33
+ this.#inner = inner;
34
+ const innerCas = inner.cas;
35
+ if (innerCas != null) this.cas = async (key, expectedValue, newValue, options) => {
36
+ await this.#evictIfExpired(key);
37
+ const swapped = await innerCas.call(inner, key, expectedValue, newValue);
38
+ if (swapped) this.#recordTtl(key, options);
39
+ return swapped;
40
+ };
41
+ }
42
+ /**
43
+ * The current reading of the virtual clock.
44
+ */
45
+ get now() {
46
+ return this.#now;
47
+ }
48
+ /**
49
+ * Moves the virtual clock forward. Any entry whose TTL has elapsed by the
50
+ * new time reads as missing from this point on, exactly as it would once a
51
+ * real `KvStore` had let it expire.
52
+ */
53
+ advance(duration) {
54
+ this.#now = this.#now.add(Temporal.Duration.from(duration).round({ largestUnit: "hour" }));
55
+ }
56
+ #encodeKey(key) {
57
+ return JSON.stringify(key);
58
+ }
59
+ /**
60
+ * Drops the entry if its TTL has elapsed. Mirrors {@link MemoryKvStore},
61
+ * which treats an entry as live up to and including its expiration instant.
62
+ */
63
+ async #evictIfExpired(key) {
64
+ const encodedKey = this.#encodeKey(key);
65
+ const expiration = this.#expirations.get(encodedKey);
66
+ if (expiration == null) return;
67
+ if (this.#now.until(expiration).sign >= 0) return;
68
+ this.#expirations.delete(encodedKey);
69
+ await this.#inner.delete(key);
70
+ }
71
+ async #evictAllExpired() {
72
+ for (const [encodedKey, expiration] of [...this.#expirations]) {
73
+ if (this.#now.until(expiration).sign >= 0) continue;
74
+ this.#expirations.delete(encodedKey);
75
+ await this.#inner.delete(JSON.parse(encodedKey));
76
+ }
77
+ }
78
+ #recordTtl(key, options) {
79
+ const encodedKey = this.#encodeKey(key);
80
+ if (options?.ttl == null) {
81
+ this.#expirations.delete(encodedKey);
82
+ return;
83
+ }
84
+ this.#expirations.set(encodedKey, this.#now.add(options.ttl.round({ largestUnit: "hour" })));
85
+ }
86
+ async get(key) {
87
+ await this.#evictIfExpired(key);
88
+ return await this.#inner.get(key);
89
+ }
90
+ async set(key, value, options) {
91
+ this.#recordTtl(key, options);
92
+ await this.#inner.set(key, value);
93
+ }
94
+ async delete(key) {
95
+ this.#expirations.delete(this.#encodeKey(key));
96
+ await this.#inner.delete(key);
97
+ }
98
+ async *list(prefix) {
99
+ await this.#evictAllExpired();
100
+ yield* this.#inner.list(prefix);
101
+ }
102
+ };
103
+ //#endregion
104
+ export { ManualClockKvStore as t };
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { C as recordDocumentCache, d as validateCryptoKey, t as doubleKnock } from "./http-CnBZmc4J.js";
3
+ import { C as recordDocumentCache, d as validateCryptoKey, t as doubleKnock } from "./http-DWZZicb2.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
6
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-BTD1L4Sv.cjs");
2
+ const require_http = require("./http-C3kkrvAE.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let es_toolkit = require("es-toolkit");
5
5
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-KDlQIBxX.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-Dkyzq7ml.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-U6Pe1U6h.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-KDlQIBxX.mjs";
6
- import { n as fetchKey, o as validateCryptoKey } from "./key-GeKuVU-k.mjs";
4
+ import { n as version, t as name } from "./deno-CfLMX4HF.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Dkyzq7ml.mjs";
6
+ import { n as fetchKey, o as validateCryptoKey } from "./key-BcA-AzPp.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-U6Pe1U6h.mjs";
4
+ import { n as version, t as name } from "./deno-CfLMX4HF.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -2,10 +2,10 @@ import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./rolldown-runtime-BC-Eo5Mj.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-DAtXl_Kc.js";
5
- import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-CnBZmc4J.js";
6
- import { S as verifyCompactJsonLd, T as wrapContextLoaderForJsonLd, _ as hasSignature, b as isInvalidUrlTypeError, c as doesActorOwnKey, d as assertSafeJsonLd, g as getNormalizationContextLoader, h as detachSignature, i as verifyObject, l as getKeyOwner, n as hasProofLike, p as compactJsonLd, r as signObject, s as normalizeOutgoingActivityJsonLd, u as InvalidContextReferenceError, v as hasSignatureLike, x as signJsonLd, y as isClearlyMalformedContextReference } from "./proof-CLZurGKe.js";
5
+ import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-DWZZicb2.js";
6
+ import { S as verifyCompactJsonLd, T as wrapContextLoaderForJsonLd, _ as hasSignature, b as isInvalidUrlTypeError, c as doesActorOwnKey, d as assertSafeJsonLd, g as getNormalizationContextLoader, h as detachSignature, i as verifyObject, l as getKeyOwner, n as hasProofLike, p as compactJsonLd, r as signObject, s as normalizeOutgoingActivityJsonLd, u as InvalidContextReferenceError, v as hasSignatureLike, x as signJsonLd, y as isClearlyMalformedContextReference } from "./proof-DrWB_vel.js";
7
7
  import { n as getNodeInfo, t as nodeInfoToJson } from "./types-Dtyf8lVj.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CMUnx7rx.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-5d2HgB_-.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
11
11
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -1460,12 +1460,14 @@ var KvKeyCache = class {
1460
1460
  prefix;
1461
1461
  options;
1462
1462
  unavailableKeyTtl;
1463
+ keyTtl;
1463
1464
  nullKeys;
1464
1465
  constructor(kv, prefix, options = {}) {
1465
1466
  this.kv = kv;
1466
1467
  this.prefix = prefix;
1467
1468
  this.options = options;
1468
1469
  this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal.Duration.from({ minutes: 10 });
1470
+ this.keyTtl = options.keyTtl ?? Temporal.Duration.from({ days: 30 });
1469
1471
  this.nullKeys = /* @__PURE__ */ new Map();
1470
1472
  }
1471
1473
  #getFetchErrorKey(keyId) {
@@ -1506,7 +1508,7 @@ var KvKeyCache = class {
1506
1508
  }
1507
1509
  this.nullKeys.delete(keyId.href);
1508
1510
  const serialized = await key.toJsonLd(this.options);
1509
- await this.kv.set([...this.prefix, keyId.href], serialized);
1511
+ await this.kv.set([...this.prefix, keyId.href], serialized, { ttl: this.keyTtl });
1510
1512
  }
1511
1513
  async getFetchError(keyId) {
1512
1514
  const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
@@ -2462,7 +2464,7 @@ async function handleInbox(request, options) {
2462
2464
  * @returns A promise that resolves to an HTTP response.
2463
2465
  */
2464
2466
  async function handleInboxInternal(request, parameters, span) {
2465
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider } = parameters;
2467
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, publicKeyTtl, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider } = parameters;
2466
2468
  const logger = getLogger([
2467
2469
  "fedify",
2468
2470
  "federation",
@@ -2533,7 +2535,12 @@ async function handleInboxInternal(request, parameters, span) {
2533
2535
  headers: { "Content-Type": "text/plain; charset=utf-8" }
2534
2536
  });
2535
2537
  }
2536
- const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
2538
+ const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, {
2539
+ documentLoader: ctx.documentLoader,
2540
+ contextLoader: ctx.contextLoader,
2541
+ tracerProvider,
2542
+ keyTtl: publicKeyTtl
2543
+ });
2537
2544
  const jsonWithoutSig = detachSignature(json);
2538
2545
  const hasLdSignature = hasSignature(json);
2539
2546
  const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
@@ -4667,6 +4674,8 @@ function createFederation(options) {
4667
4674
  var FederationImpl = class extends FederationBuilderImpl {
4668
4675
  kv;
4669
4676
  kvPrefixes;
4677
+ publicKeyTtl;
4678
+ httpMessageSignaturesSpecTtl;
4670
4679
  inboxQueue;
4671
4680
  outboxQueue;
4672
4681
  fanoutQueue;
@@ -4732,6 +4741,8 @@ var FederationImpl = class extends FederationBuilderImpl {
4732
4741
  taskDeduplication: ["_fedify", "taskDeduplication"],
4733
4742
  ...options.kvPrefixes ?? {}
4734
4743
  };
4744
+ this.publicKeyTtl = Temporal.Duration.from(options.publicKeyTtl ?? { days: 30 });
4745
+ this.httpMessageSignaturesSpecTtl = Temporal.Duration.from(options.httpMessageSignaturesSpecTtl ?? { days: 90 });
4735
4746
  if (options.queue == null) {
4736
4747
  this.inboxQueue = void 0;
4737
4748
  this.outboxQueue = void 0;
@@ -4834,7 +4845,7 @@ var FederationImpl = class extends FederationBuilderImpl {
4834
4845
  this.authenticatedDocumentLoaderFactory = (identity, factoryOpts) => instrumentDocumentLoader(userAuthFactory != null ? userAuthFactory(identity, factoryOpts) : getAuthenticatedDocumentLoader(identity, {
4835
4846
  allowPrivateAddress: factoryOpts?.allowPrivateAddress ?? allowPrivateAddress,
4836
4847
  userAgent: factoryOpts?.userAgent ?? userAgent,
4837
- specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, options.firstKnock),
4848
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, options.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
4838
4849
  tracerProvider: this.tracerProvider
4839
4850
  }), {
4840
4851
  meterProvider: this._meterProvider,
@@ -5209,7 +5220,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5209
5220
  inbox,
5210
5221
  sharedInbox: message.sharedInbox,
5211
5222
  headers: new Headers(message.headers),
5212
- specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock),
5223
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
5213
5224
  meterProvider: this.meterProvider,
5214
5225
  tracerProvider: this.tracerProvider
5215
5226
  });
@@ -5830,7 +5841,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5830
5841
  inbox: new URL(inbox),
5831
5842
  sharedInbox: inboxes[inbox].sharedInbox,
5832
5843
  headers: collectionSync == null ? void 0 : new Headers({ "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) }),
5833
- specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock),
5844
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
5834
5845
  meterProvider: this.meterProvider,
5835
5846
  tracerProvider: this.tracerProvider
5836
5847
  }));
@@ -6143,6 +6154,7 @@ var FederationImpl = class extends FederationBuilderImpl {
6143
6154
  inboxContextFactory,
6144
6155
  kv: this.kv,
6145
6156
  kvPrefixes: this.kvPrefixes,
6157
+ publicKeyTtl: this.publicKeyTtl,
6146
6158
  queue: this.inboxQueue,
6147
6159
  actorDispatcher: this.actorCallbacks?.dispatcher,
6148
6160
  inboxListeners: this.inboxListeners,
@@ -6884,7 +6896,12 @@ var ContextImpl = class ContextImpl {
6884
6896
  ]);
6885
6897
  const contextLoader = options.contextLoader ?? this.contextLoader;
6886
6898
  const json = await activity.toJsonLd({ contextLoader });
6887
- const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, this);
6899
+ const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
6900
+ documentLoader: this.documentLoader,
6901
+ contextLoader: this.contextLoader,
6902
+ tracerProvider: this.tracerProvider,
6903
+ keyTtl: this.federation.publicKeyTtl
6904
+ });
6888
6905
  if (await verifyObject(Activity, json, {
6889
6906
  contextLoader,
6890
6907
  documentLoader: options.documentLoader ?? this.documentLoader,
@@ -7159,7 +7176,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
7159
7176
  sharedInbox: inboxes[inbox].sharedInbox,
7160
7177
  meterProvider: ctx.meterProvider,
7161
7178
  tracerProvider: ctx.tracerProvider,
7162
- specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock)
7179
+ specDeterminer: new KvSpecDeterminer(ctx.federation.kv, ctx.federation.kvPrefixes.httpMessageSignaturesSpec, ctx.federation.firstKnock, { specTtl: ctx.federation.httpMessageSignaturesSpecTtl })
7163
7180
  }));
7164
7181
  await Promise.all(promises);
7165
7182
  return true;
@@ -7330,16 +7347,18 @@ var KvSpecDeterminer = class {
7330
7347
  kv;
7331
7348
  prefix;
7332
7349
  defaultSpec;
7333
- constructor(kv, prefix, defaultSpec = "rfc9421") {
7350
+ specTtl;
7351
+ constructor(kv, prefix, defaultSpec = "rfc9421", options = {}) {
7334
7352
  this.kv = kv;
7335
7353
  this.prefix = prefix;
7336
7354
  this.defaultSpec = defaultSpec;
7355
+ this.specTtl = options.specTtl ?? Temporal.Duration.from({ days: 90 });
7337
7356
  }
7338
7357
  async determineSpec(origin) {
7339
7358
  return await this.kv.get([...this.prefix, origin]) ?? this.defaultSpec;
7340
7359
  }
7341
7360
  async rememberSpec(origin, spec) {
7342
- await this.kv.set([...this.prefix, origin], spec);
7361
+ await this.kv.set([...this.prefix, origin], spec, { ttl: this.specTtl });
7343
7362
  }
7344
7363
  };
7345
7364
  function notFound(_request) {