@notionx/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/dist/admin/index.d.ts +137 -0
  2. package/dist/admin/index.js +206 -0
  3. package/dist/admin/index.js.map +1 -0
  4. package/dist/admin/pages/index.d.ts +324 -0
  5. package/dist/admin/pages/index.js +827 -0
  6. package/dist/admin/pages/index.js.map +1 -0
  7. package/dist/auth/auth-pages/forgot-password.d.ts +20 -0
  8. package/dist/auth/auth-pages/forgot-password.js +70 -0
  9. package/dist/auth/auth-pages/forgot-password.js.map +1 -0
  10. package/dist/auth/auth-pages/index.d.ts +6 -0
  11. package/dist/auth/auth-pages/index.js +342 -0
  12. package/dist/auth/auth-pages/index.js.map +1 -0
  13. package/dist/auth/auth-pages/login.d.ts +30 -0
  14. package/dist/auth/auth-pages/login.js +125 -0
  15. package/dist/auth/auth-pages/login.js.map +1 -0
  16. package/dist/auth/auth-pages/register.d.ts +17 -0
  17. package/dist/auth/auth-pages/register.js +81 -0
  18. package/dist/auth/auth-pages/register.js.map +1 -0
  19. package/dist/auth/auth-pages/reset-password.d.ts +18 -0
  20. package/dist/auth/auth-pages/reset-password.js +72 -0
  21. package/dist/auth/auth-pages/reset-password.js.map +1 -0
  22. package/dist/auth/index.d.ts +72 -0
  23. package/dist/auth/index.js +1011 -0
  24. package/dist/auth/index.js.map +1 -0
  25. package/dist/auth/passwords.d.ts +6 -0
  26. package/dist/auth/passwords.js +79 -0
  27. package/dist/auth/passwords.js.map +1 -0
  28. package/dist/auth/rate-limit.d.ts +28 -0
  29. package/dist/auth/rate-limit.js +245 -0
  30. package/dist/auth/rate-limit.js.map +1 -0
  31. package/dist/auth/routes/google-callback.d.ts +6 -0
  32. package/dist/auth/routes/google-callback.js +404 -0
  33. package/dist/auth/routes/google-callback.js.map +1 -0
  34. package/dist/auth/routes/google.d.ts +6 -0
  35. package/dist/auth/routes/google.js +250 -0
  36. package/dist/auth/routes/google.js.map +1 -0
  37. package/dist/auth/routes/index.d.ts +22 -0
  38. package/dist/auth/routes/index.js +619 -0
  39. package/dist/auth/routes/index.js.map +1 -0
  40. package/dist/auth/routes/verify-email.d.ts +6 -0
  41. package/dist/auth/routes/verify-email.js +317 -0
  42. package/dist/auth/routes/verify-email.js.map +1 -0
  43. package/dist/auth/routes/viewer.d.ts +6 -0
  44. package/dist/auth/routes/viewer.js +372 -0
  45. package/dist/auth/routes/viewer.js.map +1 -0
  46. package/dist/auth/session.d.ts +9 -0
  47. package/dist/auth/session.js +1 -0
  48. package/dist/auth/session.js.map +1 -0
  49. package/dist/auth/turnstile.d.ts +20 -0
  50. package/dist/auth/turnstile.js +301 -0
  51. package/dist/auth/turnstile.js.map +1 -0
  52. package/dist/auth/user-session.d.ts +42 -0
  53. package/dist/auth/user-session.js +419 -0
  54. package/dist/auth/user-session.js.map +1 -0
  55. package/dist/auth/users.d.ts +112 -0
  56. package/dist/auth/users.js +558 -0
  57. package/dist/auth/users.js.map +1 -0
  58. package/dist/bootstrap-CN2g76M6.d.ts +67 -0
  59. package/dist/cache/index.d.ts +6 -0
  60. package/dist/cache/index.js +47 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/content/admin-summary.d.ts +24 -0
  63. package/dist/content/admin-summary.js +36 -0
  64. package/dist/content/admin-summary.js.map +1 -0
  65. package/dist/content/index.d.ts +9 -0
  66. package/dist/content/index.js +473 -0
  67. package/dist/content/index.js.map +1 -0
  68. package/dist/content/models.d.ts +69 -0
  69. package/dist/content/models.js +24 -0
  70. package/dist/content/models.js.map +1 -0
  71. package/dist/content/prewarm.d.ts +28 -0
  72. package/dist/content/prewarm.js +56 -0
  73. package/dist/content/prewarm.js.map +1 -0
  74. package/dist/content/revalidate.d.ts +37 -0
  75. package/dist/content/revalidate.js +170 -0
  76. package/dist/content/revalidate.js.map +1 -0
  77. package/dist/content/search-index.d.ts +54 -0
  78. package/dist/content/search-index.js +172 -0
  79. package/dist/content/search-index.js.map +1 -0
  80. package/dist/content/search.d.ts +8 -0
  81. package/dist/content/search.js +57 -0
  82. package/dist/content/search.js.map +1 -0
  83. package/dist/doctor/cli.d.ts +1 -0
  84. package/dist/doctor/cli.js +360 -0
  85. package/dist/doctor/cli.js.map +1 -0
  86. package/dist/doctor/index.d.ts +139 -0
  87. package/dist/doctor/index.js +289 -0
  88. package/dist/doctor/index.js.map +1 -0
  89. package/dist/email/index.d.ts +38 -0
  90. package/dist/email/index.js +126 -0
  91. package/dist/email/index.js.map +1 -0
  92. package/dist/env-C5qu-0R-.d.ts +35 -0
  93. package/dist/hooks/index.d.ts +2 -0
  94. package/dist/hooks/index.js +1 -0
  95. package/dist/hooks/index.js.map +1 -0
  96. package/dist/i18n/index.d.ts +26 -0
  97. package/dist/i18n/index.js +73 -0
  98. package/dist/i18n/index.js.map +1 -0
  99. package/dist/index.d.ts +8 -0
  100. package/dist/index.js +1281 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/internal/admin/index.d.ts +75 -0
  103. package/dist/internal/admin/index.js +365 -0
  104. package/dist/internal/admin/index.js.map +1 -0
  105. package/dist/media/index.d.ts +24 -0
  106. package/dist/media/index.js +86 -0
  107. package/dist/media/index.js.map +1 -0
  108. package/dist/media/routes/index.d.ts +1 -0
  109. package/dist/media/routes/index.js +585 -0
  110. package/dist/media/routes/index.js.map +1 -0
  111. package/dist/media/routes/notion-media.d.ts +19 -0
  112. package/dist/media/routes/notion-media.js +588 -0
  113. package/dist/media/routes/notion-media.js.map +1 -0
  114. package/dist/middleware.d.ts +95 -0
  115. package/dist/middleware.js +79 -0
  116. package/dist/middleware.js.map +1 -0
  117. package/dist/notion/block-text.d.ts +5 -0
  118. package/dist/notion/block-text.js +37 -0
  119. package/dist/notion/block-text.js.map +1 -0
  120. package/dist/notion/blocks.d.ts +24 -0
  121. package/dist/notion/blocks.js +46 -0
  122. package/dist/notion/blocks.js.map +1 -0
  123. package/dist/notion/client.d.ts +7 -0
  124. package/dist/notion/client.js +13 -0
  125. package/dist/notion/client.js.map +1 -0
  126. package/dist/notion/config.d.ts +25 -0
  127. package/dist/notion/config.js +147 -0
  128. package/dist/notion/config.js.map +1 -0
  129. package/dist/notion/content-cache.d.ts +45 -0
  130. package/dist/notion/content-cache.js +166 -0
  131. package/dist/notion/content-cache.js.map +1 -0
  132. package/dist/notion/generic-source.d.ts +61 -0
  133. package/dist/notion/generic-source.js +408 -0
  134. package/dist/notion/generic-source.js.map +1 -0
  135. package/dist/notion/index.d.ts +13 -0
  136. package/dist/notion/index.js +1278 -0
  137. package/dist/notion/index.js.map +1 -0
  138. package/dist/notion/mappers.d.ts +1 -0
  139. package/dist/notion/mappers.js +152 -0
  140. package/dist/notion/mappers.js.map +1 -0
  141. package/dist/notion/media.d.ts +22 -0
  142. package/dist/notion/media.js +209 -0
  143. package/dist/notion/media.js.map +1 -0
  144. package/dist/notion/property-mappers.d.ts +24 -0
  145. package/dist/notion/property-mappers.js +152 -0
  146. package/dist/notion/property-mappers.js.map +1 -0
  147. package/dist/notion/routes/index.d.ts +8 -0
  148. package/dist/notion/routes/index.js +428 -0
  149. package/dist/notion/routes/index.js.map +1 -0
  150. package/dist/notion/routes/webhook.d.ts +98 -0
  151. package/dist/notion/routes/webhook.js +428 -0
  152. package/dist/notion/routes/webhook.js.map +1 -0
  153. package/dist/notion/types.d.ts +152 -0
  154. package/dist/notion/types.js +1 -0
  155. package/dist/notion/types.js.map +1 -0
  156. package/dist/notion/webhook.d.ts +83 -0
  157. package/dist/notion/webhook.js +490 -0
  158. package/dist/notion/webhook.js.map +1 -0
  159. package/dist/platform/capabilities.d.ts +34 -0
  160. package/dist/platform/capabilities.js +42 -0
  161. package/dist/platform/capabilities.js.map +1 -0
  162. package/dist/platform/current.d.ts +13 -0
  163. package/dist/platform/current.js +181 -0
  164. package/dist/platform/current.js.map +1 -0
  165. package/dist/platform/index.d.ts +5 -0
  166. package/dist/platform/index.js +269 -0
  167. package/dist/platform/index.js.map +1 -0
  168. package/dist/platform/runtime.d.ts +118 -0
  169. package/dist/platform/runtime.js +160 -0
  170. package/dist/platform/runtime.js.map +1 -0
  171. package/dist/platform/selection.d.ts +10 -0
  172. package/dist/platform/selection.js +22 -0
  173. package/dist/platform/selection.js.map +1 -0
  174. package/dist/storage/index.d.ts +17 -0
  175. package/dist/storage/index.js +218 -0
  176. package/dist/storage/index.js.map +1 -0
  177. package/dist/storage/routes/cdn.d.ts +19 -0
  178. package/dist/storage/routes/cdn.js +289 -0
  179. package/dist/storage/routes/cdn.js.map +1 -0
  180. package/dist/storage/routes/files.d.ts +27 -0
  181. package/dist/storage/routes/files.js +216 -0
  182. package/dist/storage/routes/files.js.map +1 -0
  183. package/dist/storage/routes/index.d.ts +2 -0
  184. package/dist/storage/routes/index.js +352 -0
  185. package/dist/storage/routes/index.js.map +1 -0
  186. package/dist/types-BsAcZSNX.d.ts +94 -0
  187. package/dist/types.d.ts +78 -0
  188. package/dist/types.js +1 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/util/index.d.ts +18 -0
  191. package/dist/util/index.js +48 -0
  192. package/dist/util/index.js.map +1 -0
  193. package/dist/worker/index.d.ts +6 -0
  194. package/dist/worker/index.js +1026 -0
  195. package/dist/worker/index.js.map +1 -0
  196. package/dist/worker/routes/content-prewarm.d.ts +34 -0
  197. package/dist/worker/routes/content-prewarm.js +38 -0
  198. package/dist/worker/routes/content-prewarm.js.map +1 -0
  199. package/dist/worker/routes/content-revalidate.d.ts +81 -0
  200. package/dist/worker/routes/content-revalidate.js +64 -0
  201. package/dist/worker/routes/content-revalidate.js.map +1 -0
  202. package/dist/worker/routes/health.d.ts +14 -0
  203. package/dist/worker/routes/health.js +278 -0
  204. package/dist/worker/routes/health.js.map +1 -0
  205. package/dist/worker/routes/index.d.ts +6 -0
  206. package/dist/worker/routes/index.js +373 -0
  207. package/dist/worker/routes/index.js.map +1 -0
  208. package/package.json +124 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/notion/media.ts"],"sourcesContent":["import type { NotionBlock, NotionFileSource, NotionPageLike } from \"./types\";\n\ntype FileLike = {\n type?: string;\n external?: { url?: string };\n file?: { url?: string; expiry_time?: string };\n name?: string;\n};\n\nfunction stripLeadingSlash(value: string) {\n return value.startsWith(\"/\") ? value.slice(1) : value;\n}\n\nfunction encodePathPart(value: string) {\n return encodeURIComponent(stripLeadingSlash(value));\n}\n\nfunction appendVersion(path: string, version?: string) {\n const value = String(version ?? \"\").trim();\n if (!value) return path;\n return `${path}?${new URLSearchParams({ v: value })}`;\n}\n\nfunction blockVersion(block: NotionBlock): string | undefined {\n return typeof block.last_edited_time === \"string\"\n ? block.last_edited_time\n : undefined;\n}\n\nexport function notionPageCoverMediaPath(pageId: string): string {\n return `/api/notion/media/page/${encodePathPart(pageId)}/cover`;\n}\n\nexport function notionPagePropertyMediaPath(\n pageId: string,\n propertyName: string\n): string {\n return `/api/notion/media/page/${encodePathPart(pageId)}/property/${encodePathPart(propertyName)}`;\n}\n\nexport function notionBlockMediaPath(blockId: string): string {\n return `/api/notion/media/block/${encodePathPart(blockId)}`;\n}\n\nexport function normalizeNotionFileSource(input: unknown): NotionFileSource | null {\n const file = input as FileLike | null | undefined;\n if (!file || typeof file !== \"object\") return null;\n\n if (file.type === \"external\") {\n const url = String(file.external?.url ?? \"\").trim();\n return url ? { type: \"external\", url } : null;\n }\n\n if (file.type === \"file\") {\n const url = String(file.file?.url ?? \"\").trim();\n if (!url) return null;\n return {\n type: \"file\",\n url,\n expiryTime: String(file.file?.expiry_time ?? \"\").trim() || null,\n };\n }\n\n return null;\n}\n\nexport function resolveNotionFileUrl(input: unknown): string | null {\n return normalizeNotionFileSource(input)?.url ?? null;\n}\n\nexport function isNotionHostedFile(input: unknown): boolean {\n return normalizeNotionFileSource(input)?.type === \"file\";\n}\n\nexport function pickFirstFilesPropertyValue(property: unknown): unknown | null {\n const value = property as { type?: string; files?: unknown[] } | null | undefined;\n if (!value || value.type !== \"files\" || !Array.isArray(value.files)) {\n return null;\n }\n return value.files[0] ?? null;\n}\n\nexport function pickPageCoverFile(page: NotionPageLike): unknown | null {\n return page.cover ?? null;\n}\n\nexport function coverImageUrlForPage(\n page: NotionPageLike,\n coverPropertyName = \"Cover\"\n): string | null {\n const propertyFile = pickFirstFilesPropertyValue(\n page.properties?.[coverPropertyName]\n );\n const propertySource = normalizeNotionFileSource(propertyFile);\n if (propertySource) {\n return appendVersion(\n notionPagePropertyMediaPath(page.id, coverPropertyName),\n page.last_edited_time\n );\n }\n\n const coverSource = normalizeNotionFileSource(pickPageCoverFile(page));\n if (coverSource) {\n return appendVersion(notionPageCoverMediaPath(page.id), page.last_edited_time);\n }\n\n return null;\n}\n\nexport function fileObjectForMediaBlock(block: NotionBlock): unknown | null {\n const typed = block[block.type] as Record<string, unknown> | undefined;\n if (!typed || typeof typed !== \"object\") return null;\n\n if (\n block.type === \"image\" ||\n block.type === \"video\" ||\n block.type === \"file\" ||\n block.type === \"pdf\" ||\n block.type === \"audio\"\n ) {\n return typed;\n }\n\n return null;\n}\n\nexport function mediaUrlForBlock(block: NotionBlock): string | null {\n const source = normalizeNotionFileSource(fileObjectForMediaBlock(block));\n if (!source) return null;\n if (source.type === \"external\" && block.type !== \"image\") {\n return source.url;\n }\n return appendVersion(notionBlockMediaPath(block.id), blockVersion(block));\n}\n\nexport function firstImageUrlFromBlocks(blocks: NotionBlock[]): string | null {\n for (const block of blocks) {\n if (block.type === \"image\") {\n const imageUrl = mediaUrlForBlock(block);\n if (imageUrl) return imageUrl;\n }\n\n const nested = block.children?.length\n ? firstImageUrlFromBlocks(block.children)\n : null;\n if (nested) return nested;\n }\n\n return null;\n}\n\nexport function publicMediaBlockForApi(block: NotionBlock): NotionBlock {\n const value = block[block.type];\n const source = normalizeNotionFileSource(value);\n const children = block.children?.map(publicMediaBlockForApi);\n\n if (!source) {\n return children ? { ...block, children } : { ...block };\n }\n\n const path = appendVersion(notionBlockMediaPath(block.id), blockVersion(block));\n const publicValue =\n source.type === \"external\"\n ? block.type === \"image\"\n ? {\n ...(value as Record<string, unknown>),\n external: { url: path },\n }\n : value\n : {\n ...(value as Record<string, unknown>),\n file: {\n url: path,\n expiry_time: null,\n },\n };\n\n return {\n ...block,\n [block.type]: publicValue,\n ...(children ? { children } : {}),\n };\n}\n\nexport function gatedMediaBlockForApi(\n block: NotionBlock,\n options?: { movieId?: string }\n): NotionBlock {\n const value = block[block.type];\n const source = normalizeNotionFileSource(value);\n const children = block.children?.map((child) =>\n gatedMediaBlockForApi(child, options)\n );\n\n if (block.type !== \"video\" || !source) {\n const publicBlock = publicMediaBlockForApi(block);\n return children ? { ...publicBlock, children } : publicBlock;\n }\n\n const gatedValue: Record<string, unknown> = {\n ...(value as Record<string, unknown>),\n gated: true,\n access_url: options?.movieId\n ? `/api/movies/${encodePathPart(options.movieId)}/video/${encodePathPart(block.id)}`\n : null,\n };\n\n if (source.type === \"external\") {\n gatedValue.external = { url: null };\n } else {\n gatedValue.file = {\n url: null,\n expiry_time: null,\n };\n }\n\n return {\n ...block,\n video: gatedValue,\n ...(children ? { children } : {}),\n };\n}\n\nexport function isDirectVideoUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return /\\.(mp4|webm|mov|m4v)(?:$|\\?)/i.test(parsed.pathname);\n } catch {\n return false;\n }\n}\n\nexport function videoEmbedUrl(url: string): string | null {\n try {\n const parsed = new URL(url);\n const host = parsed.hostname.replace(/^www\\./, \"\");\n\n if (host === \"youtube.com\" || host === \"m.youtube.com\") {\n const id = parsed.searchParams.get(\"v\");\n return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"youtu.be\") {\n const id = parsed.pathname.split(\"/\").filter(Boolean)[0];\n return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"youtube-nocookie.com\") {\n return parsed.toString();\n }\n\n if (host === \"vimeo.com\") {\n const id = parsed.pathname.split(\"/\").filter(Boolean)[0];\n return id ? `https://player.vimeo.com/video/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"player.vimeo.com\") {\n return parsed.toString();\n }\n } catch {\n return null;\n }\n\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,24 @@
