towards multi-user
This commit is contained in:
@@ -58,28 +58,76 @@ function emptyIndex(): BASIndex {
|
||||
// ── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Load the activity index, merging the server's copy with any locally-stored
|
||||
* activities. Local entries override server entries with the same ID.
|
||||
* Resolve shards from a BASIndex into a flat activity list.
|
||||
*
|
||||
* Handles two shard types transparently:
|
||||
* - handle shards: multi-user manifest (url = "{handle}/index.json")
|
||||
* - year shards: per-user pagination (url = "index-2025.json")
|
||||
*
|
||||
* Shard URLs are resolved relative to the index URL that declared them.
|
||||
* All shard fetches run concurrently. Errors are silently skipped so a
|
||||
* single unavailable shard doesn't break the whole feed.
|
||||
*/
|
||||
export async function loadIndex(baseUrl: string): Promise<BASIndex> {
|
||||
async function resolveShards(
|
||||
index: BASIndex,
|
||||
indexUrl: string,
|
||||
): Promise<ActivitySummary[]> {
|
||||
if (!index.shards?.length) return index.activities ?? [];
|
||||
|
||||
const base = indexUrl.substring(0, indexUrl.lastIndexOf('/') + 1);
|
||||
|
||||
const shardResults = await Promise.allSettled(
|
||||
index.shards.map(async shard => {
|
||||
const url = shard.url.startsWith('http') ? shard.url : `${base}${shard.url}`;
|
||||
const sub = await fetchJSON<BASIndex>(url);
|
||||
// Recursively resolve nested shards (e.g. user shard that itself paginates)
|
||||
const activities = await resolveShards(sub, url);
|
||||
// Tag each activity with the handle declared in the shard entry
|
||||
if (shard.handle) {
|
||||
return activities.map(a => ({ ...a, handle: shard.handle }));
|
||||
}
|
||||
return activities;
|
||||
}),
|
||||
);
|
||||
|
||||
const own = index.activities ?? [];
|
||||
const fromShards = shardResults.flatMap(r => r.status === 'fulfilled' ? r.value : []);
|
||||
return [...own, ...fromShards];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the activity index, resolving any shards (multi-user or pagination),
|
||||
* then merging with locally-stored activities from IndexedDB.
|
||||
*
|
||||
* Single-user indexes with no shards work exactly as before — zero overhead.
|
||||
*
|
||||
* @param baseUrl Site base URL (used for IDB local activities)
|
||||
* @param indexUrl Full URL of the index to load (defaults to baseUrl + data/index.json)
|
||||
*/
|
||||
export async function loadIndex(baseUrl: string, indexUrl?: string): Promise<BASIndex> {
|
||||
indexUrl = indexUrl ?? `${baseUrl}data/index.json`;
|
||||
|
||||
const [serverResult, localResult] = await Promise.allSettled([
|
||||
fetchJSON<BASIndex>(`${baseUrl}data/index.json`),
|
||||
fetchJSON<BASIndex>(indexUrl),
|
||||
listLocalActivities(),
|
||||
]);
|
||||
|
||||
const server = serverResult.status === 'fulfilled' ? serverResult.value : null;
|
||||
const local = localResult.status === 'fulfilled' ? localResult.value : [];
|
||||
|
||||
if (local.length === 0) return server ?? emptyIndex();
|
||||
if (!server) return { ...emptyIndex(), activities: local as ActivitySummary[] };
|
||||
const serverActivities = server
|
||||
? await resolveShards(server, indexUrl)
|
||||
: [];
|
||||
|
||||
if (local.length === 0 && !server) return emptyIndex();
|
||||
|
||||
// Local overrides server for the same ID; new local entries are appended
|
||||
const merged = new Map<string, ActivitySummary>();
|
||||
for (const a of server.activities ?? []) merged.set(a.id, a);
|
||||
for (const a of serverActivities) merged.set(a.id, a);
|
||||
for (const a of local as ActivitySummary[]) merged.set(a.id, a);
|
||||
|
||||
return {
|
||||
...server,
|
||||
...(server ?? emptyIndex()),
|
||||
activities: [...merged.values()].sort(
|
||||
(a, b) => (b.started_at ?? '').localeCompare(a.started_at ?? ''),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user