@fedify/relay 2.4.0-pr.934.40 → 2.4.0

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.
package/dist/mod.cjs CHANGED
@@ -18,6 +18,28 @@ function isRelayFollowerData(value) {
18
18
  const obj = value;
19
19
  return "actor" in obj && "state" in obj && typeof obj.state === "string" && (obj.state === "pending" || obj.state === "accepted");
20
20
  }
21
+ /**
22
+ * Parses and semantically validates follower data from storage.
23
+ *
24
+ * @param actorId The actor ID used as the follower's storage key.
25
+ * @param value The stored follower data.
26
+ * @returns The parsed follower, or `null` if the row is invalid.
27
+ * @internal
28
+ */
29
+ async function parseRelayFollowerData(actorId, value) {
30
+ if (!isRelayFollowerData(value)) return null;
31
+ try {
32
+ const actor = await _fedify_vocab.Object.fromJsonLd(value.actor);
33
+ if (!(0, _fedify_vocab.isActor)(actor) || actor.id?.href !== actorId) return null;
34
+ return {
35
+ actorId,
36
+ actor,
37
+ state: value.state
38
+ };
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
21
43
  //#endregion
22
44
  //#region src/builder.ts
23
45
  const relayBuilder = (0, _fedify_fedify.createFederationBuilder)();
@@ -78,12 +100,12 @@ relayBuilder.setActorDispatcher("/users/{identifier}", async (ctx, identifier) =
78
100
  });
79
101
  async function getFollowerActors(ctx) {
80
102
  const actors = [];
81
- for await (const { value } of ctx.data.kv.list(["follower"])) {
82
- if (!isRelayFollowerData(value)) continue;
83
- if (value.state !== "accepted") continue;
84
- const actor = await _fedify_vocab.Object.fromJsonLd(value.actor);
85
- if (!(0, _fedify_vocab.isActor)(actor)) continue;
86
- actors.push(actor);
103
+ for await (const { key, value } of ctx.data.kv.list(["follower"])) {
104
+ const actorId = key[1];
105
+ if (typeof actorId !== "string") continue;
106
+ const follower = await parseRelayFollowerData(actorId, value);
107
+ if (follower?.state !== "accepted") continue;
108
+ actors.push(follower.actor);
87
109
  }
88
110
  return actors;
89
111
  }
@@ -245,7 +267,7 @@ function parseInt0(s) {
245
267
  function padNumber(digits, num) {
246
268
  return String(num).padStart(digits, "0");
247
269
  }
248
- const padNumber2 = /* @__PURE__ */ bindArgs(padNumber, 2);
270
+ const padNumber2 = /*@__PURE__*/ bindArgs(padNumber, 2);
249
271
  function compareNumbers(a, b) {
250
272
  return Math.sign(a - b);
251
273
  }
@@ -313,7 +335,7 @@ const unitNameMap = {
313
335
  month: 8,
314
336
  year: 9
315
337
  };
316
- const unitNamesAsc = /* @__PURE__ */ Object.keys(unitNameMap);
338
+ const unitNamesAsc = /*@__PURE__*/ Object.keys(unitNameMap);
317
339
  const nanoInMicro = nanoInMicro$1;
318
340
  const nanoInMilli = nanoInMilli$1;
319
341
  const nanoInSec = nanoInSec$1;
@@ -329,19 +351,19 @@ const unitNanoMap = [
329
351
  nanoInHour,
330
352
  nanoInUtcDay
331
353
  ];
332
- const bigNanoInMicro = /* @__PURE__ */ BigInt(nanoInMicro);
333
- const bigNanoInMilli = /* @__PURE__ */ BigInt(nanoInMilli);
334
- const bigNanoInSec = /* @__PURE__ */ BigInt(nanoInSec);
335
- const bigNanoInMinute = /* @__PURE__ */ BigInt(nanoInMinute);
336
- const bigNanoInHour = /* @__PURE__ */ BigInt(nanoInHour);
337
- const bigNanoInUtcDay = /* @__PURE__ */ BigInt(nanoInUtcDay);
354
+ const bigNanoInMicro = /*@__PURE__*/ BigInt(nanoInMicro);
355
+ const bigNanoInMilli = /*@__PURE__*/ BigInt(nanoInMilli);
356
+ const bigNanoInSec = /*@__PURE__*/ BigInt(nanoInSec);
357
+ const bigNanoInMinute = /*@__PURE__*/ BigInt(nanoInMinute);
358
+ const bigNanoInHour = /*@__PURE__*/ BigInt(nanoInHour);
359
+ const bigNanoInUtcDay = /*@__PURE__*/ BigInt(nanoInUtcDay);
338
360
  function divideBigNanoToExactNumber(bigNano, divisorNano) {
339
361
  const days = Number(bigNano / bigNanoInUtcDay);
340
362
  const timeNano = Number(bigNano % bigNanoInUtcDay);
341
363
  return days * (nanoInUtcDay / divisorNano) + (Math.trunc(timeNano / divisorNano) + timeNano % divisorNano / divisorNano);
342
364
  }
343
- const timeFieldNamesAsc = /* @__PURE__ */ unitNamesAsc.slice(0, 6);
344
- const timeGetters = /* @__PURE__ */ createPropGetters(timeFieldNamesAsc);
365
+ const timeFieldNamesAsc = /*@__PURE__*/ unitNamesAsc.slice(0, 6);
366
+ const timeGetters = /*@__PURE__*/ createPropGetters(timeFieldNamesAsc);
345
367
  const yearFieldNamesAsc = ["year"];
346
368
  const dayFieldNamesAsc = ["day"];
347
369
  const calendarDateFieldNamesAsc = [
@@ -363,24 +385,24 @@ const monthDayFieldNames = [
363
385
  "month",
364
386
  "monthCode"
365
387
  ];
366
- const timeFieldNamesAlpha = /* @__PURE__ */ sortStrings(timeFieldNamesAsc);
367
- const yearFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(eraYearFieldNames, yearFieldNamesAsc);
368
- const yearMonthFieldNamesAlpha = /* @__PURE__ */ sortStrings(monthFieldNames, yearFieldNamesAsc);
369
- const yearMonthFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(eraYearFieldNames, yearMonthFieldNamesAlpha);
370
- const yearMonthCodeFieldNamesAlpha = /* @__PURE__ */ sortStrings(["monthCode"], yearFieldNamesAsc);
371
- const yearMonthCodeFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(eraYearFieldNames, yearMonthCodeFieldNamesAlpha);
372
- const monthCodeDayFieldNamesAlpha = /* @__PURE__ */ sortStrings(dayFieldNamesAsc, ["monthCode"]);
373
- const dateFieldNamesAlpha = /* @__PURE__ */ sortStrings(dayFieldNamesAsc, yearMonthFieldNamesAlpha);
374
- const dateFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(dayFieldNamesAsc, eraYearFieldNames, yearMonthFieldNamesAlpha);
375
- const dateTimeFieldNamesAlpha = /* @__PURE__ */ sortStrings(dateFieldNamesAlpha, timeFieldNamesAsc);
376
- const dateTimeFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(dateFieldNamesWithEraAlpha, timeFieldNamesAsc);
377
- const dateTimeAndOffsetFieldNamesAlpha = /* @__PURE__ */ sortStrings(dateFieldNamesAlpha, timeFieldNamesAsc, offsetFieldNames);
378
- const dateTimeAndOffsetFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(dateFieldNamesWithEraAlpha, timeFieldNamesAsc, offsetFieldNames);
379
- const dateTimeAndZoneFieldNamesAlpha = /* @__PURE__ */ sortStrings(dateFieldNamesAlpha, timeFieldNamesAsc, offsetFieldNames, timeZoneFieldNames);
380
- const dateTimeAndZoneFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(dateFieldNamesWithEraAlpha, timeFieldNamesAsc, offsetFieldNames, timeZoneFieldNames);
381
- const yearMonthCodeDayFieldNamesAlpha = /* @__PURE__ */ sortStrings(dayFieldNamesAsc, yearMonthCodeFieldNamesAlpha);
382
- const yearMonthCodeDayFieldNamesWithEraAlpha = /* @__PURE__ */ sortStrings(dayFieldNamesAsc, eraYearFieldNames, yearMonthCodeFieldNamesAlpha);
383
- const timeFieldDefaults = /* @__PURE__ */ zipPropsConst(timeFieldNamesAsc, 0);
388
+ const timeFieldNamesAlpha = /*@__PURE__*/ sortStrings(timeFieldNamesAsc);
389
+ const yearFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(eraYearFieldNames, yearFieldNamesAsc);
390
+ const yearMonthFieldNamesAlpha = /*@__PURE__*/ sortStrings(monthFieldNames, yearFieldNamesAsc);
391
+ const yearMonthFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(eraYearFieldNames, yearMonthFieldNamesAlpha);
392
+ const yearMonthCodeFieldNamesAlpha = /*@__PURE__*/ sortStrings(["monthCode"], yearFieldNamesAsc);
393
+ const yearMonthCodeFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(eraYearFieldNames, yearMonthCodeFieldNamesAlpha);
394
+ const monthCodeDayFieldNamesAlpha = /*@__PURE__*/ sortStrings(dayFieldNamesAsc, ["monthCode"]);
395
+ const dateFieldNamesAlpha = /*@__PURE__*/ sortStrings(dayFieldNamesAsc, yearMonthFieldNamesAlpha);
396
+ const dateFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(dayFieldNamesAsc, eraYearFieldNames, yearMonthFieldNamesAlpha);
397
+ const dateTimeFieldNamesAlpha = /*@__PURE__*/ sortStrings(dateFieldNamesAlpha, timeFieldNamesAsc);
398
+ const dateTimeFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(dateFieldNamesWithEraAlpha, timeFieldNamesAsc);
399
+ const dateTimeAndOffsetFieldNamesAlpha = /*@__PURE__*/ sortStrings(dateFieldNamesAlpha, timeFieldNamesAsc, offsetFieldNames);
400
+ const dateTimeAndOffsetFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(dateFieldNamesWithEraAlpha, timeFieldNamesAsc, offsetFieldNames);
401
+ const dateTimeAndZoneFieldNamesAlpha = /*@__PURE__*/ sortStrings(dateFieldNamesAlpha, timeFieldNamesAsc, offsetFieldNames, timeZoneFieldNames);
402
+ const dateTimeAndZoneFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(dateFieldNamesWithEraAlpha, timeFieldNamesAsc, offsetFieldNames, timeZoneFieldNames);
403
+ const yearMonthCodeDayFieldNamesAlpha = /*@__PURE__*/ sortStrings(dayFieldNamesAsc, yearMonthCodeFieldNamesAlpha);
404
+ const yearMonthCodeDayFieldNamesWithEraAlpha = /*@__PURE__*/ sortStrings(dayFieldNamesAsc, eraYearFieldNames, yearMonthCodeFieldNamesAlpha);
405
+ const timeFieldDefaults = /*@__PURE__*/ zipPropsConst(timeFieldNamesAsc, 0);
384
406
  function validateTimeFields(timeFields) {
385
407
  return constrainTimeFields(timeFields, 1), timeFields;
386
408
  }
@@ -609,20 +631,20 @@ function computeCalendarWeekOfYear(calendar, isoDate) {
609
631
  function computeCalendarYearOfWeek(calendar, isoDate) {
610
632
  return calendar === void 0 ? computeIsoWeekFields(isoDate).yearOfWeek : void 0;
611
633
  }
612
- const durationFieldNamesAsc = /* @__PURE__ */ unitNamesAsc.map((unitName) => unitName + "s");
613
- const durationGetters = /* @__PURE__ */ createPropGetters(durationFieldNamesAsc);
614
- const durationFieldNamesAlpha = /* @__PURE__ */ sortStrings(durationFieldNamesAsc);
615
- const durationTimeFieldNamesAsc = /* @__PURE__ */ durationFieldNamesAsc.slice(0, 6);
616
- const durationDateFieldNamesAsc = /* @__PURE__ */ durationFieldNamesAsc.slice(6);
617
- const durationCalendarFieldNamesAsc = /* @__PURE__ */ durationDateFieldNamesAsc.slice(1);
618
- const durationFieldIndexes = /* @__PURE__ */ durationFieldNamesAsc.reduce((indexes, fieldName, i) => (indexes[fieldName] = i, indexes), {});
619
- const durationFieldDefaults = /* @__PURE__ */ zipPropsConst(durationFieldNamesAsc, 0);
620
- const durationTimeFieldDefaults = /* @__PURE__ */ zipPropsConst(durationTimeFieldNamesAsc, 0);
621
- const clearDurationFields = /* @__PURE__ */ bindArgs(zeroOutProps, durationFieldNamesAsc);
634
+ const durationFieldNamesAsc = /*@__PURE__*/ unitNamesAsc.map((unitName) => unitName + "s");
635
+ const durationGetters = /*@__PURE__*/ createPropGetters(durationFieldNamesAsc);
636
+ const durationFieldNamesAlpha = /*@__PURE__*/ sortStrings(durationFieldNamesAsc);
637
+ const durationTimeFieldNamesAsc = /*@__PURE__*/ durationFieldNamesAsc.slice(0, 6);
638
+ const durationDateFieldNamesAsc = /*@__PURE__*/ durationFieldNamesAsc.slice(6);
639
+ const durationCalendarFieldNamesAsc = /*@__PURE__*/ durationDateFieldNamesAsc.slice(1);
640
+ const durationFieldIndexes = /*@__PURE__*/ durationFieldNamesAsc.reduce((indexes, fieldName, i) => (indexes[fieldName] = i, indexes), {});
641
+ const durationFieldDefaults = /*@__PURE__*/ zipPropsConst(durationFieldNamesAsc, 0);
642
+ const durationTimeFieldDefaults = /*@__PURE__*/ zipPropsConst(durationTimeFieldNamesAsc, 0);
643
+ const clearDurationFields = /*@__PURE__*/ bindArgs(zeroOutProps, durationFieldNamesAsc);
622
644
  function requirePropDefined(optionName, optionVal) {
623
645
  return optionVal ?? throwRangeError(missingField(optionName)), optionVal;
624
646
  }
625
- const requireString = /* @__PURE__ */ bindArgs(requireType, "string");
647
+ const requireString = /*@__PURE__*/ bindArgs(requireType, "string");
626
648
  function requireType(typeName, arg, entityName = typeName) {
627
649
  return typeof arg !== typeName && throwTypeError(invalidEntity(entityName, arg)), arg;
628
650
  }
@@ -734,17 +756,17 @@ function coerceChoiceOption(optionName, enumNameMap, options, defaultChoice = 0)
734
756
  const enumNum = enumNameMap[enumStr];
735
757
  return void 0 === enumNum && throwRangeError(invalidChoice(optionName, enumStr, enumNameMap)), enumNum;
736
758
  }
737
- const coerceSmallestUnit = /* @__PURE__ */ bindArgs(coerceUnitOption, smallestUnitStr);
738
- const coerceLargestUnit = /* @__PURE__ */ bindArgs(coerceUnitOption, "largestUnit");
739
- const coerceTotalUnit = /* @__PURE__ */ bindArgs(coerceUnitOption, "unit");
740
- const coerceOverflow = /* @__PURE__ */ bindArgs(coerceChoiceOption, "overflow", overflowMap);
741
- const coerceEpochDisambig = /* @__PURE__ */ bindArgs(coerceChoiceOption, "disambiguation", epochDisambigMap);
742
- const coerceOffsetDisambig = /* @__PURE__ */ bindArgs(coerceChoiceOption, "offset", offsetDisambigMap);
743
- const coerceCalendarDisplay = /* @__PURE__ */ bindArgs(coerceChoiceOption, "calendarName", calendarDisplayMap);
744
- const coerceTimeZoneDisplay = /* @__PURE__ */ bindArgs(coerceChoiceOption, "timeZoneName", timeZoneDisplayMap);
745
- const coerceOffsetDisplay = /* @__PURE__ */ bindArgs(coerceChoiceOption, "offset", offsetDisplayMap);
746
- const coerceRoundingMode = /* @__PURE__ */ bindArgs(coerceChoiceOption, "roundingMode", roundingModeMap);
747
- const coerceDirection = /* @__PURE__ */ bindArgs(coerceChoiceOption, "direction", directionMap);
759
+ const coerceSmallestUnit = /*@__PURE__*/ bindArgs(coerceUnitOption, smallestUnitStr);
760
+ const coerceLargestUnit = /*@__PURE__*/ bindArgs(coerceUnitOption, "largestUnit");
761
+ const coerceTotalUnit = /*@__PURE__*/ bindArgs(coerceUnitOption, "unit");
762
+ const coerceOverflow = /*@__PURE__*/ bindArgs(coerceChoiceOption, "overflow", overflowMap);
763
+ const coerceEpochDisambig = /*@__PURE__*/ bindArgs(coerceChoiceOption, "disambiguation", epochDisambigMap);
764
+ const coerceOffsetDisambig = /*@__PURE__*/ bindArgs(coerceChoiceOption, "offset", offsetDisambigMap);
765
+ const coerceCalendarDisplay = /*@__PURE__*/ bindArgs(coerceChoiceOption, "calendarName", calendarDisplayMap);
766
+ const coerceTimeZoneDisplay = /*@__PURE__*/ bindArgs(coerceChoiceOption, "timeZoneName", timeZoneDisplayMap);
767
+ const coerceOffsetDisplay = /*@__PURE__*/ bindArgs(coerceChoiceOption, "offset", offsetDisplayMap);
768
+ const coerceRoundingMode = /*@__PURE__*/ bindArgs(coerceChoiceOption, "roundingMode", roundingModeMap);
769
+ const coerceDirection = /*@__PURE__*/ bindArgs(coerceChoiceOption, "direction", directionMap);
748
770
  function validateRoundingInc(roundingInc, smallestUnit, allowManyLargeUnits, solarMode) {
749
771
  const upUnitNano = solarMode ? nanoInUtcDay : unitNanoMap[smallestUnit + 1];
750
772
  if (upUnitNano) {
@@ -798,8 +820,8 @@ function refineZonedFieldOptions(options, defaultOffsetDisambig = 0) {
798
820
  epochDisambig
799
821
  ];
800
822
  }
801
- const epochNanoMax = /* @__PURE__ */ BigInt(1e8) * bigNanoInUtcDay;
802
- const epochNanoMin = /* @__PURE__ */ BigInt(-1e8) * bigNanoInUtcDay;
823
+ const epochNanoMax = /*@__PURE__*/ BigInt(1e8) * bigNanoInUtcDay;
824
+ const epochNanoMin = /*@__PURE__*/ BigInt(-1e8) * bigNanoInUtcDay;
803
825
  const plainDateEpochNanoMin = epochNanoMin - bigNanoInUtcDay;
804
826
  const isoYearMonthIndexMin = -3261848;
805
827
  function checkIsoYearMonthInBounds(isoDate) {
@@ -1070,11 +1092,12 @@ function nudgeRelativeDuration(sign, durationFields, endEpochNano, _largestUnit,
1070
1092
  }
1071
1093
  function getTimeZoneTransitionEpochNanoseconds(slots, options) {
1072
1094
  return slots.timeZone.U(slots.epochNanoseconds, ((options) => {
1073
- const res = coerceDirection(normalizeOptionsOrString(options, "direction"), 0);
1095
+ const normalizedOptions = normalizeOptionsOrString(options, "direction");
1096
+ const res = coerceDirection(normalizedOptions, 0);
1074
1097
  return res || throwRangeError(invalidEntity("direction", res)), res;
1075
1098
  })(options));
1076
1099
  }
1077
- const zonedEpochSlotsToIso = /* @__PURE__ */ memoize(_zonedEpochSlotsToIso, WeakMap);
1100
+ const zonedEpochSlotsToIso = /*@__PURE__*/ memoize(_zonedEpochSlotsToIso, WeakMap);
1078
1101
  function _zonedEpochSlotsToIso(slots) {
1079
1102
  const { epochNanoseconds, timeZone } = slots;
1080
1103
  const offsetNanoseconds = timeZone.C(epochNanoseconds);
@@ -1640,7 +1663,7 @@ function resolveTimeFields(fields, overflow) {
1640
1663
  ...fields
1641
1664
  }), overflow);
1642
1665
  }
1643
- const offsetRegExp = /* @__PURE__ */ createRegExp("([+-])(\\d{2})(?::?(\\d{2})(?::?(\\d{2})(?:[.,](\\d{1,9}))?)?)?");
1666
+ const offsetRegExp = /*@__PURE__*/ createRegExp("([+-])(\\d{2})(?::?(\\d{2})(?::?(\\d{2})(?:[.,](\\d{1,9}))?)?)?");
1644
1667
  function parseOffsetNano(s) {
1645
1668
  const offsetNano = parseOffsetNanoMaybe(s);
1646
1669
  return void 0 === offsetNano && throwRangeError(failedParse(s)), offsetNano;
@@ -1675,13 +1698,13 @@ const dateFieldRefiners = {
1675
1698
  },
1676
1699
  day: toPositiveIntegerWithTruncation
1677
1700
  };
1678
- const timeFieldRefiners = /* @__PURE__ */ zipPropsConst(timeFieldNamesAsc, toIntegerWithTrunc);
1679
- const durationFieldRefiners = /* @__PURE__ */ zipPropsConst(durationFieldNamesAsc, toStrictInteger);
1701
+ const timeFieldRefiners = /*@__PURE__*/ zipPropsConst(timeFieldNamesAsc, toIntegerWithTrunc);
1702
+ const durationFieldRefiners = /*@__PURE__*/ zipPropsConst(durationFieldNamesAsc, toStrictInteger);
1680
1703
  const offsetFieldRefiners = { offset(offsetString) {
1681
1704
  return parseOffsetNano(toStringViaPrimitive(offsetString));
1682
1705
  } };
1683
- const dateTimeFieldRefiners = /* @__PURE__ */ Object.assign({}, dateFieldRefiners, timeFieldRefiners);
1684
- const zonedDateTimeFieldRefiners = /* @__PURE__ */ Object.assign({}, dateTimeFieldRefiners, offsetFieldRefiners);
1706
+ const dateTimeFieldRefiners = /*@__PURE__*/ Object.assign({}, dateFieldRefiners, timeFieldRefiners);
1707
+ const zonedDateTimeFieldRefiners = /*@__PURE__*/ Object.assign({}, dateTimeFieldRefiners, offsetFieldRefiners);
1685
1708
  function readAndRefineBagFields(bag, validFieldNames, fieldRefiners, requiredFieldNames, disallowEmpty = !requiredFieldNames) {
1686
1709
  const res = {};
1687
1710
  let anyMatching = 0;
@@ -1784,7 +1807,9 @@ const timeZonePeriodDaysByName = {
1784
1807
  const minPossibleTransitionSec = -388152e4;
1785
1808
  function refineTimeDisplayTuple(options, maxSmallestUnit = 4) {
1786
1809
  const subsecDigits = coerceFractionalSecondDigits(options);
1787
- return [coerceRoundingMode(options, 4), ...resolveSmallestUnitAndSubsecDigits(validateUnitRange(smallestUnitStr, coerceSmallestUnit(options), 0, maxSmallestUnit), subsecDigits)];
1810
+ const roundingMode = coerceRoundingMode(options, 4);
1811
+ const smallestUnit = coerceSmallestUnit(options);
1812
+ return [roundingMode, ...resolveSmallestUnitAndSubsecDigits(validateUnitRange(smallestUnitStr, smallestUnit, 0, maxSmallestUnit), subsecDigits)];
1788
1813
  }
1789
1814
  function refineDateDisplayOptions(options) {
1790
1815
  return coerceCalendarDisplay(normalizeOptions(options));
@@ -1959,15 +1984,17 @@ function resolveTimeZoneRecord(rawId) {
1959
1984
  kind: "fixed",
1960
1985
  ...offsetRecord
1961
1986
  };
1962
- return queryNamedTimeZoneRecord("UTC" === upperRawId ? "UTC" : ((rawId) => (badCharactersRegExp.test(rawId) && throwRangeError(invalidTimeZone(rawId)), icuRegExp.test(rawId) && throwRangeError("Forbidden ICU TimeZone"), rawId.toLowerCase().split("/").map((part, partI) => (part.length <= 3 || /\d/.test(part)) && !/etc|yap/.test(part) ? part.toUpperCase() : part.replace(/baja|dumont|[a-z]+/g, (a, i) => a.length <= 2 && !partI || "in" === a || "chat" === a ? a.toUpperCase() : a.length > 2 || !i ? capitalize(a).replace(/island|noronha|murdo|rivadavia|urville/, capitalize) : a)).join("/")))(rawId));
1987
+ const normId = "UTC" === upperRawId ? "UTC" : ((rawId) => (badCharactersRegExp.test(rawId) && throwRangeError(invalidTimeZone(rawId)), icuRegExp.test(rawId) && throwRangeError("Forbidden ICU TimeZone"), rawId.toLowerCase().split("/").map((part, partI) => (part.length <= 3 || /\d/.test(part)) && !/etc|yap/.test(part) ? part.toUpperCase() : part.replace(/baja|dumont|[a-z]+/g, (a, i) => a.length <= 2 && !partI || "in" === a || "chat" === a ? a.toUpperCase() : a.length > 2 || !i ? capitalize(a).replace(/island|noronha|murdo|rivadavia|urville/, capitalize) : a)).join("/")))(rawId);
1988
+ return queryNamedTimeZoneRecord(normId);
1963
1989
  }
1964
- const queryNamedTimeZoneRecord = /* @__PURE__ */ memoize((normId) => {
1990
+ const queryNamedTimeZoneRecord = /*@__PURE__*/ memoize((normId) => {
1965
1991
  if ("UTC" === normId) return {
1966
1992
  kind: "utc",
1967
1993
  id: normId,
1968
1994
  o: normId
1969
1995
  };
1970
- const format = queryTimeZoneIntlFormat(normId.toUpperCase());
1996
+ const upperNormId = normId.toUpperCase();
1997
+ const format = queryTimeZoneIntlFormat(upperNormId);
1971
1998
  return {
1972
1999
  kind: "named",
1973
2000
  id: normId,
@@ -1975,7 +2002,7 @@ const queryNamedTimeZoneRecord = /* @__PURE__ */ memoize((normId) => {
1975
2002
  o: format.resolvedOptions().timeZone
1976
2003
  };
1977
2004
  });
1978
- const queryTimeZoneIntlFormat = /* @__PURE__ */ memoize((upperNormId) => new RawDateTimeFormat("en-u-hc-h23", {
2005
+ const queryTimeZoneIntlFormat = /*@__PURE__*/ memoize((upperNormId) => new RawDateTimeFormat("en-u-hc-h23", {
1979
2006
  calendar: "iso8601",
1980
2007
  timeZone: upperNormId,
1981
2008
  era: "short",
@@ -1990,7 +2017,7 @@ function queryTimeZone(rawTimeZoneId) {
1990
2017
  const record = resolveTimeZoneRecord(rawTimeZoneId);
1991
2018
  return queryTimeZoneRecord(record.id, record);
1992
2019
  }
1993
- const queryTimeZoneRecord = /* @__PURE__ */ memoize((normTimeZoneId, record) => "named" === record.kind ? new IntlTimeZone(normTimeZoneId, record.o, record.format) : new FixedTimeZone(normTimeZoneId, record.o, "fixed" === record.kind ? record._ : 0));
2020
+ const queryTimeZoneRecord = /*@__PURE__*/ memoize((normTimeZoneId, record) => "named" === record.kind ? new IntlTimeZone(normTimeZoneId, record.o, record.format) : new FixedTimeZone(normTimeZoneId, record.o, "fixed" === record.kind ? record._ : 0));
1994
2021
  var FixedTimeZone = class {
1995
2022
  constructor(id, compareKey, offsetNano) {
1996
2023
  this.id = id, this.o = compareKey, this._ = offsetNano;
@@ -2072,7 +2099,8 @@ var IntlTimeZone = class {
2072
2099
  return void 0 !== intlParts.era && "bce" === normalizeEraName(intlParts.era) ? 1 - year : year;
2073
2100
  })(intlParts), parseInt(intlParts.month), parseInt(intlParts.day)) + 3600 * parseInt(intlParts.hour) + 60 * parseInt(intlParts.minute) + parseInt(intlParts.second) - epochSec;
2074
2101
  })(format), ((timeZoneId) => {
2075
- return timeZonePeriodDaysByName[timeZoneId.split("/").pop()] || 60;
2102
+ const timeZoneName = timeZoneId.split("/").pop();
2103
+ return timeZonePeriodDaysByName[timeZoneName] || 60;
2076
2104
  })(id));
2077
2105
  }
2078
2106
  C(epochNano) {
@@ -2296,12 +2324,12 @@ function offsetRegExpStr(separatorIndex) {
2296
2324
  return "([+-])" + timeRegExpStr(separatorIndex);
2297
2325
  }
2298
2326
  const dateTimeRegExpStr = "(?:(?:([+-])(\\d{6}))|(\\d{4}))(-?)(\\d{2})\\4(\\d{2})(?:[T ]" + timeRegExpStr(8) + "(Z|" + offsetRegExpStr(15) + ")?)?";
2299
- const yearMonthRegExp = /* @__PURE__ */ createRegExp("(?:(?:([+-])(\\d{6}))|(\\d{4}))-?(\\d{2})((?:\\[(!?)([^\\]]*)\\]){0,9})");
2300
- const monthDayRegExp = /* @__PURE__ */ createRegExp("(?:--)?(\\d{2})-?(\\d{2})((?:\\[(!?)([^\\]]*)\\]){0,9})");
2301
- const dateTimeRegExp = /* @__PURE__ */ createRegExp(dateTimeRegExpStr + "((?:\\[(!?)([^\\]]*)\\]){0,9})");
2302
- const timeRegExp = /* @__PURE__ */ createRegExp("T?" + timeRegExpStr(2) + `(${offsetRegExpStr(9)})?((?:\\[(!?)([^\\]]*)\\]){0,9})`);
2303
- const annotationRegExp = /* @__PURE__ */ new RegExp("\\[(!?)([^\\]]*)\\]", "g");
2304
- const durationRegExp = /* @__PURE__ */ createRegExp("([+-])?P(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)?(?:T(?:(\\d+)(?:[.,](\\d{1,9}))?H)?(?:(\\d+)(?:[.,](\\d{1,9}))?M)?(?:(\\d+)(?:[.,](\\d{1,9}))?S)?)?");
2327
+ const yearMonthRegExp = /*@__PURE__*/ createRegExp("(?:(?:([+-])(\\d{6}))|(\\d{4}))-?(\\d{2})((?:\\[(!?)([^\\]]*)\\]){0,9})");
2328
+ const monthDayRegExp = /*@__PURE__*/ createRegExp("(?:--)?(\\d{2})-?(\\d{2})((?:\\[(!?)([^\\]]*)\\]){0,9})");
2329
+ const dateTimeRegExp = /*@__PURE__*/ createRegExp(dateTimeRegExpStr + "((?:\\[(!?)([^\\]]*)\\]){0,9})");
2330
+ const timeRegExp = /*@__PURE__*/ createRegExp("T?" + timeRegExpStr(2) + `(${offsetRegExpStr(9)})?((?:\\[(!?)([^\\]]*)\\]){0,9})`);
2331
+ const annotationRegExp = /*@__PURE__*/ new RegExp("\\[(!?)([^\\]]*)\\]", "g");
2332
+ const durationRegExp = /*@__PURE__*/ createRegExp("([+-])?P(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)?(?:T(?:(\\d+)(?:[.,](\\d{1,9}))?H)?(?:(\\d+)(?:[.,](\\d{1,9}))?M)?(?:(\\d+)(?:[.,](\\d{1,9}))?S)?)?");
2305
2333
  function parseDateTimeLike(s) {
2306
2334
  const parts = dateTimeRegExp.exec(s);
2307
2335
  return parts ? ((parts) => {
@@ -2579,8 +2607,8 @@ const timeDefaultShapeFields = {
2579
2607
  minute: "numeric",
2580
2608
  second: "numeric"
2581
2609
  };
2582
- const dateTimeDefaultShapeFields = /* @__PURE__ */ Object.assign({}, dateDefaultShapeFields, timeDefaultShapeFields);
2583
- const zonedDateTimeDefaultShapeFields = /* @__PURE__ */ Object.assign({}, dateTimeDefaultShapeFields, { timeZoneName: "short" });
2610
+ const dateTimeDefaultShapeFields = /*@__PURE__*/ Object.assign({}, dateDefaultShapeFields, timeDefaultShapeFields);
2611
+ const zonedDateTimeDefaultShapeFields = /*@__PURE__*/ Object.assign({}, dateTimeDefaultShapeFields, { timeZoneName: "short" });
2584
2612
  const yearMonthDefaultShapeFields = {
2585
2613
  year: "numeric",
2586
2614
  month: "numeric"
@@ -2604,15 +2632,15 @@ const timeShapeFieldNames = [
2604
2632
  "fractionalSecondDigits",
2605
2633
  "timeStyle"
2606
2634
  ];
2607
- const dateTimeShapeFieldNames = /* @__PURE__ */ dateShapeFieldNames.concat(timeShapeFieldNames);
2608
- const yearMonthIgnoredFieldNames = /* @__PURE__ */ ["weekday", "day"].concat(timeShapeFieldNames);
2609
- const monthDayIgnoredFieldNames = /* @__PURE__ */ ["weekday", "year"].concat(timeShapeFieldNames);
2610
- const transformInstantOptions = /* @__PURE__ */ createOptionsTransformer(dateTimeShapeFieldNames, [], [], dateTimeDefaultShapeFields);
2611
- const transformZonedOptions = /* @__PURE__ */ createOptionsTransformer(dateTimeShapeFieldNames, [], [], zonedDateTimeDefaultShapeFields);
2612
- const transformDateTimeOptions = /* @__PURE__ */ createOptionsTransformer(dateTimeShapeFieldNames, [], ["timeZoneName"], dateTimeDefaultShapeFields);
2613
- const transformDateOptions = /* @__PURE__ */ createOptionsTransformer(dateShapeFieldNames, timeShapeFieldNames, ["timeZoneName"], dateDefaultShapeFields);
2614
- const transformTimeOptions = /* @__PURE__ */ createOptionsTransformer(timeShapeFieldNames, dateShapeFieldNames, ["timeZoneName", "era"], timeDefaultShapeFields);
2615
- const transformYearMonthOptions = /* @__PURE__ */ createOptionsTransformer([
2635
+ const dateTimeShapeFieldNames = /*@__PURE__*/ dateShapeFieldNames.concat(timeShapeFieldNames);
2636
+ const yearMonthIgnoredFieldNames = /*@__PURE__*/ ["weekday", "day"].concat(timeShapeFieldNames);
2637
+ const monthDayIgnoredFieldNames = /*@__PURE__*/ ["weekday", "year"].concat(timeShapeFieldNames);
2638
+ const transformInstantOptions = /*@__PURE__*/ createOptionsTransformer(dateTimeShapeFieldNames, [], [], dateTimeDefaultShapeFields);
2639
+ const transformZonedOptions = /*@__PURE__*/ createOptionsTransformer(dateTimeShapeFieldNames, [], [], zonedDateTimeDefaultShapeFields);
2640
+ const transformDateTimeOptions = /*@__PURE__*/ createOptionsTransformer(dateTimeShapeFieldNames, [], ["timeZoneName"], dateTimeDefaultShapeFields);
2641
+ const transformDateOptions = /*@__PURE__*/ createOptionsTransformer(dateShapeFieldNames, timeShapeFieldNames, ["timeZoneName"], dateDefaultShapeFields);
2642
+ const transformTimeOptions = /*@__PURE__*/ createOptionsTransformer(timeShapeFieldNames, dateShapeFieldNames, ["timeZoneName", "era"], timeDefaultShapeFields);
2643
+ const transformYearMonthOptions = /*@__PURE__*/ createOptionsTransformer([
2616
2644
  "year",
2617
2645
  "month",
2618
2646
  "dateStyle"
@@ -2634,7 +2662,7 @@ const transformYearMonthOptions = /* @__PURE__ */ createOptionsTransformer([
2634
2662
  month: "numeric"
2635
2663
  }
2636
2664
  });
2637
- const transformMonthDayOptions = /* @__PURE__ */ createOptionsTransformer([
2665
+ const transformMonthDayOptions = /*@__PURE__*/ createOptionsTransformer([
2638
2666
  "month",
2639
2667
  "day",
2640
2668
  "dateStyle"
@@ -2808,8 +2836,8 @@ function resolveBasicCalendarId(rawCalendarId) {
2808
2836
  function resolveBasicCalendarArg(rawCalendarId = isoCalendarId) {
2809
2837
  return resolveBasicCalendarId(rawCalendarId);
2810
2838
  }
2811
- const zonedDateTimeSlotsMap = /* @__PURE__ */ new WeakMap();
2812
- const ZonedDateTime = /* @__PURE__ */ defineTemporalClass(ZonedDateTimeBranding, class {
2839
+ const zonedDateTimeSlotsMap = /*@__PURE__*/ new WeakMap();
2840
+ const ZonedDateTime = /*@__PURE__*/ defineTemporalClass(ZonedDateTimeBranding, class {
2813
2841
  constructor(epochNanoseconds, timeZoneId, calendar = void 0) {
2814
2842
  const epochNano = checkEpochNanoInBounds(toBigInt(epochNanoseconds));
2815
2843
  const timeZone = queryTimeZone(refineTimeZoneId(timeZoneId));
@@ -2957,8 +2985,8 @@ function refineTimeZoneArg(arg) {
2957
2985
  }
2958
2986
  return ((arg) => resolveTimeZoneId(parseTimeZoneId(requireString(arg))))(arg);
2959
2987
  }
2960
- const instantSlotsMap = /* @__PURE__ */ new WeakMap();
2961
- const Instant = /* @__PURE__ */ defineTemporalClass(InstantBranding, class {
2988
+ const instantSlotsMap = /*@__PURE__*/ new WeakMap();
2989
+ const Instant = /*@__PURE__*/ defineTemporalClass(InstantBranding, class {
2962
2990
  constructor(epochNanoseconds) {
2963
2991
  const epochNano = checkEpochNanoInBounds(toBigInt(epochNanoseconds));
2964
2992
  initInstant(this, createEpochNanoSlots(epochNano));
@@ -3043,8 +3071,8 @@ const { toTemporalInstant: toTemporalInstant$1 } = { toTemporalInstant() {
3043
3071
  function initInstant(instance, slots) {
3044
3072
  return instantSlotsMap.set(instance, slots), attachDebugString(instance), instance;
3045
3073
  }
3046
- const plainMonthDaySlotsMap = /* @__PURE__ */ new WeakMap();
3047
- const PlainMonthDay = /* @__PURE__ */ defineTemporalClass(PlainMonthDayBranding, class {
3074
+ const plainMonthDaySlotsMap = /*@__PURE__*/ new WeakMap();
3075
+ const PlainMonthDay = /*@__PURE__*/ defineTemporalClass(PlainMonthDayBranding, class {
3048
3076
  constructor(isoMonth, isoDay, calendar = void 0, referenceIsoYear) {
3049
3077
  const isoMonthInt = toIntegerWithTrunc(isoMonth);
3050
3078
  const isoDayInt = toIntegerWithTrunc(isoDay);
@@ -3108,8 +3136,8 @@ function toPlainMonthDaySlots(arg, options) {
3108
3136
  function initPlainMonthDay(instance, slots) {
3109
3137
  return plainMonthDaySlotsMap.set(instance, slots), attachDebugString(instance), instance;
3110
3138
  }
3111
- const plainYearMonthSlotsMap = /* @__PURE__ */ new WeakMap();
3112
- const PlainYearMonth = /* @__PURE__ */ defineTemporalClass(PlainYearMonthBranding, class {
3139
+ const plainYearMonthSlotsMap = /*@__PURE__*/ new WeakMap();
3140
+ const PlainYearMonth = /*@__PURE__*/ defineTemporalClass(PlainYearMonthBranding, class {
3113
3141
  constructor(isoYear, isoMonth, calendar = void 0, referenceIsoDay) {
3114
3142
  const isoYearInt = toIntegerWithTrunc(isoYear);
3115
3143
  const isoMonthInt = toIntegerWithTrunc(isoMonth);
@@ -3201,8 +3229,8 @@ function getTemporalBrandingAndSlots(obj) {
3201
3229
  function validateBag(bag) {
3202
3230
  return (getTemporalBrandingAndSlots(bag) || void 0 !== bag.calendar || void 0 !== bag.timeZone) && throwTypeError("Invalid bag"), bag;
3203
3231
  }
3204
- const plainTimeSlotsMap = /* @__PURE__ */ new WeakMap();
3205
- const PlainTime = /* @__PURE__ */ defineTemporalClass(PlainTimeBranding, class {
3232
+ const plainTimeSlotsMap = /*@__PURE__*/ new WeakMap();
3233
+ const PlainTime = /*@__PURE__*/ defineTemporalClass(PlainTimeBranding, class {
3206
3234
  constructor(hour = 0, minute = 0, second = 0, millisecond = 0, microsecond = 0, nanosecond = 0) {
3207
3235
  const fields = validateTimeFields(mapProps(toIntegerWithTrunc, {
3208
3236
  hour,
@@ -3284,8 +3312,8 @@ function optionalToPlainTimeFields(timeArg) {
3284
3312
  function initPlainTime(instance, slots) {
3285
3313
  return plainTimeSlotsMap.set(instance, slots), attachDebugString(instance), instance;
3286
3314
  }
3287
- const plainDateTimeSlotsMap = /* @__PURE__ */ new WeakMap();
3288
- const PlainDateTime = /* @__PURE__ */ defineTemporalClass(PlainDateTimeBranding, class {
3315
+ const plainDateTimeSlotsMap = /*@__PURE__*/ new WeakMap();
3316
+ const PlainDateTime = /*@__PURE__*/ defineTemporalClass(PlainDateTimeBranding, class {
3289
3317
  constructor(isoYear, isoMonth, isoDay, hour = 0, minute = 0, second = 0, millisecond = 0, microsecond = 0, nanosecond = 0, calendar = void 0) {
3290
3318
  const fields = checkIsoDateTimeInBounds(validateIsoDateTimeFields(mapProps(toIntegerWithTrunc, {
3291
3319
  year: isoYear,
@@ -3396,8 +3424,8 @@ function toPlainDateTimeSlots(arg, options) {
3396
3424
  function initPlainDateTime(instance, slots) {
3397
3425
  return plainDateTimeSlotsMap.set(instance, slots), attachDebugString(instance), instance;
3398
3426
  }
3399
- const plainDateSlotsMap = /* @__PURE__ */ new WeakMap();
3400
- const PlainDate = /* @__PURE__ */ defineTemporalClass(PlainDateBranding, class {
3427
+ const plainDateSlotsMap = /*@__PURE__*/ new WeakMap();
3428
+ const PlainDate = /*@__PURE__*/ defineTemporalClass(PlainDateBranding, class {
3401
3429
  constructor(isoYear, isoMonth, isoDay, calendar = void 0) {
3402
3430
  const fields = checkIsoDateInBounds(validateIsoDateFields(mapProps(toIntegerWithTrunc, {
3403
3431
  year: isoYear,
@@ -3515,8 +3543,8 @@ function refineCalendarArg(arg) {
3515
3543
  }
3516
3544
  return ((arg) => resolveBasicCalendarId(parseCalendarId(requireString(arg))))(arg);
3517
3545
  }
3518
- const durationSlotsMap = /* @__PURE__ */ new WeakMap();
3519
- const Duration = /* @__PURE__ */ defineTemporalClass(DurationBranding, class {
3546
+ const durationSlotsMap = /*@__PURE__*/ new WeakMap();
3547
+ const Duration = /*@__PURE__*/ defineTemporalClass(DurationBranding, class {
3520
3548
  constructor(years = 0, months = 0, weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0, microseconds = 0, nanoseconds = 0) {
3521
3549
  const fields = validateDurationFields(mapProps(toStrictInteger, {
3522
3550
  years,
@@ -3609,7 +3637,7 @@ function refinePublicRelativeTo(relativeTo) {
3609
3637
  function initDuration(instance, slots) {
3610
3638
  return durationSlotsMap.set(instance, slots), attachDebugString(instance), instance;
3611
3639
  }
3612
- const Now = /* @__PURE__ */ Object.defineProperties({}, {
3640
+ const Now = /*@__PURE__*/ Object.defineProperties({}, {
3613
3641
  ...createStringTagDescriptors("Temporal.Now"),
3614
3642
  ...createPropDescriptors({
3615
3643
  timeZoneId() {
@@ -3633,7 +3661,7 @@ const Now = /* @__PURE__ */ Object.defineProperties({}, {
3633
3661
  }
3634
3662
  })
3635
3663
  });
3636
- const Temporal$2 = /* @__PURE__ */ Object.defineProperties({}, {
3664
+ const Temporal$2 = /*@__PURE__*/ Object.defineProperties({}, {
3637
3665
  ...createStringTagDescriptors("Temporal"),
3638
3666
  ...createPropDescriptors({
3639
3667
  PlainYearMonth,
@@ -3652,6 +3680,64 @@ const Temporal$2 = /* @__PURE__ */ Object.defineProperties({}, {
3652
3680
  const Temporal$1 = NativeTemporal || Temporal$2;
3653
3681
  NativeTemporal && Date.prototype.toTemporalInstant;
3654
3682
  //#endregion
3683
+ //#region src/follow.ts
3684
+ /**
3685
+ * Validate Follow activity and return follower actor if valid.
3686
+ * This validation is common to both Mastodon and LitePub relay protocols.
3687
+ *
3688
+ * @param ctx The federation context
3689
+ * @param follow The Follow activity to validate
3690
+ * @returns The follower Actor if valid, null otherwise
3691
+ */
3692
+ async function validateFollowActivity(ctx, follow) {
3693
+ if (follow.id == null || follow.objectId == null) return null;
3694
+ const parsed = ctx.parseUri(follow.objectId);
3695
+ if (!(follow.objectId.href === "https://www.w3.org/ns/activitystreams#Public") && parsed?.type !== "actor") return null;
3696
+ const follower = await follow.getActor(ctx);
3697
+ if (follower == null || follower.id == null || follower.preferredUsername == null || follower.inboxId == null) return null;
3698
+ return follower;
3699
+ }
3700
+ /**
3701
+ * Send Accept or Reject response for a Follow activity.
3702
+ * This is common to both Mastodon and LitePub relay protocols.
3703
+ *
3704
+ * @param ctx The federation context
3705
+ * @param follow The Follow activity being responded to
3706
+ * @param follower The actor who sent the Follow
3707
+ * @param approved Whether the follow was approved
3708
+ */
3709
+ async function sendFollowResponse(ctx, follow, follower, approved) {
3710
+ const relayActorUri = ctx.getActorUri(RELAY_SERVER_ACTOR);
3711
+ const Activity = approved ? _fedify_vocab.Accept : _fedify_vocab.Reject;
3712
+ const action = approved ? "accepts" : "rejects";
3713
+ await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, follower, new Activity({
3714
+ id: new URL(`#${action}`, relayActorUri),
3715
+ actor: relayActorUri,
3716
+ object: follow
3717
+ }));
3718
+ }
3719
+ /**
3720
+ * Handle Undo activity for Follow.
3721
+ * This logic is identical for both Mastodon and LitePub relay protocols.
3722
+ *
3723
+ * @param ctx The federation context
3724
+ * @param undo The Undo activity to handle
3725
+ * @param logger The logger instance to use for warnings
3726
+ */
3727
+ async function handleUndoFollow(ctx, undo, logger) {
3728
+ const activity = await undo.getObject({
3729
+ crossOrigin: "trust",
3730
+ ...ctx
3731
+ });
3732
+ if (activity instanceof _fedify_vocab.Follow) {
3733
+ if (activity.id == null || activity.actorId == null) return;
3734
+ await ctx.data.kv.delete(["follower", activity.actorId.href]);
3735
+ } else logger.warn("Unsupported object type ({type}) for Undo activity: {object}", {
3736
+ type: activity?.constructor.name,
3737
+ object: activity
3738
+ });
3739
+ }
3740
+ //#endregion
3655
3741
  //#region src/base.ts
3656
3742
  /**
3657
3743
  * Abstract base class for relay implementations.
@@ -3671,25 +3757,6 @@ var BaseRelay = class {
3671
3757
  return await (await this.#getFederation()).fetch(request, { contextData: this.options });
3672
3758
  }
3673
3759
  /**
3674
- * Helper method to parse and validate follower data from storage.
3675
- * Deserializes JSON-LD actor data and validates it.
3676
- *
3677
- * @param actorId The actor ID of the follower
3678
- * @param data Raw data from KV store
3679
- * @returns RelayFollower object if valid, null otherwise
3680
- * @internal
3681
- */
3682
- async parseFollowerData(actorId, data) {
3683
- if (!isRelayFollowerData(data)) return null;
3684
- const actor = await _fedify_vocab.Object.fromJsonLd(data.actor);
3685
- if (!(0, _fedify_vocab.isActor)(actor)) return null;
3686
- return {
3687
- actorId,
3688
- actor,
3689
- state: data.state
3690
- };
3691
- }
3692
- /**
3693
3760
  * Lists all followers of the relay.
3694
3761
  *
3695
3762
  * @returns An async iterator of follower entries
@@ -3718,7 +3785,7 @@ var BaseRelay = class {
3718
3785
  for await (const entry of this.options.kv.list(["follower"])) {
3719
3786
  const actorId = entry.key[1];
3720
3787
  if (typeof actorId !== "string") continue;
3721
- const follower = await this.parseFollowerData(actorId, entry.value);
3788
+ const follower = await parseRelayFollowerData(actorId, entry.value);
3722
3789
  if (follower) yield follower;
3723
3790
  }
3724
3791
  }
@@ -3751,8 +3818,35 @@ var BaseRelay = class {
3751
3818
  * @since 2.0.0
3752
3819
  */
3753
3820
  async getFollower(actorId) {
3754
- const followerData = await this.options.kv.get(["follower", actorId]);
3755
- return await this.parseFollowerData(actorId, followerData);
3821
+ return await parseRelayFollowerData(actorId, await this.options.kv.get(["follower", actorId]));
3822
+ }
3823
+ shouldSkipFollow(_ctx, _follower) {
3824
+ return Promise.resolve(false);
3825
+ }
3826
+ afterFollowApproved(_ctx, _follower) {
3827
+ return Promise.resolve();
3828
+ }
3829
+ async #handleFollow(ctx, follow) {
3830
+ const follower = await validateFollowActivity(ctx, follow);
3831
+ if (follower?.id == null || await this.shouldSkipFollow(ctx, follower)) return;
3832
+ const approved = await this.options.subscriptionHandler(ctx, follower);
3833
+ if (approved) await ctx.data.kv.set(["follower", follower.id.href], {
3834
+ actor: await follower.toJsonLd(),
3835
+ state: this.initialFollowerState
3836
+ });
3837
+ await sendFollowResponse(ctx, follow, follower, approved);
3838
+ if (approved) await this.afterFollowApproved(ctx, follower);
3839
+ }
3840
+ async #relayActivity(ctx, activity) {
3841
+ const senderId = activity.actorId;
3842
+ const excludeBaseUris = senderId == null ? [] : [senderId];
3843
+ await this.deliverActivity(ctx, activity, excludeBaseUris);
3844
+ }
3845
+ setupInboxListeners() {
3846
+ if (this.federation == null) throw new Error("Federation must be initialized before inbox listeners");
3847
+ const listeners = this.federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
3848
+ listeners.on(_fedify_vocab.Follow, async (ctx, follow) => await this.#handleFollow(ctx, follow)).on(_fedify_vocab.Undo, async (ctx, undo) => await handleUndoFollow(ctx, undo, this.logger)).on(_fedify_vocab.Create, async (ctx, create) => await this.#relayActivity(ctx, create)).on(_fedify_vocab.Delete, async (ctx, deleteActivity) => await this.#relayActivity(ctx, deleteActivity)).on(_fedify_vocab.Move, async (ctx, move) => await this.#relayActivity(ctx, move)).on(_fedify_vocab.Update, async (ctx, update) => await this.#relayActivity(ctx, update)).on(_fedify_vocab.Announce, async (ctx, announce) => await this.#relayActivity(ctx, announce));
3849
+ return listeners;
3756
3850
  }
3757
3851
  async #getFederation() {
3758
3852
  if (this.federation == null) {
@@ -3772,64 +3866,6 @@ var BaseRelay = class {
3772
3866
  }
3773
3867
  };
3774
3868
  //#endregion
3775
- //#region src/follow.ts
3776
- /**
3777
- * Validate Follow activity and return follower actor if valid.
3778
- * This validation is common to both Mastodon and LitePub relay protocols.
3779
- *
3780
- * @param ctx The federation context
3781
- * @param follow The Follow activity to validate
3782
- * @returns The follower Actor if valid, null otherwise
3783
- */
3784
- async function validateFollowActivity(ctx, follow) {
3785
- if (follow.id == null || follow.objectId == null) return null;
3786
- const parsed = ctx.parseUri(follow.objectId);
3787
- if (!(follow.objectId.href === "https://www.w3.org/ns/activitystreams#Public") && parsed?.type !== "actor") return null;
3788
- const follower = await follow.getActor(ctx);
3789
- if (follower == null || follower.id == null || follower.preferredUsername == null || follower.inboxId == null) return null;
3790
- return follower;
3791
- }
3792
- /**
3793
- * Send Accept or Reject response for a Follow activity.
3794
- * This is common to both Mastodon and LitePub relay protocols.
3795
- *
3796
- * @param ctx The federation context
3797
- * @param follow The Follow activity being responded to
3798
- * @param follower The actor who sent the Follow
3799
- * @param approved Whether the follow was approved
3800
- */
3801
- async function sendFollowResponse(ctx, follow, follower, approved) {
3802
- const relayActorUri = ctx.getActorUri(RELAY_SERVER_ACTOR);
3803
- const Activity = approved ? _fedify_vocab.Accept : _fedify_vocab.Reject;
3804
- const action = approved ? "accepts" : "rejects";
3805
- await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, follower, new Activity({
3806
- id: new URL(`#${action}`, relayActorUri),
3807
- actor: relayActorUri,
3808
- object: follow
3809
- }));
3810
- }
3811
- /**
3812
- * Handle Undo activity for Follow.
3813
- * This logic is identical for both Mastodon and LitePub relay protocols.
3814
- *
3815
- * @param ctx The federation context
3816
- * @param undo The Undo activity to handle
3817
- * @param logger The logger instance to use for warnings
3818
- */
3819
- async function handleUndoFollow(ctx, undo, logger) {
3820
- const activity = await undo.getObject({
3821
- crossOrigin: "trust",
3822
- ...ctx
3823
- });
3824
- if (activity instanceof _fedify_vocab.Follow) {
3825
- if (activity.id == null || activity.actorId == null) return;
3826
- await ctx.data.kv.delete(["follower", activity.actorId.href]);
3827
- } else logger.warn("Unsupported object type ({type}) for Undo activity: {object}", {
3828
- type: activity?.constructor.name,
3829
- object: activity
3830
- });
3831
- }
3832
- //#endregion
3833
3869
  //#region src/litepub.ts
3834
3870
  const Temporal = Temporal$1;
3835
3871
  const logger$1 = (0, _logtape_logtape.getLogger)([
@@ -3845,9 +3881,23 @@ const logger$1 = (0, _logtape_logtape.getLogger)([
3845
3881
  * @since 2.0.0
3846
3882
  */
3847
3883
  var LitePubRelay = class extends BaseRelay {
3848
- async #announceToFollowers(ctx, activity) {
3849
- const sender = await activity.getActor(ctx);
3850
- const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
3884
+ initialFollowerState = "pending";
3885
+ logger = logger$1;
3886
+ async shouldSkipFollow(ctx, follower) {
3887
+ if (follower.id == null) return true;
3888
+ const existingFollow = await ctx.data.kv.get(["follower", follower.id.href]);
3889
+ return await parseRelayFollowerData(follower.id.href, existingFollow) != null;
3890
+ }
3891
+ async afterFollowApproved(ctx, follower) {
3892
+ if (follower.id == null) return;
3893
+ const relayActorUri = ctx.getActorUri(RELAY_SERVER_ACTOR);
3894
+ await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, follower, new _fedify_vocab.Follow({
3895
+ actor: relayActorUri,
3896
+ object: follower.id,
3897
+ to: follower.id
3898
+ }));
3899
+ }
3900
+ async deliverActivity(ctx, activity, excludeBaseUris) {
3851
3901
  const announce = new _fedify_vocab.Announce({
3852
3902
  id: new URL(`/announce#${crypto.randomUUID()}`, ctx.origin),
3853
3903
  actor: ctx.getActorUri(RELAY_SERVER_ACTOR),
@@ -3861,25 +3911,7 @@ var LitePubRelay = class extends BaseRelay {
3861
3911
  });
3862
3912
  }
3863
3913
  setupInboxListeners() {
3864
- if (this.federation != null) this.federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(_fedify_vocab.Follow, async (ctx, follow) => {
3865
- const follower = await validateFollowActivity(ctx, follow);
3866
- if (!follower || !follower.id) return;
3867
- if ((await ctx.data.kv.get(["follower", follower.id.href]))?.state === "pending") return;
3868
- const approved = await this.options.subscriptionHandler(ctx, follower);
3869
- if (approved) {
3870
- await ctx.data.kv.set(["follower", follower.id.href], {
3871
- actor: await follower.toJsonLd(),
3872
- state: "pending"
3873
- });
3874
- await sendFollowResponse(ctx, follow, follower, approved);
3875
- const relayActorUri = ctx.getActorUri(RELAY_SERVER_ACTOR);
3876
- await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, follower, new _fedify_vocab.Follow({
3877
- actor: relayActorUri,
3878
- object: follower.id,
3879
- to: follower.id
3880
- }));
3881
- } else await sendFollowResponse(ctx, follow, follower, approved);
3882
- }).on(_fedify_vocab.Accept, async (ctx, accept) => {
3914
+ return super.setupInboxListeners().on(_fedify_vocab.Accept, async (ctx, accept) => {
3883
3915
  const follow = await accept.getObject({
3884
3916
  crossOrigin: "trust",
3885
3917
  ...ctx
@@ -3887,18 +3919,19 @@ var LitePubRelay = class extends BaseRelay {
3887
3919
  if (!(follow instanceof _fedify_vocab.Follow)) return;
3888
3920
  const relayActorId = follow.actorId;
3889
3921
  if (relayActorId == null) return;
3890
- const followerActor = await accept.getActor();
3922
+ const followerActor = await accept.getActor(ctx);
3891
3923
  if (!(0, _fedify_vocab.isActor)(followerActor) || !followerActor.id) return;
3892
3924
  const parsed = ctx.parseUri(relayActorId);
3893
3925
  if (parsed == null || parsed.type !== "actor") return;
3894
3926
  const followerData = await ctx.data.kv.get(["follower", followerActor.id.href]);
3895
- if (followerData == null) return;
3927
+ if (!isRelayFollowerData(followerData)) return;
3928
+ if (await parseRelayFollowerData(followerActor.id.href, followerData) == null) return;
3896
3929
  const updatedFollowerData = {
3897
3930
  ...followerData,
3898
3931
  state: "accepted"
3899
3932
  };
3900
3933
  await ctx.data.kv.set(["follower", followerActor.id.href], updatedFollowerData);
3901
- }).on(_fedify_vocab.Undo, async (ctx, undo) => await handleUndoFollow(ctx, undo, logger$1)).on(_fedify_vocab.Create, async (ctx, create) => await this.#announceToFollowers(ctx, create)).on(_fedify_vocab.Update, async (ctx, update) => await this.#announceToFollowers(ctx, update)).on(_fedify_vocab.Move, async (ctx, move) => await this.#announceToFollowers(ctx, move)).on(_fedify_vocab.Delete, async (ctx, deleteActivity) => await this.#announceToFollowers(ctx, deleteActivity)).on(_fedify_vocab.Announce, async (ctx, announce) => await this.#announceToFollowers(ctx, announce));
3934
+ });
3902
3935
  }
3903
3936
  };
3904
3937
  //#endregion
@@ -3916,27 +3949,15 @@ const logger = (0, _logtape_logtape.getLogger)([
3916
3949
  * @since 2.0.0
3917
3950
  */
3918
3951
  var MastodonRelay = class extends BaseRelay {
3919
- async #forwardToFollowers(ctx, activity) {
3920
- const sender = await activity.getActor(ctx);
3921
- const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
3952
+ initialFollowerState = "accepted";
3953
+ logger = logger;
3954
+ async deliverActivity(ctx, _activity, excludeBaseUris) {
3922
3955
  await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
3923
3956
  skipIfUnsigned: true,
3924
3957
  excludeBaseUris,
3925
3958
  preferSharedInbox: true
3926
3959
  });
3927
3960
  }
3928
- setupInboxListeners() {
3929
- if (this.federation != null) this.federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(_fedify_vocab.Follow, async (ctx, follow) => {
3930
- const follower = await validateFollowActivity(ctx, follow);
3931
- if (!follower || !follower.id) return;
3932
- const approved = await this.options.subscriptionHandler(ctx, follower);
3933
- if (approved) await ctx.data.kv.set(["follower", follower.id.href], {
3934
- actor: await follower.toJsonLd(),
3935
- state: "accepted"
3936
- });
3937
- await sendFollowResponse(ctx, follow, follower, approved);
3938
- }).on(_fedify_vocab.Undo, async (ctx, undo) => await handleUndoFollow(ctx, undo, logger)).on(_fedify_vocab.Create, async (ctx, create) => await this.#forwardToFollowers(ctx, create)).on(_fedify_vocab.Delete, async (ctx, deleteActivity) => await this.#forwardToFollowers(ctx, deleteActivity)).on(_fedify_vocab.Move, async (ctx, move) => await this.#forwardToFollowers(ctx, move)).on(_fedify_vocab.Update, async (ctx, update) => await this.#forwardToFollowers(ctx, update)).on(_fedify_vocab.Announce, async (ctx, announce) => await this.#forwardToFollowers(ctx, announce));
3939
- }
3940
3961
  };
3941
3962
  //#endregion
3942
3963
  //#region src/factory.ts