1
+ type PropertyMap = Record<string, unknown>;
2
+ declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ declare function getFirstTitleProperty(properties: PropertyMap): string;
4
+ declare function getRichTextProperty(properties: PropertyMap, key: string): string;
5
+ declare function getDateProperty(properties: PropertyMap, key: string): string;
6
+ declare function getFirstDateProperty(properties: PropertyMap): string;
7
+ declare function getSelectProperty(properties: PropertyMap, key: string): string;
8
+ declare function getCheckboxProperty(properties: PropertyMap, key: string): boolean;
9
+ declare function getRelationPageIds(properties: PropertyMap, key: string): string[];
10
+ declare function getTagsProperty(properties: PropertyMap, key: string): string[];
11
+ declare function getFirstTagsProperty(properties: PropertyMap): string[];
12
+ declare function getAuthorProperty(properties: PropertyMap, key: string): string;
13
+ declare function getFirstPeopleProperty(properties: PropertyMap): string;
14
+ declare function pickPublishedFlag(properties: PropertyMap): boolean;
15
+ declare function pickDescriptionFallback(description: string, title: string): string;
16
+ declare function isValidPublicSlug(slug: string): boolean;
17
+ declare function notionPageEditUrl(pageId: string, editBaseUrl?: string): string;
18
+ /**
19
+ * Normalize a Notion page id (with or without dashes) to a compact lowercase
20
+ * string. Used as a stable identifier in URLs and cache keys.
21
+ */
22
+ declare function compactNotionId(id: string): string;
23
+
24
+ export { compactNotionId, getAuthorProperty, getCheckboxProperty, getDateProperty, getFirstDateProperty, getFirstPeopleProperty, getFirstTagsProperty, getFirstTitleProperty, getRelationPageIds, getRichTextProperty, getSelectProperty, getTagsProperty, isRecord, isValidPublicSlug, notionPageEditUrl, pickDescriptionFallback, pickPublishedFlag };
@@ -0,0 +1,152 @@
1
+ // src/notion/property-mappers.ts
2
+ function isRecord(value) {
3
+ return Boolean(value && typeof value === "object");
4
+ }
5
+ function getPlainText(parts) {
6
+ if (!Array.isArray(parts)) return "";
7
+ return parts.map((part) => part.plain_text ?? "").join("").trim();
8
+ }
9
+ function getProperty(properties, key) {
10
+ return properties[key];
11
+ }
12
+ function firstPropertyOfType(properties, type) {
13
+ return Object.values(properties).find(
14
+ (property) => isRecord(property) && property.type === type
15
+ );
16
+ }
17
+ function getFirstTitleProperty(properties) {
18
+ const property = firstPropertyOfType(properties, "title");
19
+ return property ? getPlainText(property.title) : "";
20
+ }
21
+ function getRichTextProperty(properties, key) {
22
+ const property = getProperty(properties, key);
23
+ if (!property) return "";
24
+ if (property.type === "title") return getPlainText(property.title);
25
+ if (property.type === "rich_text") return getPlainText(property.rich_text);
26
+ if (property.type === "url") return String(property.url ?? "").trim();
27
+ if (property.type === "email") return String(property.email ?? "").trim();
28
+ if (property.type === "phone_number") {
29
+ return String(property.phone_number ?? "").trim();
30
+ }
31
+ return "";
32
+ }
33
+ function getDateProperty(properties, key) {
34
+ const property = getProperty(properties, key);
35
+ if (property?.type !== "date") return "";
36
+ const date = property.date;
37
+ return String(date?.start ?? "").trim();
38
+ }
39
+ function getFirstDateProperty(properties) {
40
+ const property = firstPropertyOfType(properties, "date");
41
+ if (!property) return "";
42
+ const date = property.date;
43
+ return String(date?.start ?? "").trim();
44
+ }
45
+ function getSelectProperty(properties, key) {
46
+ const property = getProperty(properties, key);
47
+ if (property?.type !== "select") return "";
48
+ const select = property.select;
49
+ return String(select?.name ?? "").trim();
50
+ }
51
+ function getCheckboxProperty(properties, key) {
52
+ const property = getProperty(properties, key);
53
+ if (property?.type !== "checkbox") return false;
54
+ return Boolean(property.checkbox);
55
+ }
56
+ function getRelationPageIds(properties, key) {
57
+ const property = getProperty(properties, key);
58
+ if (property?.type !== "relation" || !Array.isArray(property.relation)) {
59
+ return [];
60
+ }
61
+ return property.relation.map((item) => String(item.id ?? "").trim()).filter(Boolean);
62
+ }
63
+ function getTagsProperty(properties, key) {
64
+ const property = getProperty(properties, key);
65
+ if (property?.type === "multi_select" && Array.isArray(property.multi_select)) {
66
+ return property.multi_select.map((item) => String(item.name ?? "").trim()).filter(Boolean);
67
+ }
68
+ if (property?.type === "select") {
69
+ const select = property.select;
70
+ const name = String(select?.name ?? "").trim();
71
+ return name ? [name] : [];
72
+ }
73
+ return [];
74
+ }
75
+ function getFirstTagsProperty(properties) {
76
+ const multiSelect = firstPropertyOfType(properties, "multi_select");
77
+ if (multiSelect && Array.isArray(multiSelect.multi_select)) {
78
+ return multiSelect.multi_select.map((item) => String(item.name ?? "").trim()).filter(Boolean);
79
+ }
80
+ const select = firstPropertyOfType(properties, "select");
81
+ const name = String(select?.select?.name ?? "").trim();
82
+ return name ? [name] : [];
83
+ }
84
+ function getAuthorProperty(properties, key) {
85
+ const property = getProperty(properties, key);
86
+ if (!property) return "";
87
+ if (property.type === "people" && Array.isArray(property.people)) {
88
+ return property.people.map(
89
+ (person) => String(person.name ?? person.person?.email ?? "").trim()
90
+ ).filter(Boolean).join(", ");
91
+ }
92
+ return getRichTextProperty(properties, key);
93
+ }
94
+ function getFirstPeopleProperty(properties) {
95
+ const property = firstPropertyOfType(properties, "people");
96
+ if (!property || !Array.isArray(property.people)) return "";
97
+ return property.people.map(
98
+ (person) => String(person.name ?? person.person?.email ?? "").trim()
99
+ ).filter(Boolean).join(", ");
100
+ }
101
+ function pickPublishedFlag(properties) {
102
+ const published = getProperty(properties, "Published");
103
+ if (published?.type === "checkbox") {
104
+ return Boolean(published.checkbox);
105
+ }
106
+ const status = getProperty(properties, "Status");
107
+ if (status?.type === "status") {
108
+ const statusValue = status.status;
109
+ return String(statusValue?.name ?? "").trim().toLowerCase() === "published";
110
+ }
111
+ if (status?.type === "select") {
112
+ const statusValue = status.select;
113
+ return String(statusValue?.name ?? "").trim().toLowerCase() === "published";
114
+ }
115
+ return false;
116
+ }
117
+ function pickDescriptionFallback(description, title) {
118
+ return description.trim() || title.trim();
119
+ }
120
+ function isValidPublicSlug(slug) {
121
+ return /^[a-z0-9][a-z0-9-]{0,79}$/.test(slug);
122
+ }
123
+ function notionPageEditUrl(pageId, editBaseUrl) {
124
+ const compactPageId = pageId.replaceAll("-", "");
125
+ if (editBaseUrl?.includes("{pageId}")) {
126
+ return editBaseUrl.replaceAll("{pageId}", compactPageId);
127
+ }
128
+ return `https://www.notion.so/${compactPageId}`;
129
+ }
130
+ function compactNotionId(id) {
131
+ return id.replaceAll("-", "").toLowerCase();
132
+ }
133
+ export {
134
+ compactNotionId,
135
+ getAuthorProperty,
136
+ getCheckboxProperty,
137
+ getDateProperty,
138
+ getFirstDateProperty,
139
+ getFirstPeopleProperty,
140
+ getFirstTagsProperty,
141
+ getFirstTitleProperty,
142
+ getRelationPageIds,
143
+ getRichTextProperty,
144
+ getSelectProperty,
145
+ getTagsProperty,
146
+ isRecord,
147
+ isValidPublicSlug,
148
+ notionPageEditUrl,
149
+ pickDescriptionFallback,
150
+ pickPublishedFlag
151
+ };
152
+ //# sourceMappingURL=property-mappers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/notion/property-mappers.ts"],"sourcesContent":["type PropertyMap = Record<string, unknown>;\n\ntype TextPart = {\n plain_text?: string;\n};\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value && typeof value === \"object\");\n}\n\nfunction getPlainText(parts: unknown): string {\n if (!Array.isArray(parts)) return \"\";\n return parts\n .map((part: TextPart) => part.plain_text ?? \"\")\n .join(\"\")\n .trim();\n}\n\nfunction getProperty(properties: PropertyMap, key: string) {\n return properties[key] as Record<string, unknown> | undefined;\n}\n\nfunction firstPropertyOfType(properties: PropertyMap, type: string) {\n return Object.values(properties).find(\n (property): property is Record<string, unknown> =>\n isRecord(property) && property.type === type\n );\n}\n\nexport function getFirstTitleProperty(properties: PropertyMap): string {\n const property = firstPropertyOfType(properties, \"title\");\n return property ? getPlainText(property.title) : \"\";\n}\n\nexport function getRichTextProperty(properties: PropertyMap, key: string): string {\n const property = getProperty(properties, key);\n if (!property) return \"\";\n\n if (property.type === \"title\") return getPlainText(property.title);\n if (property.type === \"rich_text\") return getPlainText(property.rich_text);\n if (property.type === \"url\") return String(property.url ?? \"\").trim();\n if (property.type === \"email\") return String(property.email ?? \"\").trim();\n if (property.type === \"phone_number\") {\n return String(property.phone_number ?? \"\").trim();\n }\n\n return \"\";\n}\n\nexport function getDateProperty(properties: PropertyMap, key: string): string {\n const property = getProperty(properties, key);\n if (property?.type !== \"date\") return \"\";\n const date = property.date as { start?: string } | null | undefined;\n return String(date?.start ?? \"\").trim();\n}\n\nexport function getFirstDateProperty(properties: PropertyMap): string {\n const property = firstPropertyOfType(properties, \"date\");\n if (!property) return \"\";\n const date = property.date as { start?: string } | null | undefined;\n return String(date?.start ?? \"\").trim();\n}\n\nexport function getSelectProperty(properties: PropertyMap, key: string): string {\n const property = getProperty(properties, key);\n if (property?.type !== \"select\") return \"\";\n const select = property.select as { name?: string } | null | undefined;\n return String(select?.name ?? \"\").trim();\n}\n\nexport function getCheckboxProperty(properties: PropertyMap, key: string): boolean {\n const property = getProperty(properties, key);\n if (property?.type !== \"checkbox\") return false;\n return Boolean(property.checkbox);\n}\n\nexport function getRelationPageIds(properties: PropertyMap, key: string): string[] {\n const property = getProperty(properties, key);\n if (property?.type !== \"relation\" || !Array.isArray(property.relation)) {\n return [];\n }\n\n return property.relation\n .map((item: { id?: string }) => String(item.id ?? \"\").trim())\n .filter(Boolean);\n}\n\nexport function getTagsProperty(properties: PropertyMap, key: string): string[] {\n const property = getProperty(properties, key);\n if (property?.type === \"multi_select\" && Array.isArray(property.multi_select)) {\n return property.multi_select\n .map((item: { name?: string }) => String(item.name ?? \"\").trim())\n .filter(Boolean);\n }\n\n if (property?.type === \"select\") {\n const select = property.select as { name?: string } | null | undefined;\n const name = String(select?.name ?? \"\").trim();\n return name ? [name] : [];\n }\n\n return [];\n}\n\nexport function getFirstTagsProperty(properties: PropertyMap): string[] {\n const multiSelect = firstPropertyOfType(properties, \"multi_select\");\n if (multiSelect && Array.isArray(multiSelect.multi_select)) {\n return multiSelect.multi_select\n .map((item: { name?: string }) => String(item.name ?? \"\").trim())\n .filter(Boolean);\n }\n\n const select = firstPropertyOfType(properties, \"select\");\n const name = String((select?.select as { name?: string } | null)?.name ?? \"\").trim();\n return name ? [name] : [];\n}\n\nexport function getAuthorProperty(properties: PropertyMap, key: string): string {\n const property = getProperty(properties, key);\n if (!property) return \"\";\n\n if (property.type === \"people\" && Array.isArray(property.people)) {\n return property.people\n .map((person: { name?: string; person?: { email?: string } }) =>\n String(person.name ?? person.person?.email ?? \"\").trim()\n )\n .filter(Boolean)\n .join(\", \");\n }\n\n return getRichTextProperty(properties, key);\n}\n\nexport function getFirstPeopleProperty(properties: PropertyMap): string {\n const property = firstPropertyOfType(properties, \"people\");\n if (!property || !Array.isArray(property.people)) return \"\";\n\n return property.people\n .map((person: { name?: string; person?: { email?: string } }) =>\n String(person.name ?? person.person?.email ?? \"\").trim()\n )\n .filter(Boolean)\n .join(\", \");\n}\n\nexport function pickPublishedFlag(properties: PropertyMap): boolean {\n const published = getProperty(properties, \"Published\");\n if (published?.type === \"checkbox\") {\n return Boolean(published.checkbox);\n }\n\n const status = getProperty(properties, \"Status\");\n if (status?.type === \"status\") {\n const statusValue = status.status as { name?: string } | null | undefined;\n return String(statusValue?.name ?? \"\").trim().toLowerCase() === \"published\";\n }\n\n if (status?.type === \"select\") {\n const statusValue = status.select as { name?: string } | null | undefined;\n return String(statusValue?.name ?? \"\").trim().toLowerCase() === \"published\";\n }\n\n return false;\n}\n\nexport function pickDescriptionFallback(description: string, title: string): string {\n return description.trim() || title.trim();\n}\n\nexport function isValidPublicSlug(slug: string): boolean {\n return /^[a-z0-9][a-z0-9-]{0,79}$/.test(slug);\n}\n\nexport function notionPageEditUrl(pageId: string, editBaseUrl?: string): string {\n const compactPageId = pageId.replaceAll(\"-\", \"\");\n if (editBaseUrl?.includes(\"{pageId}\")) {\n return editBaseUrl.replaceAll(\"{pageId}\", compactPageId);\n }\n return `https://www.notion.so/${compactPageId}`;\n}\n\n/**\n * Normalize a Notion page id (with or without dashes) to a compact lowercase\n * string. Used as a stable identifier in URLs and cache keys.\n */\nexport function compactNotionId(id: string): string {\n return id.replaceAll(\"-\", \"\").toLowerCase();\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,8 @@
1
+ export { CreateNotionWebhookRouteOptions, NotionWebhookParserFn, RevalidateContentModelFromWebhookFn, createNotionWebhookRoute } from './webhook.js';
2
+ import 'next/server';
3
+ import '../webhook.js';
4
+ import '../../platform/runtime.js';
5
+ import '../../env-C5qu-0R-.js';
6
+ import '../property-mappers.js';
7
+ import '../types.js';
8
+ import '../config.js';
@@ -0,0 +1,428 @@
1
+ // src/notion/routes/webhook.ts
2
+ import { NextResponse } from "next/server";
3
+
4
+ // src/notion/client.ts
5
+ import { Client } from "@notionhq/client";
6
+
7
+ // src/notion/config.ts
8
+ function readProcessEnv() {
9
+ const env2 = {
10
+ NOTION_TOKEN: process.env.NOTION_TOKEN,
11
+ NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,
12
+ NOTION_MOVIES_DATA_SOURCE_ID: process.env.NOTION_MOVIES_DATA_SOURCE_ID,
13
+ NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,
14
+ NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,
15
+ NOTION_WEBHOOK_VERIFICATION_TOKEN: process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN
16
+ };
17
+ for (const [key, value] of Object.entries(process.env)) {
18
+ if (key.startsWith("NOTION_") && typeof value === "string") {
19
+ env2[key] = value;
20
+ }
21
+ }
22
+ return env2;
23
+ }
24
+ async function readWorkerEnv() {
25
+ try {
26
+ const mod = await import(
27
+ /* webpackIgnore: true */
28
+ "cloudflare:workers"
29
+ );
30
+ const env2 = {};
31
+ for (const [key, value] of Object.entries(mod.env ?? {})) {
32
+ if (key.startsWith("NOTION_") && typeof value === "string") {
33
+ env2[key] = value;
34
+ }
35
+ }
36
+ return env2;
37
+ } catch {
38
+ return {};
39
+ }
40
+ }
41
+ function readString(source, name) {
42
+ const value = String(source[name] ?? "").trim();
43
+ return value || void 0;
44
+ }
45
+ function mergeEnv(...sources) {
46
+ const merged = {};
47
+ for (const source of sources) {
48
+ for (const name of Object.keys(source)) {
49
+ if (!name.startsWith("NOTION_")) continue;
50
+ const value = readString(source, name);
51
+ if (value) merged[name] = value;
52
+ }
53
+ }
54
+ return merged;
55
+ }
56
+ async function readEnv() {
57
+ const processEnv = readProcessEnv();
58
+ return mergeEnv(await readWorkerEnv(), processEnv);
59
+ }
60
+ async function getNotionWebhookVerificationToken() {
61
+ const env2 = await readEnv();
62
+ return readString(env2, "NOTION_WEBHOOK_VERIFICATION_TOKEN");
63
+ }
64
+
65
+ // src/notion/webhook.ts
66
+ var WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY = "notion:webhook:verification-token:v1";
67
+ function isRecord(value) {
68
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
69
+ }
70
+ function readString2(source, key) {
71
+ if (!isRecord(source)) return "";
72
+ const value = source[key];
73
+ return typeof value === "string" ? value.trim() : "";
74
+ }
75
+ function notionWebhookEventToRevalidateRequest(event) {
76
+ return {
77
+ modelId: event.modelId,
78
+ pageId: event.pageId,
79
+ routeId: event.routeId,
80
+ locale: event.locale,
81
+ kind: event.kind,
82
+ includeApi: event.includeApi
83
+ };
84
+ }
85
+ async function signNotionWebhookBody(body, verificationToken) {
86
+ const key = await crypto.subtle.importKey(
87
+ "raw",
88
+ new TextEncoder().encode(verificationToken),
89
+ { name: "HMAC", hash: "SHA-256" },
90
+ false,
91
+ ["sign"]
92
+ );
93
+ const signature = await crypto.subtle.sign(
94
+ "HMAC",
95
+ key,
96
+ new TextEncoder().encode(body)
97
+ );
98
+ return Array.from(new Uint8Array(signature)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
99
+ }
100
+ function normalizeNotionSignature(signature) {
101
+ const value = String(signature ?? "").trim();
102
+ const prefixed = value.match(/^sha256=(.+)$/i);
103
+ return prefixed && prefixed[1] ? prefixed[1].trim() : value;
104
+ }
105
+ async function verifyNotionWebhookSignature(input) {
106
+ const token = String(input.verificationToken ?? "").trim();
107
+ const signature = normalizeNotionSignature(input.signature);
108
+ if (!token || !signature) return false;
109
+ const expected = await signNotionWebhookBody(input.body, token);
110
+ if (expected.length !== signature.length) return false;
111
+ let diff = 0;
112
+ for (let index = 0; index < expected.length; index += 1) {
113
+ diff |= expected.charCodeAt(index) ^ signature.charCodeAt(index);
114
+ }
115
+ return diff === 0;
116
+ }
117
+ async function verifyNotionWebhookSignatureWithTokens(input) {
118
+ const tokens = Array.from(
119
+ new Set(input.verificationTokens.map((token) => String(token ?? "").trim()))
120
+ ).filter(Boolean);
121
+ for (const token of tokens) {
122
+ if (await verifyNotionWebhookSignature({
123
+ body: input.body,
124
+ signature: input.signature,
125
+ verificationToken: token
126
+ })) {
127
+ return true;
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+ function readStoredWebhookVerificationToken(value) {
133
+ if (typeof value === "string") return value.trim() || null;
134
+ if (!isRecord(value)) return null;
135
+ const token = readString2(value, "token");
136
+ return token || null;
137
+ }
138
+ async function getStoredNotionWebhookVerificationToken(cache) {
139
+ if (!cache) return null;
140
+ try {
141
+ const value = await cache.get(
142
+ WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,
143
+ { cacheTtl: 60 }
144
+ );
145
+ return readStoredWebhookVerificationToken(value);
146
+ } catch (error) {
147
+ console.warn(
148
+ JSON.stringify({
149
+ tag: "notion_webhook_token_lookup_failed",
150
+ message: error instanceof Error ? error.message : String(error)
151
+ })
152
+ );
153
+ return null;
154
+ }
155
+ }
156
+ async function putStoredNotionWebhookVerificationToken(cache, token) {
157
+ const normalized = token.trim();
158
+ if (!cache || !normalized) return false;
159
+ await cache.put(
160
+ WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,
161
+ {
162
+ token: normalized,
163
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
164
+ },
165
+ {
166
+ metadata: {
167
+ source: "notion-webhook"
168
+ }
169
+ }
170
+ );
171
+ return true;
172
+ }
173
+
174
+ // src/util/env.ts
175
+ import { env } from "cloudflare:workers";
176
+ var workerEnv = env;
177
+
178
+ // src/platform/runtime.ts
179
+ function cacheRequestForKey(key) {
180
+ return new Request(key, { method: "GET" });
181
+ }
182
+ function createCloudflarePublicCacheAdapter(cache) {
183
+ return {
184
+ kind: "cloudflare-cache",
185
+ async match(key) {
186
+ return await cache.match(cacheRequestForKey(key)) ?? null;
187
+ },
188
+ put(key, response) {
189
+ return cache.put(cacheRequestForKey(key), response);
190
+ },
191
+ delete(key) {
192
+ return cache.delete(cacheRequestForKey(key));
193
+ }
194
+ };
195
+ }
196
+ function createCloudflareKeyValueCacheAdapter(namespace) {
197
+ return {
198
+ kind: "workers-kv",
199
+ async get(key, options) {
200
+ return await namespace.get(key, {
201
+ type: "json",
202
+ cacheTtl: options?.cacheTtl
203
+ });
204
+ },
205
+ async put(key, value, options) {
206
+ await namespace.put(key, JSON.stringify(value), {
207
+ expirationTtl: options?.expirationTtl,
208
+ metadata: options?.metadata
209
+ });
210
+ },
211
+ delete(key) {
212
+ return namespace.delete(key);
213
+ },
214
+ async list(options) {
215
+ const result = await namespace.list({
216
+ prefix: options?.prefix,
217
+ limit: options?.limit,
218
+ cursor: options?.cursor
219
+ });
220
+ return {
221
+ keys: result.keys.map((key) => ({ name: key.name })),
222
+ cursor: result.list_complete ? void 0 : result.cursor,
223
+ listComplete: result.list_complete
224
+ };
225
+ }
226
+ };
227
+ }
228
+ function r2ObjectToStoredObject(object) {
229
+ return {
230
+ body: object.body,
231
+ size: object.size,
232
+ etag: object.etag,
233
+ contentType: object.httpMetadata?.contentType
234
+ };
235
+ }
236
+ function createCloudflareRuntimePlatform(env2, options) {
237
+ const database = env2.DB ? {
238
+ kind: "d1",
239
+ prepare(query) {
240
+ return env2.DB.prepare(query);
241
+ },
242
+ async batch(statements) {
243
+ return await env2.DB.batch(
244
+ statements
245
+ );
246
+ }
247
+ } : null;
248
+ const objectStorage = env2.ASSETS_BUCKET ? {
249
+ kind: "r2",
250
+ async get(key) {
251
+ const object = await env2.ASSETS_BUCKET?.get(key);
252
+ return object ? r2ObjectToStoredObject(object) : null;
253
+ },
254
+ async put(key, value, options2) {
255
+ await env2.ASSETS_BUCKET?.put(key, value, {
256
+ httpMetadata: {
257
+ contentType: options2?.contentType,
258
+ cacheControl: options2?.cacheControl
259
+ },
260
+ customMetadata: options2?.metadata
261
+ });
262
+ },
263
+ async delete(key) {
264
+ await env2.ASSETS_BUCKET?.delete(key);
265
+ },
266
+ async list(options2) {
267
+ const listed = await env2.ASSETS_BUCKET?.list({
268
+ prefix: options2?.prefix,
269
+ limit: options2?.limit
270
+ });
271
+ return listed?.objects.map((object) => ({
272
+ key: object.key,
273
+ size: object.size,
274
+ uploaded: object.uploaded
275
+ })) ?? [];
276
+ }
277
+ } : null;
278
+ const imageTransformer = env2.IMAGES ? {
279
+ kind: "cloudflare-images",
280
+ async transform(body, options2) {
281
+ const result = await env2.IMAGES.input(body).transform(options2.width ? { width: options2.width } : {}).output({
282
+ format: options2.format,
283
+ quality: options2.quality
284
+ });
285
+ return {
286
+ body: result.image(),
287
+ contentType: result.contentType(),
288
+ response: () => result.response()
289
+ };
290
+ }
291
+ } : null;
292
+ const keyValueCache = env2.CONTENT_CACHE ? createCloudflareKeyValueCacheAdapter(env2.CONTENT_CACHE) : null;
293
+ return {
294
+ id: "cloudflare-workers",
295
+ database,
296
+ objectStorage,
297
+ imageTransformer,
298
+ keyValueCache,
299
+ publicCache: options?.publicCache ? createCloudflarePublicCacheAdapter(options.publicCache) : null
300
+ };
301
+ }
302
+
303
+ // src/platform/cloudflare-runtime.ts
304
+ function getDefaultCloudflareCache() {
305
+ const globalWithCaches = globalThis;
306
+ return globalWithCaches.caches?.default ?? null;
307
+ }
308
+ function getRuntimePlatform() {
309
+ return createCloudflareRuntimePlatform(workerEnv, {
310
+ publicCache: getDefaultCloudflareCache()
311
+ });
312
+ }
313
+
314
+ // src/platform/current.ts
315
+ function getRuntimePlatform2() {
316
+ return getRuntimePlatform();
317
+ }
318
+
319
+ // src/notion/routes/webhook.ts
320
+ function createNotionWebhookRoute(options) {
321
+ const parseFn = options.parseNotionWebhookPayload;
322
+ const getToken = options.getVerificationToken ?? getNotionWebhookVerificationToken;
323
+ return {
324
+ async POST(request) {
325
+ return handlePost(request, options, parseFn, getToken);
326
+ },
327
+ async handle(request) {
328
+ if (request.method !== "POST") {
329
+ return NextResponse.json(
330
+ { ok: false, error: "Method not allowed" },
331
+ { status: 405, headers: { "Cache-Control": "no-store" } }
332
+ );
333
+ }
334
+ return handlePost(request, options, parseFn, getToken);
335
+ }
336
+ };
337
+ }
338
+ async function handlePost(request, options, parseFn, getToken) {
339
+ const bodyText = await request.text();
340
+ let payload;
341
+ try {
342
+ payload = JSON.parse(bodyText || "{}");
343
+ } catch {
344
+ return NextResponse.json(
345
+ { ok: false, error: "Invalid JSON" },
346
+ { status: 400, headers: { "Cache-Control": "no-store" } }
347
+ );
348
+ }
349
+ const platform = getRuntimePlatform2();
350
+ const parsed = await parseFn(payload, { lookupPages: false });
351
+ if (parsed.type === "verification") {
352
+ const verified2 = await verifyNotionWebhookSignatureWithTokens({
353
+ body: bodyText,
354
+ signature: request.headers.get("x-notion-signature"),
355
+ verificationTokens: [parsed.verificationToken]
356
+ });
357
+ if (!verified2) {
358
+ return NextResponse.json(
359
+ { ok: false, error: "Invalid Notion webhook verification signature" },
360
+ { status: 401, headers: { "Cache-Control": "no-store" } }
361
+ );
362
+ }
363
+ const stored = await putStoredNotionWebhookVerificationToken(
364
+ platform.keyValueCache,
365
+ parsed.verificationToken
366
+ );
367
+ return NextResponse.json(
368
+ {
369
+ verification_token: parsed.verificationToken,
370
+ stored
371
+ },
372
+ { headers: { "Cache-Control": "no-store" } }
373
+ );
374
+ }
375
+ const token = await getToken();
376
+ const storedToken = await getStoredNotionWebhookVerificationToken(
377
+ platform.keyValueCache
378
+ );
379
+ const verified = await verifyNotionWebhookSignatureWithTokens({
380
+ body: bodyText,
381
+ signature: request.headers.get("x-notion-signature"),
382
+ verificationTokens: [token, storedToken]
383
+ });
384
+ if (!verified) {
385
+ return NextResponse.json(
386
+ { ok: false, error: "Invalid Notion webhook signature" },
387
+ { status: 401, headers: { "Cache-Control": "no-store" } }
388
+ );
389
+ }
390
+ const resolved = await parseFn(payload);
391
+ if (resolved.type === "verification") {
392
+ return NextResponse.json(
393
+ { ok: false, error: "Unexpected verification payload" },
394
+ { status: 400, headers: { "Cache-Control": "no-store" } }
395
+ );
396
+ }
397
+ const results = [];
398
+ for (const event of resolved.events) {
399
+ const result = await options.revalidateContentModel({
400
+ request: notionWebhookEventToRevalidateRequest(event),
401
+ tokenAuthorized: true,
402
+ revalidatePath: options.revalidatePath,
403
+ contentCache: platform.keyValueCache ?? void 0,
404
+ getContentCache: () => platform.keyValueCache,
405
+ database: platform.database ?? void 0,
406
+ getDatabase: () => platform.database
407
+ });
408
+ results.push({
409
+ eventId: event.id,
410
+ eventType: event.eventType,
411
+ modelId: event.modelId,
412
+ routeId: event.routeId ?? null,
413
+ result
414
+ });
415
+ }
416
+ return NextResponse.json(
417
+ {
418
+ ok: true,
419
+ received: parsed.events.length,
420
+ results
421
+ },
422
+ { headers: { "Cache-Control": "no-store" } }
423
+ );
424
+ }
425
+ export {
426
+ createNotionWebhookRoute
427
+ };
428
+ //# sourceMappingURL=index.js.map