iori-nav
CLAUDE.md745 starsChanged 11 months agoMITView on GitHub
iori-nav(灰色轨迹) 是一个完全跑在 Cloudflare 上的书签导航站。 AGENTS.md 是给 AI 助手阅读的详细规范,请在动手前一并参考。 本地开发依赖 wrangler.toml(在 .gitignore 中),首次需自行填入 D1 / KV 资源 ID。 详见 functions/constants.js 顶部注释。 functions/index.js 才是首页 handler。public/index.html 只是模板,里面散落着 {{PLACEHOLDER}},由 index.js 用数据库内容、settings、菜单 / 卡片渲染器(functions/lib/{card,menu}-renderer.js)填充。 functions/api/settings.js 写设置 → 同时清除 KV settings_cache + 标记 home cache dirty;functions/index.js 读取时优先查 settings_cache(24h TTL),未命中再查 D1 并异步回填。只改 WebDAV / AI 配置(两者都不进 SETTINGS_SCHEMA)时不动这两个缓存,因为它们不影响 SSR 输出。秘密字段(apiKey / webdav_password)统一三态:null 清除、'' 保留、其他值写入。
- Installs packages
In this repositoryOne other file it gives its agents.
- AGENTS.mdAGENTS.md
The file
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
项目概览
iori-nav(灰色轨迹) 是一个完全跑在 Cloudflare 上的书签导航站。
- 运行平台:Cloudflare Pages + Pages Functions(Workers)+ D1(SQLite)+ KV
- 语言:纯 JavaScript(ES Modules,无 TypeScript)
- 前端:原生 HTML/JS + TailwindCSS(SSR 模板替换,不是 SPA)
- 测试 / lint:测试使用 Node.js 内置
node:test,暂无 lint 工具;主要 dev 依赖是 TailwindCSS 与 Husky
AGENTS.md 是给 AI 助手阅读的详细规范,请在动手前一并参考。
常用命令
npm install # 安装依赖(Tailwind / Husky)
npm run build:css # 构建 tailwind.min.css(修改 tailwind.css 后必跑)
npm run dev:css # Tailwind watch 模式
npm run dev # 启动 wrangler pages dev(predev 会先跑 update-versions.js)
npm run check # 语法检查 + node:test 测试
npm test # 运行 node:test 测试
npm run version # 手动重新计算静态资源 ?v= 哈希
npm run changelog # 根据 git log 自动更新 README 更新日志区块
# D1 数据库(本地 / 远程)
npx wrangler d1 execute book --local --file=schema.sql
npx wrangler d1 execute book --remote --file=schema.sql本地开发依赖
wrangler.toml(在.gitignore中),首次需自行填入 D1 / KV 资源 ID。
架构要点(必须理解的大局)
1. 三套并存的"版本机制"——不要混用
详见 functions/constants.js 顶部注释。
| 名称 | 作用 | 何时改 |
SCHEMA_VERSION | 触发 D1 schema 迁移 | 改表 / 字段 / 索引时手动 +1 |
HOME_CACHE_VERSION | 首页 HTML KV 缓存键 | 想强制刷新已缓存首页时 |
静态资源 ?v=hash | 浏览器侧 CSS/JS 缓存 | 不要手动改,pre-commit hook 通过 scripts/update-versions.js 自动写回 HTML |
2. 运行时 Schema 迁移
- 入口:
functions/lib/schema-migration.js的ensureSchemaReady() - 通过
_middleware.js在所有非"首页 GET"路径上await触发;首页 GET 路径把它和 KV 读并行掉,避免命中 HIT 时多一次串行 - 迁移成功后在 KV 中写
schema_migrated_{SCHEMA_VERSION}长期标记;冷启动只需 1 次 KV 读判断即可短路 - 新增字段流程:在
runIncrementalMigrations()内追加ALTER,并把SCHEMA_VERSION(含PREVIOUS_SCHEMA_VERSION)一起 +1
3. 首页是 SSR + KV HTML 缓存(不是静态 HTML)
functions/index.js 才是首页 handler。public/index.html 只是模板,里面散落着 {{PLACEHOLDER}},由 index.js 用数据库内容、settings、菜单 / 卡片渲染器(functions/lib/{card,menu}-renderer.js)填充。
- 公私两份缓存:
home_html_{public,private}_{HOME_CACHE_VERSION}_{generation}(key 由getHomeCacheKey(scope, generation)生成);generation 复用 D1settings.graph_cache_generation,首页读取前必须先保证 schema 就绪,不要缓存这个版本号到 KV - 两份缓存与 dirty 标记共用
HOME_CACHE_TTL(6 小时)。不要单独调长 HTML 那份:dirty 标记必须不比 HTML 活得短,否则会出现「旧 HTML 还在、dirty 已过期」的脏读窗口 - 写操作(增删改 site / category / settings)必须调用
markHomeCacheDirty(env, scope)标记 dirty,下一次 GET 才会重渲染并回填缓存 - 写完不要主动 delete 缓存——
markHomeCacheDirty先轮换 D1 generation;回填走writeHomeCacheIfUnchanged(env, scope, html, expectedGeneration),版本变了就不写。即使版本在检查后变更,旧 HTML 也只能写入旧版本的键,无法覆盖新版本页面 - 冷缓存直接按 D1 版本回填(初始版本为
'0'),不要在 KV 占位后要求立即读回 token:KV 的负缓存与最终一致性不保证写后读。版本读取失败时跳过缓存;每次首页 HIT 也需要一次 D1 版本查询 - KV dirty 标记保留为设置缓存传播和版本写入失败时的兜底;渲染开始时有标记才在回填后用
clearHomeCacheDirtyIfValue(env, scope, expectedValue)条件清除。管理端强制刷新先删除 HTML / 设置缓存,再用clearGraphCache(env, { throwOnError: true })轮换 generation,成功后才无条件清 dirty;版本读取或轮换失败返回 500 并保留 dirty。普通数据写入仍使用版本轮换的 best-effort 行为,让 KV dirty 兜底 cachedTemplateHtml在 Worker 实例内缓存,避免每次 MISS 重复ASSETS.fetch('/index.html')- 标签间空白会被
>\s+<→><压缩,所以模板里不要塞<pre>/<textarea>
4. 认证 / CSRF / 防爆破(全在 _middleware.js)
- 登录走 form POST,成功后写
admin_session=<uuid>; HttpOnly; Secure; SameSite=Lax,对应 KVsession_${token};TTL 用户可选 1/7/30/60/90 天 - CSRF:登录同时生成
csrf_${token}写 KV,functions/admin/index.js把它注入到 admin HTML<meta name="csrf-token"> - 中间件对
/api/*的POST/PUT/DELETE/PATCH强制校验X-CSRF-Token头(/api/config/submit例外:用 Origin/Referer 同源校验,因为它是匿名公开提交接口) - 登录失败计数
login_fail_${ip}:5 次/10 分钟锁定 - 字符串比较使用
timingSafeEqual()防时序攻击
5. Settings 双层缓存
functions/api/settings.js 写设置 → 同时清除 KV settings_cache + 标记 home cache dirty;functions/index.js 读取时优先查 settings_cache(24h TTL),未命中再查 D1 并异步回填。只改 WebDAV / AI 配置(两者都不进 SETTINGS_SCHEMA)时不动这两个缓存,因为它们不影响 SSR 输出。秘密字段(apiKey / webdav_password)统一三态:null 清除、'' 保留、其他值写入。
6. 私密书签 / 分类(is_private)
- 未认证查询统一带
WHERE (is_private = 0 OR ? = 1),绑定includePrivate = isAuthenticated ? 1 : 0 - 分类
is_private = 1时,api/config/index.js会强制把站点也置为私密 - 公私两条缓存路径互不污染(参见
cacheScope与Set-Cookie iori_cache_*_stale)
7. CSP / 输入清洗
- HTML 输出统一过
lib/utils.js的escapeHTML - 所有用户提供的 URL 必须过
sanitizeUrl(仅放行 http / https) - 字体
font-family必须命中FONT_MAP白名单(getStyleStr内部强校验) - D1 一律使用参数绑定(
prepare(...).bind(...)),SQLLIKE通配符通过escapeLikePattern转义
目录速查
functions/
├── _middleware.js # 全局中间件 + 工具函数(认证 / CSRF / 限流 / 缓存 dirty 标记)
├── constants.js # SCHEMA_VERSION / HOME_CACHE_VERSION / DB_SCHEMA / FONT_MAP
├── index.js # 首页 SSR 入口
├── admin/ # /admin、/admin/login、/admin/logout
├── api/ # REST API(categories/, config/, pending/, cache/, settings.js, ai-chat.js, wallpaper.js …)
└── lib/ # 共用:card-renderer, menu-renderer, schema-migration, settings-parser, utils
public/ # Pages 静态资源(构建输出目录)
├── index.html # 首页 SSR 模板(含 {{PLACEHOLDER}})
├── admin/index.html # 后台 SPA-ish 页面
├── _headers # CSS/JS/favicon 长缓存策略
├── css/ # tailwind.css 是源;tailwind.min.css 是构建产物
└── js/ # main.js(前端) + admin-*.js(后台分模块)
scripts/
├── update-versions.js # pre-commit 自动跑:MD5 → 写 ?v=xxx
└── update-changelog.js # 根据 git log 重生成 README 的 changelog 区块
schema.sql # D1 初始建表(运行时 ensureSchemaReady 会再补 ALTER)
wrangler.toml # 已 gitignore,本地需自填 D1/KV id写代码的几条硬性约束
- 修改
public/css/tailwind.css必须npm run build:css否则上线无样式 - 任何写 D1 的接口都要
markHomeCacheDirty(env, scope),否则首页 KV 缓存会一直返回旧 HTML - 新加 setting 字段:在
lib/settings-parser.js的SETTINGS_SCHEMA里先注册,否则 SSR 读不到 - 不要在 commit 时手动改 HTML 里的
?v=参数,pre-commit hook 会改回去 - API 文件命名:动态路由用方括号(
api/config/[id].js、api/categories/[id].js) - 状态变更接口请求体走 JSON,并附带
X-CSRF-Token: <meta>头(admin HTML 已注入)
部署绑定(Cloudflare Pages → 设置 → 绑定)
| 绑定名 | 类型 | 必需 |
NAV_DB | D1 数据库(默认名 book) | ✅ |
NAV_AUTH | KV(同时存 session / CSRF / 限流计数器 / settings 缓存 / home HTML 缓存) | ✅ |
可选环境变量:ENABLE_PUBLIC_SUBMISSION、SITE_NAME、SITE_DESCRIPTION、FOOTER_TEXT、ICON_API、AI_REQUEST_DELAY。
后台凭据通过 KV 条目 admin_username / admin_password 配置;两者长度都不得超过 MAX_TIMING_SAFE_INPUT_LENGTH(4096),超长会触发登录页的配置错误提示。
中文支持
注释、用户面文案、commit message 均可使用中文,与现有风格保持一致。
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

