@rmdes/indiekit-endpoint-activitypub 3.6.9 → 3.7.1

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.
@@ -7,12 +7,18 @@
7
7
  * LRU-style with TTL — entries expire after 1 hour.
8
8
  */
9
9
 
10
+ import { remoteActorId } from "./id-mapping.js";
11
+
10
12
  const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
11
13
  const MAX_ENTRIES = 500;
12
14
 
13
15
  // Map<actorUrl, { followersCount, followingCount, statusesCount, createdAt, cachedAt }>
14
16
  const cache = new Map();
15
17
 
18
+ // Reverse map: accountId (hash) → actorUrl
19
+ // Populated alongside the stats cache for follow/unfollow lookups
20
+ const idToUrl = new Map();
21
+
16
22
  /**
17
23
  * Store account stats in cache.
18
24
  * @param {string} actorUrl - The actor's URL (cache key)
@@ -28,6 +34,10 @@ export function cacheAccountStats(actorUrl, stats) {
28
34
  }
29
35
 
30
36
  cache.set(actorUrl, { ...stats, cachedAt: Date.now() });
37
+
38
+ // Maintain reverse lookup
39
+ const hashId = remoteActorId(actorUrl);
40
+ if (hashId) idToUrl.set(hashId, actorUrl);
31
41
  }
32
42
 
33
43
  /**
@@ -49,3 +59,12 @@ export function getCachedAccountStats(actorUrl) {
49
59
 
50
60
  return entry;
51
61
  }
62
+
63
+ /**
64
+ * Reverse lookup: get actor URL from account hash ID.
65
+ * @param {string} hashId - The 24-char hex account ID
66
+ * @returns {string|null} Actor URL or null
67
+ */
68
+ export function getActorUrlFromId(hashId) {
69
+ return idToUrl.get(hashId) || null;
70
+ }
@@ -10,6 +10,7 @@ import { serializeStatus } from "../entities/status.js";
10
10
  import { accountId, remoteActorId } from "../helpers/id-mapping.js";
11
11
  import { buildPaginationQuery, parseLimit, setPaginationHeaders } from "../helpers/pagination.js";
12
12
  import { resolveRemoteAccount } from "../helpers/resolve-account.js";
13
+ import { getActorUrlFromId } from "../helpers/account-cache.js";
13
14
 
14
15
  const router = express.Router(); // eslint-disable-line new-cap
15
16
 
@@ -714,6 +715,10 @@ async function resolveActorUrl(id, collections) {
714
715
  return profile.url;
715
716
  }
716
717
 
718
+ // Check account cache reverse lookup (populated by resolveRemoteAccount)
719
+ const cachedUrl = getActorUrlFromId(id);
720
+ if (cachedUrl) return cachedUrl;
721
+
717
722
  // Check followers
718
723
  const followers = await collections.ap_followers.find({}).toArray();
719
724
  for (const f of followers) {
@@ -567,8 +567,17 @@ async function findTimelineItemById(collection, id) {
567
567
  // Try cursor-based lookup first (published date from ms-since-epoch)
568
568
  const publishedDate = decodeCursor(id);
569
569
  if (publishedDate) {
570
- const item = await collection.findOne({ published: publishedDate });
570
+ // Try exact match first (with .000Z suffix from toISOString)
571
+ let item = await collection.findOne({ published: publishedDate });
571
572
  if (item) return item;
573
+
574
+ // Try without milliseconds — stored dates often lack .000Z
575
+ // e.g., "2026-03-21T15:33:50Z" vs "2026-03-21T15:33:50.000Z"
576
+ const withoutMs = publishedDate.replace(/\.000Z$/, "Z");
577
+ if (withoutMs !== publishedDate) {
578
+ item = await collection.findOne({ published: withoutMs });
579
+ if (item) return item;
580
+ }
572
581
  }
573
582
 
574
583
  // Fall back to ObjectId lookup (legacy IDs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "3.6.9",
3
+ "version": "3.7.1",
4
4
  "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
5
5
  "keywords": [
6
6
  "indiekit",