1e6fb78d58b5

build each public repo's Atlas after its sync, so the first visitor after a new save gets a warm page (v3.0.186)

time
2026-10-01 20:40 UTC · 1 week ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
atlas speed
parent
dc678fae81ff
6 files package.json+1 −1src/bucket/sync.ts+3 −0src/main.ts+9 −1src/pages/atlas.ts+10 −0src/pages/layout.ts+4 −1tests/sync.test.ts+11 −0
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.185",3+	"version": "3.0.186",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

src/bucket/sync.ts+3 −0
@@ -44,6 +44,8 @@4444 	bucket: Bucket;4545 	kepr: Database;4646 	mirrorDir: string;47+	/** Called after each repo that synced without error. */48+	synced?: (repo: string) => void;4749 };4850 4951 let running: Promise<void> | null = null;@@ -113,6 +115,7 @@113115 			}114116 		}115117 		recordSync(target.kepr, repo, Math.floor(Date.now() / 1000), error);118+		if (error === null) target.synced?.(repo);116119 	}117120 }118121 

view file

src/main.ts+9 −1
@@ -3,6 +3,7 @@33 import { createSetupApp } from "./admin/setup.ts";44 import { openBucket } from "./bucket/reader.ts";55 import { startSyncLoop } from "./bucket/sync.ts";6+import { warmAtlas } from "./pages/atlas.ts";67 import { createApp } from "./server/app.ts";78 import { startBackupLoop } from "./server/backup.ts";89 import { CONFIG_FILE, type Config, ConfigError, loadConfig } from "./server/config.ts";@@ -20,7 +21,14 @@2021 	const bucket = openBucket(config.bucket);2122 	startBackupLoop(kepr, config);2223 	startMailLoop(kepr, config);23-	startSyncLoop({ bucket, kepr, mirrorDir: join(config.dataDir, "mirror") });24+	const mirrorDir = join(config.dataDir, "mirror");25+	// The first visitor after a new save would otherwise wait a second or two on a big repo.26+	startSyncLoop({27+		bucket,28+		kepr,29+		mirrorDir,30+		synced: (repo) => warmAtlas({ kepr, mirrorDir }, repo),31+	});2432 	return createApp(config, kepr, bucket).fetch;2533 }2634 

view file

src/pages/atlas.ts+10 −0
@@ -3,6 +3,7 @@33 import {44 	filesAt,55 	fileTouches,6+	headId,67 	objectBytes,78 	type Save,89 	type Touches,@@ -13,6 +14,8 @@1314 import {1415 	formatDate,1516 	formatSize,17+	openPublicRepo,18+	type PageContext,1619 	type PublicRepo,1720 	pathUrl,1821 	plural,@@ -119,6 +122,13 @@119122 	if (models.size >= MODELS) models.delete(models.keys().next().value as string);120123 	models.set(key, model);121124 	return model;125+}126+127+/** Builds a public repo's Atlas at its head ahead of the first visitor, as the sync calls it. */128+export function warmAtlas(ctx: Pick<PageContext, "kepr" | "mirrorDir">, name: string): void {129+	const repo = openPublicRepo(ctx, name);130+	const head = repo && headId(repo.mirror);131+	if (repo && head) atlasModel(repo, head);122132 }123133 124134 /**

view file

src/pages/layout.ts+4 −1
@@ -23,7 +23,10 @@2323 /**2424  * The gate of §20: a private repo and an unknown one both come back null, and so both 404 alike.2525  */26-export function openPublicRepo(ctx: PageContext, name: string): PublicRepo | null {26+export function openPublicRepo(27+	ctx: Pick<PageContext, "kepr" | "mirrorDir">,28+	name: string,29+): PublicRepo | null {2730 	const settings = publicRepo(ctx.kepr, name);2831 	return settings ? { settings, mirror: openMirror(ctx.mirrorDir, name) } : null;2932 }

view file

tests/sync.test.ts+11 −0
@@ -136,6 +136,17 @@136136 	expect(saveByPrefix(mirror, "af96c81")?.message).toBe("made by Koh 3");137137 });138138 139+test("tells its target about each repo that synced cleanly, and not one that failed", async () => {140+	const fx = await makeKohBucket("told");141+	const t = await syncTarget(fx);142+	const synced: string[] = [];143+	await syncAll({ ...t, synced: (repo) => synced.push(repo) });144+	expect(synced).toEqual(["told"]);145+	await writeFile(join(fx.bucketDir, fx.repo, "format"), "koh-remote 2");146+	await syncAll({ ...t, synced: (repo) => synced.push(repo) });147+	expect(synced).toEqual(["told"]);148+});149+139150 test("a sync lands history in batches, so one cut short resumes where it stopped", async () => {140151 	const fx = await makeKohBucket("batched");141152 	const bucket = openBucket({ kind: "folder", path: fx.bucketDir });

view file