From 10761ccc76a876891ebb11924d5ccdb4fbde8600 Mon Sep 17 00:00:00 2001 From: khiem20tc Date: Sat, 21 Feb 2026 10:09:44 +0700 Subject: [PATCH 1/3] fix(table): handle on changing api notion responses --- src/routes/table.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/table.ts b/src/routes/table.ts index 078c84c..119d1fc 100644 --- a/src/routes/table.ts +++ b/src/routes/table.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { fetchPageById, fetchTableData, @@ -30,7 +31,7 @@ export const getTableData = async ( const tableArr: RowType[] = table.result.reducerResults.collection_group_results.blockIds.map( - (id: string) => table.recordMap.block[id] + (id: string) => table.recordMap.block[id]?.value ); const tableData = tableArr.filter( From bd0bac99dfac30b671eec5dab760a460d453a0c0 Mon Sep 17 00:00:00 2001 From: khiem20tc Date: Sat, 21 Feb 2026 10:15:03 +0700 Subject: [PATCH 2/3] build(cloudfare): config cloudfare wk --- .gitignore | 3 +++ package-lock.json | 66 ++++++++++++++++++++++++++++++++++++++++++++++ src/utils/index.ts | 10 +++---- 3 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index bad92b6..c1c32b6 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ yarn-error.log* # typescript *.tsbuildinfo + +# wrangler +wrangler.toml \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ca80804 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,66 @@ +{ + "name": "notion-api-worker", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "notion-api-worker", + "version": "0.1.0", + "dependencies": { + "hono": "^4.10.0" + }, + "devDependencies": { + "@types/node": "^24.8.1", + "prettier": "^3.3.3", + "typescript": "^5.9.3" + } + }, + "node_modules/@types/node": { + "version": "24.10.13", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/hono": { + "version": "4.12.0", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "dev": true, + "license": "MIT" + } + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 748a681..242eae9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,9 +1,9 @@ import { HandlerRequest } from "../notion-api/types.js"; export const getNotionToken = (c: HandlerRequest) => { - return ( - process.env.NOTION_TOKEN || - (c.req.header("Authorization") || "").split("Bearer ")[1] || - undefined - ); + const fromContext = (c.env as { NOTION_TOKEN?: string } | undefined)?.NOTION_TOKEN; + const fromProcess = + typeof process !== "undefined" ? process.env?.NOTION_TOKEN : undefined; + const fromHeader = (c.req.header("Authorization") || "").split("Bearer ")[1]; + return fromContext || fromProcess || fromHeader || undefined; }; From 231cda149c3c8fd67145d37cb46f014d25219b95 Mon Sep 17 00:00:00 2001 From: khiem20tc Date: Fri, 24 Jul 2026 13:33:41 +0700 Subject: [PATCH 3/3] fix(notion): fix notion api --- src/notion-api/notion.ts | 19 +++++++++++++- src/notion-api/utils.ts | 53 +++++++++++++++++++++++++++++++++++++++ src/routes/notion-page.ts | 10 +++++--- src/routes/table.ts | 25 ++++++++++++------ 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/src/notion-api/notion.ts b/src/notion-api/notion.ts index 704267b..6695a77 100644 --- a/src/notion-api/notion.ts +++ b/src/notion-api/notion.ts @@ -6,6 +6,7 @@ import { NotionSearchParamsType, NotionSearchResultsType, } from "./types.js"; +import { normalizeRecordMap } from "./utils.js"; const NOTION_API = "https://www.notion.so/api/v3"; @@ -52,6 +53,10 @@ export const fetchPageById = async (pageId: string, notionToken?: string) => { notionToken, }); + if (res?.recordMap) { + res.recordMap = normalizeRecordMap(res.recordMap); + } + return res; }; @@ -93,9 +98,11 @@ export const fetchTableData = async ( body: { collection: { id: collectionId, + ...(spaceId && { spaceId }), }, collectionView: { id: collectionViewId, + ...(spaceId && { spaceId }), }, ...queryCollectionBody, }, @@ -103,6 +110,10 @@ export const fetchTableData = async ( headers, }); + if (table?.recordMap) { + table.recordMap = normalizeRecordMap(table.recordMap); + } + return table; }; @@ -136,7 +147,7 @@ export const fetchBlocks = async ( blockList: string[], notionToken?: string ) => { - return await fetchNotionData({ + const res = await fetchNotionData({ resource: "syncRecordValues", body: { requests: blockList.map((id) => ({ @@ -147,6 +158,12 @@ export const fetchBlocks = async ( }, notionToken, }); + + if (res?.recordMap) { + res.recordMap = normalizeRecordMap(res.recordMap); + } + + return res; }; export const fetchNotionSearch = async ( diff --git a/src/notion-api/utils.ts b/src/notion-api/utils.ts index 4a4dafa..fb587e1 100644 --- a/src/notion-api/utils.ts +++ b/src/notion-api/utils.ts @@ -10,6 +10,59 @@ export const parsePageId = (id: string) => { } }; +/** + * Notion now returns records as `{ spaceId, value: { value: T, role } }`. + * Older clients expect `{ role, value: T }`. Normalize to the legacy shape. + */ +export const unwrapRecord = ( + record: T | undefined | null +): T | undefined | null => { + if (!record || typeof record !== "object") return record; + + const outer = record as any; + const nested = outer.value; + + // New format: { spaceId?, value: { value: actual, role } } + if ( + nested && + typeof nested === "object" && + nested.value && + typeof nested.value === "object" && + (nested.value.id || nested.value.schema || nested.value.type) + ) { + return { + ...outer, + role: nested.role ?? outer.role, + value: nested.value, + } as T; + } + + return record; +}; + +export const normalizeRecordMap = | undefined | null>( + recordMap: T +): T => { + if (!recordMap || typeof recordMap !== "object") return recordMap; + + const normalized: Record = { ...recordMap }; + + for (const table of Object.keys(normalized)) { + const entries = normalized[table]; + if (!entries || typeof entries !== "object" || Array.isArray(entries)) continue; + // Skip metadata keys like __version__ + if (table.startsWith("__")) continue; + + const next: Record = {}; + for (const [id, record] of Object.entries(entries)) { + next[id] = unwrapRecord(record as any) ?? record; + } + normalized[table] = next; + } + + return normalized as T; +}; + export const getNotionValue = ( val: DecorationType[], type: ColumnType, diff --git a/src/routes/notion-page.ts b/src/routes/notion-page.ts index 9b3099d..897d6fd 100644 --- a/src/routes/notion-page.ts +++ b/src/routes/notion-page.ts @@ -57,8 +57,10 @@ export async function pageRoute(c: HandlerRequest) { if (collection && collectionView) { const pendingCollections = allBlockKeys.flatMap((blockId) => { const block = allBlocks[blockId]; + const type = block.value && block.value.type; - return block.value && block.value.type === "collection_view" + // collection_view_page is a full-page database; collection_view is inline + return type === "collection_view" || type === "collection_view_page" ? [block.value.id] : []; }); @@ -66,16 +68,18 @@ export async function pageRoute(c: HandlerRequest) { for (let b of pendingCollections) { const collPage = await fetchPageById(b!, notionToken); - const coll = Object.keys(collPage.recordMap.collection).map( + const coll = Object.keys(collPage.recordMap.collection || {}).map( (k) => collPage.recordMap.collection[k] )[0]; const collView: { value: { id: CollectionType["value"]["id"] }; - } = Object.keys(collPage.recordMap.collection_view).map( + } = Object.keys(collPage.recordMap.collection_view || {}).map( (k) => collPage.recordMap.collection_view[k] )[0]; + if (!coll || !collView) continue; + const { rows, schema } = await getTableData( coll, collView.value.id, diff --git a/src/routes/table.ts b/src/routes/table.ts index 119d1fc..be3bbe2 100644 --- a/src/routes/table.ts +++ b/src/routes/table.ts @@ -20,23 +20,34 @@ export const getTableData = async ( notionToken?: string, raw?: boolean ) => { + const spaceId = + (collection as any).spaceId || + (collection.value as any)?.space_id || + undefined; + const table = await fetchTableData( collection.value.id, collectionViewId, - notionToken + notionToken, + spaceId ); const collectionRows = collection.value.schema; - const collectionColKeys = Object.keys(collectionRows); + const collectionColKeys = Object.keys(collectionRows || {}); - const tableArr: RowType[] = - table.result.reducerResults.collection_group_results.blockIds.map( - (id: string) => table.recordMap.block[id]?.value - ); + const blockIds = + table?.result?.reducerResults?.collection_group_results?.blockIds || []; + + const tableArr: RowType[] = blockIds.map( + (id: string) => table.recordMap.block[id] + ); const tableData = tableArr.filter( (b) => - b.value && b.value.properties && b.value.parent_id === collection.value.id + b && + b.value && + b.value.properties && + b.value.parent_id === collection.value.id ); type Row = { id: string; [key: string]: RowContentType };