Skip to content

Commit 1e34875

Browse files
committed
Stabilize build cache locks and filtered collection data
1 parent 560d04e commit 1e34875

16 files changed

Lines changed: 1433 additions & 530 deletions

.vitepress/config.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ export default defineConfig({
327327
items: [
328328
{ text: '开发文档首页', link: '/developer/' },
329329
{ text: '快速上手', link: '/developer/GETTING_STARTED' },
330+
{ text: '愿景与路线图', link: '/developer/VISION_ROADMAP' },
330331
{ text: '架构总览', link: '/developer/ARCHITECTURE' },
331332
{ text: '目录与模块', link: '/developer/PROJECT_STRUCTURE' },
332333
{ text: '配置体系', link: '/developer/CONFIGURATION' },
@@ -341,6 +342,7 @@ export default defineConfig({
341342
{ text: '维护者手册', link: '/developer/MAINTAINER_RUNBOOK.zh-CN' },
342343
{ text: '版本更新说明', link: '/developer/UPDATE' },
343344
{ text: '社区路线图', link: '/developer/COMMUNITY_SITE_ROADMAP' },
345+
{ text: '5.0 愿景与参与方向', link: '/developer/VISION_ROADMAP' },
344346
{ text: 'RFC', link: '/developer/rfc/' }
345347
]
346348
},

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ NotionNext 主仓库由 GitHub 组织 [notionnext-org](https://github.com/notion
140140
| 内容 | 链接 |
141141
| --- | --- |
142142
| 参与社区 | [community-participate.md](./docs/user-guide/community-participate.md) |
143+
| 5.0 愿景与路线图 | [VISION_ROADMAP.md](./docs/developer/VISION_ROADMAP.md) |
143144
| 贡献指南 | [CONTRIBUTING.zh-CN.md](./CONTRIBUTING.zh-CN.md) |
144145
| 项目治理 | [GOVERNANCE.zh-CN.md](./GOVERNANCE.zh-CN.md) |
145146
| 维护者 | [MAINTAINERS.md](./MAINTAINERS.md) |
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
5+
import fs from 'fs'
6+
import os from 'os'
7+
import path from 'path'
8+
9+
function sleep(ms) {
10+
return new Promise(resolve => setTimeout(resolve, ms))
11+
}
12+
13+
async function loadFileLockWithTempCache() {
14+
const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'notionnext-lock-'))
15+
process.env.NOTION_NEXT_NOTION_CACHE_DIR = cacheDir
16+
jest.resetModules()
17+
18+
const mod = await import('@/lib/cache/file_lock')
19+
20+
return {
21+
cacheDir,
22+
withFileLock: mod.withFileLock
23+
}
24+
}
25+
26+
describe('withFileLock', () => {
27+
afterEach(() => {
28+
delete process.env.NOTION_NEXT_NOTION_CACHE_DIR
29+
})
30+
31+
it('waits for the lock holder cache result instead of bypassing in wait mode', async () => {
32+
const { cacheDir, withFileLock } = await loadFileLockWithTempCache()
33+
let cachedValue = null
34+
let calls = 0
35+
36+
try {
37+
const holder = withFileLock(
38+
'shared-build-key',
39+
async () => {
40+
calls++
41+
await sleep(50)
42+
cachedValue = 'from-holder'
43+
return cachedValue
44+
},
45+
() => cachedValue,
46+
{
47+
timeout: 1000,
48+
staleLockMs: 5000,
49+
timeoutStrategy: 'wait',
50+
maxWaitMs: 1000
51+
}
52+
)
53+
54+
await sleep(5)
55+
56+
const waiter = withFileLock(
57+
'shared-build-key',
58+
() => {
59+
calls++
60+
return 'from-waiter'
61+
},
62+
() => cachedValue,
63+
{
64+
timeout: 10,
65+
staleLockMs: 5000,
66+
timeoutStrategy: 'wait',
67+
maxWaitMs: 1000
68+
}
69+
)
70+
71+
await expect(Promise.all([holder, waiter])).resolves.toEqual([
72+
'from-holder',
73+
'from-holder'
74+
])
75+
expect(calls).toBe(1)
76+
} finally {
77+
fs.rmSync(cacheDir, { recursive: true, force: true })
78+
}
79+
})
80+
81+
it('keeps the default timeout bypass behavior for existing callers', async () => {
82+
const { cacheDir, withFileLock } = await loadFileLockWithTempCache()
83+
let cachedValue = null
84+
let calls = 0
85+
86+
try {
87+
const holder = withFileLock(
88+
'compat-key',
89+
async () => {
90+
calls++
91+
await sleep(500)
92+
cachedValue = 'from-holder'
93+
return cachedValue
94+
},
95+
() => cachedValue,
96+
1000
97+
)
98+
99+
await sleep(5)
100+
101+
const waiter = withFileLock(
102+
'compat-key',
103+
() => {
104+
calls++
105+
return 'from-bypass'
106+
},
107+
() => cachedValue,
108+
10
109+
)
110+
111+
await expect(Promise.all([holder, waiter])).resolves.toEqual([
112+
'from-holder',
113+
'from-bypass'
114+
])
115+
expect(calls).toBe(2)
116+
} finally {
117+
fs.rmSync(cacheDir, { recursive: true, force: true })
118+
}
119+
})
120+
})

__tests__/lib/db/notion/convertInnerUrl.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
jest.mock('notion-utils', () => ({
2+
idToUuid: id =>
3+
`${id.slice(0, 8)}-${id.slice(8, 12)}-${id.slice(12, 16)}-${id.slice(
4+
16,
5+
20
6+
)}-${id.slice(20)}`
7+
}))
8+
19
import { convertInnerUrl } from '@/lib/db/notion/convertInnerUrl'
210

311
describe('convertInnerUrl', () => {

__tests__/lib/notion-data-format.test.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import getAllPageIds from '@/lib/db/notion/getAllPageIds'
2+
import { filterCollectionViewData } from '@/lib/db/notion/filterCollectionViewData'
23
import {
34
adapterNotionBlockMap,
45
normalizeNotionBlockType
@@ -170,6 +171,95 @@ describe('Notion data format compatibility', () => {
170171
expect(pageIds).toEqual(['page_1', 'page_2'])
171172
})
172173

174+
it('filters embedded collection query results by selected view filters', () => {
175+
const blockMap = {
176+
block: {
177+
published_page: {
178+
value: {
179+
id: 'published_page',
180+
type: 'page',
181+
properties: {
182+
type: [['Post']],
183+
status: [['Published']]
184+
}
185+
}
186+
},
187+
draft_page: {
188+
value: {
189+
id: 'draft_page',
190+
type: 'page',
191+
properties: {
192+
type: [['Post']],
193+
status: [['Draft']]
194+
}
195+
}
196+
},
197+
invisible_page: {
198+
value: {
199+
id: 'invisible_page',
200+
type: 'page',
201+
properties: {
202+
type: [['Post']],
203+
status: [['Invisible']]
204+
}
205+
}
206+
}
207+
},
208+
collection_view: {
209+
view_1: {
210+
value: {
211+
value: {
212+
id: 'view_1',
213+
page_sort: ['published_page', 'draft_page', 'invisible_page'],
214+
format: {
215+
collection_pointer: { id: 'collection_1' },
216+
property_filters: [
217+
{
218+
filter: {
219+
property: 'type',
220+
filter: {
221+
operator: 'enum_is',
222+
value: { type: 'exact', value: 'Post' }
223+
}
224+
}
225+
},
226+
{
227+
filter: {
228+
property: 'status',
229+
filter: {
230+
operator: 'enum_is',
231+
value: { type: 'exact', value: 'Published' }
232+
}
233+
}
234+
}
235+
]
236+
}
237+
}
238+
}
239+
}
240+
},
241+
collection_query: {
242+
collection_1: {
243+
view_1: {
244+
collection_group_results: {
245+
blockIds: ['published_page', 'draft_page', 'invisible_page']
246+
}
247+
}
248+
}
249+
}
250+
}
251+
252+
filterCollectionViewData(blockMap)
253+
254+
expect(
255+
blockMap.collection_query.collection_1.view_1.collection_group_results
256+
.blockIds
257+
).toEqual(['published_page'])
258+
expect(blockMap.collection_view.view_1.value.value.page_sort).toEqual([
259+
'published_page'
260+
])
261+
})
262+
173263
it('normalizes nested blocks and strips crdt fields before rendering', () => {
174264
const formatted = formatNotionBlock({
175265
page_1: {

docs/developer/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
| 主题 | 中文 | English |
1111
| --- | --- | --- |
1212
| 快速上手 | [GETTING_STARTED.md](./GETTING_STARTED.md) | [GETTING_STARTED.en.md](./GETTING_STARTED.en.md) |
13+
| 愿景与路线图 | [VISION_ROADMAP.md](./VISION_ROADMAP.md) ||
1314
| 架构总览 | [ARCHITECTURE.md](./ARCHITECTURE.md) | [ARCHITECTURE.en.md](./ARCHITECTURE.en.md) |
1415
| 目录与模块 | [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) | [PROJECT_STRUCTURE.en.md](./PROJECT_STRUCTURE.en.md) |
1516
| 配置体系 | [CONFIGURATION.md](./CONFIGURATION.md) | [CONFIGURATION.en.md](./CONFIGURATION.en.md) |
@@ -21,6 +22,7 @@
2122
| 主题迁移 | [THEME_MIGRATION_GUIDE.zh-CN.md](./THEME_MIGRATION_GUIDE.zh-CN.md) | [THEME_MIGRATION_GUIDE.md](./THEME_MIGRATION_GUIDE.md) |
2223
| 版本更新说明 | [UPDATE.md](./UPDATE.md) ||
2324
| 社区路线图 | [COMMUNITY_SITE_ROADMAP.md](./COMMUNITY_SITE_ROADMAP.md) ||
25+
| 5.0 愿景与参与方向 | [VISION_ROADMAP.md](./VISION_ROADMAP.md) ||
2426

2527
仓库贡献总则:[CONTRIBUTING.zh-CN.md](../../CONTRIBUTING.zh-CN.md) · 用户向 [参与社区](../user-guide/community-participate.md) · [MAINTAINERS.md](../../MAINTAINERS.md)
2628

docs/developer/VISION_ROADMAP.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# NotionNext 愿景与路线图
2+
3+
NotionNext 的长期目标,不是把 Notion 简单包装成另一个博客模板,而是让它成为一个稳定、可演进、可由社区共同维护的内容发布系统。
4+
5+
Notion 负责内容生产、资料整理和团队协作;NotionNext 负责公开访问、主题展示、SEO、部署、性能和长期运营。两者连接得越自然,站长需要手工维护的地方就越少,内容也越容易持续沉淀。
6+
7+
## 5.0 方向
8+
9+
5.0 不应只是“多几个功能”的版本。它更应该回答一个问题:一个普通站长如何在不被 Git、部署平台和 Notion 数据结构反复打断的情况下,长期维护自己的站点?
10+
11+
围绕这个目标,后续路线会优先关注这些方向。
12+
13+
### 1. 降低使用门槛
14+
15+
NotionNext 仍然是开源项目,但部署、升级、排错不能只服务熟悉工程流程的人。未来需要继续减少这些摩擦:
16+
17+
- 更清楚的升级路径,降低分支、Fork、Pull Request 带来的理解成本。
18+
- 更稳定的构建和缓存策略,减少“代码已经更新但网站仍是旧状态”的困惑。
19+
- 更友好的错误提示和自检工具,让站长能先判断是配置问题、缓存问题还是代码问题。
20+
- 更完整的文档,把常见真实场景沉淀成可复用的排查步骤。
21+
22+
### 2. 更完整地承接 Notion 数据库能力
23+
24+
Notion 的数据库、视图、筛选和排序是许多站长真正依赖的能力。NotionNext 应尽量理解这些结构,而不是要求用户额外维护大量重复标签。
25+
26+
近期已经开始补齐嵌入数据库视图筛选等兼容问题。长期可以继续推进:
27+
28+
- 更稳定地读取 Notion 数据库视图筛选、排序和分组结果。
29+
- 支持更多 Notion 属性类型和多条件组合。
30+
- 降低“为了网站展示而重复造标签”的人工维护成本。
31+
- 明确哪些 Notion 行为可以可靠支持,哪些需要降级或文档说明。
32+
33+
### 3. 主题、版型和内容模块化
34+
35+
NotionNext 已经有多套主题,但长期维护的关键不是主题数量,而是主题之间能否共享稳定的基础能力。
36+
37+
后续可以逐步沉淀:
38+
39+
- 更清晰的主题接口和配置边界。
40+
- 可复用的文章卡片、列表、图库、导航、筛选页面等内容模块。
41+
- 面向不同场景的版型能力,例如案例库、作品集、知识库、产品页和导航站。
42+
- 更容易迁移和维护的主题文档、预览图与测试方式。
43+
44+
### 4. 更安全的升级体验
45+
46+
很多站长会在 Fork 后修改配置、样式或主题。项目需要承认这种现实,而不是假设所有人都只使用原始代码。
47+
48+
未来需要继续优化:
49+
50+
- 把站长自定义配置和核心代码改动尽量分离。
51+
- 让版本升级更容易判断哪些冲突必须人工处理。
52+
- 对常见下游分支维护方式提供更明确的建议。
53+
- 在 CI、测试和发布流程中尽早发现破坏性变更。
54+
55+
### 5. 让更多角色能参与
56+
57+
开源项目不只需要核心代码贡献者。NotionNext 的真实使用场景很多,站长、设计师、文档维护者、测试者都能帮助项目变好。
58+
59+
我们希望把任务拆得更小、更清楚,让不同经验的人都能找到入口。
60+
61+
## 可以如何参与
62+
63+
如果你还不熟悉代码,可以从这些事情开始:
64+
65+
- 复现问题:提供网站链接、Notion 页面、分支名、部署平台和截图。
66+
- 测试修复:在自己的站点或测试分支验证 PR 是否解决真实问题。
67+
- 补充文档:把踩过的坑整理成教程、FAQ 或排查步骤。
68+
- 改进体验:指出哪些概念、配置或升级流程对非开发者不友好。
69+
- 贡献主题素材:补主题截图、使用说明、适用场景和配置示例。
70+
71+
如果你已经能改代码,可以优先关注:
72+
73+
- Notion 数据结构兼容性和测试用例。
74+
- 主题公共组件和版型模块。
75+
- 构建、缓存、部署和升级稳定性。
76+
- 文档站、CI、发布流程和维护工具。
77+
- 带有 `good first issue``help wanted` 标签的问题。
78+
79+
## 近期适合拆分的任务
80+
81+
这些方向适合逐步拆成 Issue、Discussion 或 RFC:
82+
83+
| 方向 | 可拆任务 |
84+
| --- | --- |
85+
| Notion 数据库能力 | 补齐视图筛选、排序、状态分组、多选、日期、关系属性等兼容测试 |
86+
| 图文卡片版型 | 为标签页、分类页或指定数据库视图提供稳定的卡片展示方案 |
87+
| 升级体验 | 梳理 Fork 用户如何同步上游、如何处理分支和部署缓存 |
88+
| 主题模块化 | 抽象文章卡片、列表、图库、筛选页等跨主题可复用组件 |
89+
| 文档维护 | 把真实用户反馈整理成 FAQ、排查手册和升级指南 |
90+
| 测试与 CI | 为关键数据格式、构建缓存、主题渲染补充小范围自动化验证 |
91+
92+
## 协作原则
93+
94+
- 先讨论真实场景,再设计功能。
95+
- 大改动先写 RFC,小修复保持 PR 小而清楚。
96+
- 优先补测试和文档,减少后续维护成本。
97+
- 允许非代码贡献存在价值,真实反馈本身就是项目路线的一部分。
98+
99+
NotionNext 的 5.0 不是某个人一次性完成的大版本,而是社区持续把复杂度收拢、把门槛降下来的过程。每一次可复现的问题、一次文档修正、一个小范围 PR,都会让后来的站长少踩一个坑。

docs/developer/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ NotionNext 文档不再把“站长教程”和“开发者文档”做成两个
99
| 主题 | 中文 | English |
1010
| --- | --- | --- |
1111
| 快速上手 | [GETTING_STARTED.md](./GETTING_STARTED.md) | [GETTING_STARTED.en.md](./GETTING_STARTED.en.md) |
12+
| 愿景与路线图 | [VISION_ROADMAP.md](./VISION_ROADMAP.md) ||
1213
| 架构总览 | [ARCHITECTURE.md](./ARCHITECTURE.md) | [ARCHITECTURE.en.md](./ARCHITECTURE.en.md) |
1314
| 目录与模块 | [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) | [PROJECT_STRUCTURE.en.md](./PROJECT_STRUCTURE.en.md) |
1415
| 配置体系 | [CONFIGURATION.md](./CONFIGURATION.md) | [CONFIGURATION.en.md](./CONFIGURATION.en.md) |
@@ -30,5 +31,6 @@ NotionNext 文档不再把“站长教程”和“开发者文档”做成两个
3031
| --- | --- |
3132
| 版本更新说明 | [UPDATE.md](./UPDATE.md) |
3233
| 社区路线图 | [COMMUNITY_SITE_ROADMAP.md](./COMMUNITY_SITE_ROADMAP.md) |
34+
| 5.0 愿景与参与方向 | [VISION_ROADMAP.md](./VISION_ROADMAP.md) |
3335
| 项目治理 | [GOVERNANCE.zh-CN.md](https://github.com/notionnext-org/NotionNext/blob/main/GOVERNANCE.zh-CN.md) · [GOVERNANCE.md](https://github.com/notionnext-org/NotionNext/blob/main/GOVERNANCE.md) |
3436
| 仓库贡献总则 | [CONTRIBUTING.zh-CN.md](https://github.com/notionnext-org/NotionNext/blob/main/CONTRIBUTING.zh-CN.md) |

0 commit comments

Comments
 (0)