@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,1278 @@
1
+ // src/notion/client.ts
2
+ import { Client } from "@notionhq/client";
3
+ function createNotionClient(config) {
4
+ return new Client({
5
+ auth: config.token,
6
+ baseUrl: config.apiBaseUrl,
7
+ notionVersion: "2026-03-11"
8
+ });
9
+ }
10
+
11
+ // src/notion/config.ts
12
+ var DEFAULT_NOTION_MOVIES_DATA_SOURCE_ID = "371dc62d-0738-8015-a601-000bc3944fcb";
13
+ function readProcessEnv() {
14
+ const env = {
15
+ NOTION_TOKEN: process.env.NOTION_TOKEN,
16
+ NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,
17
+ NOTION_MOVIES_DATA_SOURCE_ID: process.env.NOTION_MOVIES_DATA_SOURCE_ID,
18
+ NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,
19
+ NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,
20
+ NOTION_WEBHOOK_VERIFICATION_TOKEN: process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN
21
+ };
22
+ for (const [key, value] of Object.entries(process.env)) {
23
+ if (key.startsWith("NOTION_") && typeof value === "string") {
24
+ env[key] = value;
25
+ }
26
+ }
27
+ return env;
28
+ }
29
+ async function readWorkerEnv() {
30
+ try {
31
+ const mod = await import(
32
+ /* webpackIgnore: true */
33
+ "cloudflare:workers"
34
+ );
35
+ const env = {};
36
+ for (const [key, value] of Object.entries(mod.env ?? {})) {
37
+ if (key.startsWith("NOTION_") && typeof value === "string") {
38
+ env[key] = value;
39
+ }
40
+ }
41
+ return env;
42
+ } catch {
43
+ return {};
44
+ }
45
+ }
46
+ function readString(source, name) {
47
+ const value = String(source[name] ?? "").trim();
48
+ return value || void 0;
49
+ }
50
+ function mergeEnv(...sources) {
51
+ const merged = {};
52
+ for (const source of sources) {
53
+ for (const name of Object.keys(source)) {
54
+ if (!name.startsWith("NOTION_")) continue;
55
+ const value = readString(source, name);
56
+ if (value) merged[name] = value;
57
+ }
58
+ }
59
+ return merged;
60
+ }
61
+ async function readEnv() {
62
+ const processEnv = readProcessEnv();
63
+ return mergeEnv(await readWorkerEnv(), processEnv);
64
+ }
65
+ function readRequired(source, name) {
66
+ const value = readString(source, name);
67
+ if (!value) {
68
+ throw new Error(`Missing required Notion env: ${name}`);
69
+ }
70
+ return value;
71
+ }
72
+ function getNotionEditBaseUrl() {
73
+ return readString(readProcessEnv(), "NOTION_EDIT_BASE_URL") ?? "https://www.notion.so";
74
+ }
75
+ async function hasNotionConfig() {
76
+ const env = await readEnv();
77
+ return Boolean(
78
+ readString(env, "NOTION_TOKEN") && readString(env, "NOTION_DATA_SOURCE_ID")
79
+ );
80
+ }
81
+ async function hasNotionMovieConfig() {
82
+ const env = await readEnv();
83
+ return Boolean(readString(env, "NOTION_TOKEN"));
84
+ }
85
+ async function hasNotionModelConfig(model) {
86
+ const env = await readEnv();
87
+ return Boolean(
88
+ readString(env, "NOTION_TOKEN") && (readString(env, model.source.dataSourceEnv) || model.source.defaultDataSourceId)
89
+ );
90
+ }
91
+ async function getNotionClientConfig() {
92
+ const env = await readEnv();
93
+ return {
94
+ token: readRequired(env, "NOTION_TOKEN"),
95
+ apiBaseUrl: readString(env, "NOTION_API_BASE_URL")
96
+ };
97
+ }
98
+ async function getNotionConfig() {
99
+ const env = await readEnv();
100
+ return {
101
+ token: readRequired(env, "NOTION_TOKEN"),
102
+ dataSourceId: readRequired(env, "NOTION_DATA_SOURCE_ID"),
103
+ apiBaseUrl: readString(env, "NOTION_API_BASE_URL"),
104
+ editBaseUrl: readString(env, "NOTION_EDIT_BASE_URL"),
105
+ webhookVerificationToken: readString(
106
+ env,
107
+ "NOTION_WEBHOOK_VERIFICATION_TOKEN"
108
+ )
109
+ };
110
+ }
111
+ async function getNotionWebhookVerificationToken() {
112
+ const env = await readEnv();
113
+ return readString(env, "NOTION_WEBHOOK_VERIFICATION_TOKEN");
114
+ }
115
+ async function getNotionMovieConfig() {
116
+ const env = await readEnv();
117
+ return {
118
+ token: readRequired(env, "NOTION_TOKEN"),
119
+ dataSourceId: readString(env, "NOTION_MOVIES_DATA_SOURCE_ID") ?? DEFAULT_NOTION_MOVIES_DATA_SOURCE_ID,
120
+ apiBaseUrl: readString(env, "NOTION_API_BASE_URL"),
121
+ editBaseUrl: readString(env, "NOTION_EDIT_BASE_URL"),
122
+ webhookVerificationToken: readString(
123
+ env,
124
+ "NOTION_WEBHOOK_VERIFICATION_TOKEN"
125
+ )
126
+ };
127
+ }
128
+ async function getNotionConfigForModel(model) {
129
+ const env = await readEnv();
130
+ const dataSourceId = readString(env, model.source.dataSourceEnv) ?? model.source.defaultDataSourceId;
131
+ if (!dataSourceId) {
132
+ throw new Error(`Missing required Notion env: ${model.source.dataSourceEnv}`);
133
+ }
134
+ return {
135
+ token: readRequired(env, model.source.tokenEnv),
136
+ dataSourceId,
137
+ apiBaseUrl: readString(env, "NOTION_API_BASE_URL"),
138
+ editBaseUrl: readString(env, "NOTION_EDIT_BASE_URL"),
139
+ webhookVerificationToken: readString(
140
+ env,
141
+ "NOTION_WEBHOOK_VERIFICATION_TOKEN"
142
+ )
143
+ };
144
+ }
145
+
146
+ // src/notion/blocks.ts
147
+ function normalizeBlock(input) {
148
+ if (!input || typeof input !== "object") return null;
149
+ const block = input;
150
+ return block.id && block.type ? block : null;
151
+ }
152
+ async function listBlockChildren(client, blockId) {
153
+ const results = [];
154
+ let cursor;
155
+ do {
156
+ const response = await client.blocks.children.list({
157
+ block_id: blockId,
158
+ page_size: 100,
159
+ ...cursor ? { start_cursor: cursor } : {}
160
+ });
161
+ for (const item of response.results ?? []) {
162
+ const block = normalizeBlock(item);
163
+ if (block) results.push(block);
164
+ }
165
+ cursor = response.next_cursor ?? void 0;
166
+ if (!response.has_more) break;
167
+ } while (cursor);
168
+ return results;
169
+ }
170
+ async function listBlockChildrenDeep(client, blockId, options) {
171
+ const maxDepth = options?.maxDepth ?? 6;
172
+ async function visit(id, depth) {
173
+ const children = await listBlockChildren(client, id);
174
+ if (depth >= maxDepth) return children;
175
+ return Promise.all(
176
+ children.map(async (block) => {
177
+ if (!block.has_children) return block;
178
+ return {
179
+ ...block,
180
+ children: await visit(block.id, depth + 1)
181
+ };
182
+ })
183
+ );
184
+ }
185
+ return visit(blockId, 0);
186
+ }
187
+
188
+ // src/notion/block-text.ts
189
+ function typedValue(block) {
190
+ return block[block.type] ?? {};
191
+ }
192
+ function richTextPlainText(parts) {
193
+ if (!Array.isArray(parts)) return "";
194
+ return parts.map(
195
+ (part) => part.type === "equation" ? part.equation?.expression ?? "" : part.plain_text ?? part.text?.content ?? ""
196
+ ).join("");
197
+ }
198
+ function blockPlainText(block) {
199
+ const value = typedValue(block);
200
+ const text = [
201
+ richTextPlainText(value.rich_text),
202
+ richTextPlainText(value.caption),
203
+ richTextPlainText(value.title),
204
+ value.expression ?? "",
205
+ ...(value.cells ?? []).map(richTextPlainText)
206
+ ];
207
+ return text.filter(Boolean).join(" ");
208
+ }
209
+ function flattenNotionBlockText(blocks) {
210
+ const parts = [];
211
+ function visit(block) {
212
+ const text = blockPlainText(block).trim();
213
+ if (text) parts.push(text);
214
+ for (const child of block.children ?? []) {
215
+ visit(child);
216
+ }
217
+ }
218
+ for (const block of blocks) visit(block);
219
+ return parts.join(" ").normalize("NFKC").replace(/\s+/g, " ").trim();
220
+ }
221
+
222
+ // src/notion/content-cache.ts
223
+ var CACHE_VERSION = "v2";
224
+ var NOTION_LIST_CACHE_TTL_SECONDS = 300;
225
+ var NOTION_BLOCKS_CACHE_TTL_SECONDS = 60 * 60 * 24;
226
+ var CONTENT_CACHE_READ_TTL_SECONDS = 60;
227
+ function modelPrefix(modelId) {
228
+ return `notion:${CACHE_VERSION}:${modelId}`;
229
+ }
230
+ function notionModelListCacheKey(modelId) {
231
+ return `${modelPrefix(modelId)}:list`;
232
+ }
233
+ function versionSegment(value) {
234
+ const version = String(value ?? "").trim();
235
+ return version ? `:v:${encodeURIComponent(version)}` : "";
236
+ }
237
+ function notionPageBlocksCacheKey(modelId, pageId, cacheVersion) {
238
+ return `${modelPrefix(modelId)}:page:${pageId}:blocks${versionSegment(cacheVersion)}`;
239
+ }
240
+ function notionModelCachePrefix(modelId) {
241
+ return `${modelPrefix(modelId)}:`;
242
+ }
243
+ function notionPageCachePrefix(modelId, pageId) {
244
+ return `${modelPrefix(modelId)}:page:${pageId}:`;
245
+ }
246
+ function logNotionContentCache(fields) {
247
+ try {
248
+ console.log(JSON.stringify({ tag: "notion_content_cache", ...fields }));
249
+ } catch {
250
+ }
251
+ }
252
+ async function getCachedNotionValue(cache2, key) {
253
+ if (!cache2) return null;
254
+ try {
255
+ const value = await cache2.get(key, {
256
+ cacheTtl: CONTENT_CACHE_READ_TTL_SECONDS
257
+ });
258
+ logNotionContentCache({
259
+ op: "get",
260
+ key,
261
+ hit: value !== null,
262
+ cache: cache2.kind
263
+ });
264
+ return value;
265
+ } catch (error) {
266
+ logNotionContentCache({
267
+ op: "get_error",
268
+ key,
269
+ cache: cache2.kind,
270
+ message: error instanceof Error ? error.message : String(error)
271
+ });
272
+ return null;
273
+ }
274
+ }
275
+ async function putCachedNotionValue(cache2, key, value, options) {
276
+ if (!cache2) return;
277
+ try {
278
+ await cache2.put(key, value, {
279
+ expirationTtl: options?.expirationTtl ?? NOTION_BLOCKS_CACHE_TTL_SECONDS,
280
+ metadata: {
281
+ source: "notion",
282
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString()
283
+ }
284
+ });
285
+ logNotionContentCache({
286
+ op: "put",
287
+ key,
288
+ cache: cache2.kind
289
+ });
290
+ } catch (error) {
291
+ logNotionContentCache({
292
+ op: "put_error",
293
+ key,
294
+ cache: cache2.kind,
295
+ message: error instanceof Error ? error.message : String(error)
296
+ });
297
+ }
298
+ }
299
+ async function getCachedNotionBlocks(cache2, input) {
300
+ return getCachedNotionValue(
301
+ cache2,
302
+ notionPageBlocksCacheKey(input.modelId, input.pageId, input.cacheVersion)
303
+ );
304
+ }
305
+ async function putCachedNotionBlocks(cache2, input) {
306
+ await putCachedNotionValue(
307
+ cache2,
308
+ notionPageBlocksCacheKey(input.modelId, input.pageId, input.cacheVersion),
309
+ input.blocks,
310
+ { expirationTtl: NOTION_BLOCKS_CACHE_TTL_SECONDS }
311
+ );
312
+ }
313
+ async function deleteNotionContentCache(input) {
314
+ let cache2;
315
+ try {
316
+ cache2 = input.cache ?? input.getCache?.() ?? null;
317
+ } catch (error) {
318
+ return {
319
+ ok: false,
320
+ skipped: true,
321
+ deleted: [],
322
+ failed: [
323
+ {
324
+ error: error instanceof Error ? error.message : String(error)
325
+ }
326
+ ]
327
+ };
328
+ }
329
+ if (!cache2) {
330
+ return { ok: true, skipped: true, deleted: [], failed: [] };
331
+ }
332
+ const prefixes = /* @__PURE__ */ new Set();
333
+ if (input.pageId) {
334
+ prefixes.add(notionPageCachePrefix(input.modelId, input.pageId));
335
+ } else {
336
+ prefixes.add(notionModelCachePrefix(input.modelId));
337
+ }
338
+ prefixes.add(notionModelListCacheKey(input.modelId));
339
+ const deleted = [];
340
+ const failed = [];
341
+ for (const prefix of prefixes) {
342
+ let cursor;
343
+ do {
344
+ try {
345
+ const result = await cache2.list({ prefix, cursor, limit: 100 });
346
+ for (const { name } of result.keys) {
347
+ try {
348
+ await cache2.delete(name);
349
+ deleted.push(name);
350
+ } catch (error) {
351
+ failed.push({
352
+ key: name,
353
+ error: error instanceof Error ? error.message : String(error)
354
+ });
355
+ }
356
+ }
357
+ cursor = result.listComplete ? void 0 : result.cursor;
358
+ } catch (error) {
359
+ failed.push({
360
+ key: prefix,
361
+ error: error instanceof Error ? error.message : String(error)
362
+ });
363
+ cursor = void 0;
364
+ }
365
+ } while (cursor);
366
+ }
367
+ return {
368
+ ok: failed.length === 0,
369
+ skipped: false,
370
+ deleted,
371
+ failed
372
+ };
373
+ }
374
+
375
+ // src/notion/media.ts
376
+ function stripLeadingSlash(value) {
377
+ return value.startsWith("/") ? value.slice(1) : value;
378
+ }
379
+ function encodePathPart(value) {
380
+ return encodeURIComponent(stripLeadingSlash(value));
381
+ }
382
+ function appendVersion(path, version) {
383
+ const value = String(version ?? "").trim();
384
+ if (!value) return path;
385
+ return `${path}?${new URLSearchParams({ v: value })}`;
386
+ }
387
+ function blockVersion(block) {
388
+ return typeof block.last_edited_time === "string" ? block.last_edited_time : void 0;
389
+ }
390
+ function notionPageCoverMediaPath(pageId) {
391
+ return `/api/notion/media/page/${encodePathPart(pageId)}/cover`;
392
+ }
393
+ function notionPagePropertyMediaPath(pageId, propertyName) {
394
+ return `/api/notion/media/page/${encodePathPart(pageId)}/property/${encodePathPart(propertyName)}`;
395
+ }
396
+ function notionBlockMediaPath(blockId) {
397
+ return `/api/notion/media/block/${encodePathPart(blockId)}`;
398
+ }
399
+ function normalizeNotionFileSource(input) {
400
+ const file = input;
401
+ if (!file || typeof file !== "object") return null;
402
+ if (file.type === "external") {
403
+ const url = String(file.external?.url ?? "").trim();
404
+ return url ? { type: "external", url } : null;
405
+ }
406
+ if (file.type === "file") {
407
+ const url = String(file.file?.url ?? "").trim();
408
+ if (!url) return null;
409
+ return {
410
+ type: "file",
411
+ url,
412
+ expiryTime: String(file.file?.expiry_time ?? "").trim() || null
413
+ };
414
+ }
415
+ return null;
416
+ }
417
+ function resolveNotionFileUrl(input) {
418
+ return normalizeNotionFileSource(input)?.url ?? null;
419
+ }
420
+ function isNotionHostedFile(input) {
421
+ return normalizeNotionFileSource(input)?.type === "file";
422
+ }
423
+ function pickFirstFilesPropertyValue(property) {
424
+ const value = property;
425
+ if (!value || value.type !== "files" || !Array.isArray(value.files)) {
426
+ return null;
427
+ }
428
+ return value.files[0] ?? null;
429
+ }
430
+ function pickPageCoverFile(page) {
431
+ return page.cover ?? null;
432
+ }
433
+ function coverImageUrlForPage(page, coverPropertyName = "Cover") {
434
+ const propertyFile = pickFirstFilesPropertyValue(
435
+ page.properties?.[coverPropertyName]
436
+ );
437
+ const propertySource = normalizeNotionFileSource(propertyFile);
438
+ if (propertySource) {
439
+ return appendVersion(
440
+ notionPagePropertyMediaPath(page.id, coverPropertyName),
441
+ page.last_edited_time
442
+ );
443
+ }
444
+ const coverSource = normalizeNotionFileSource(pickPageCoverFile(page));
445
+ if (coverSource) {
446
+ return appendVersion(notionPageCoverMediaPath(page.id), page.last_edited_time);
447
+ }
448
+ return null;
449
+ }
450
+ function fileObjectForMediaBlock(block) {
451
+ const typed = block[block.type];
452
+ if (!typed || typeof typed !== "object") return null;
453
+ if (block.type === "image" || block.type === "video" || block.type === "file" || block.type === "pdf" || block.type === "audio") {
454
+ return typed;
455
+ }
456
+ return null;
457
+ }
458
+ function mediaUrlForBlock(block) {
459
+ const source = normalizeNotionFileSource(fileObjectForMediaBlock(block));
460
+ if (!source) return null;
461
+ if (source.type === "external" && block.type !== "image") {
462
+ return source.url;
463
+ }
464
+ return appendVersion(notionBlockMediaPath(block.id), blockVersion(block));
465
+ }
466
+ function firstImageUrlFromBlocks(blocks) {
467
+ for (const block of blocks) {
468
+ if (block.type === "image") {
469
+ const imageUrl = mediaUrlForBlock(block);
470
+ if (imageUrl) return imageUrl;
471
+ }
472
+ const nested = block.children?.length ? firstImageUrlFromBlocks(block.children) : null;
473
+ if (nested) return nested;
474
+ }
475
+ return null;
476
+ }
477
+ function publicMediaBlockForApi(block) {
478
+ const value = block[block.type];
479
+ const source = normalizeNotionFileSource(value);
480
+ const children = block.children?.map(publicMediaBlockForApi);
481
+ if (!source) {
482
+ return children ? { ...block, children } : { ...block };
483
+ }
484
+ const path = appendVersion(notionBlockMediaPath(block.id), blockVersion(block));
485
+ const publicValue = source.type === "external" ? block.type === "image" ? {
486
+ ...value,
487
+ external: { url: path }
488
+ } : value : {
489
+ ...value,
490
+ file: {
491
+ url: path,
492
+ expiry_time: null
493
+ }
494
+ };
495
+ return {
496
+ ...block,
497
+ [block.type]: publicValue,
498
+ ...children ? { children } : {}
499
+ };
500
+ }
501
+ function gatedMediaBlockForApi(block, options) {
502
+ const value = block[block.type];
503
+ const source = normalizeNotionFileSource(value);
504
+ const children = block.children?.map(
505
+ (child) => gatedMediaBlockForApi(child, options)
506
+ );
507
+ if (block.type !== "video" || !source) {
508
+ const publicBlock = publicMediaBlockForApi(block);
509
+ return children ? { ...publicBlock, children } : publicBlock;
510
+ }
511
+ const gatedValue = {
512
+ ...value,
513
+ gated: true,
514
+ access_url: options?.movieId ? `/api/movies/${encodePathPart(options.movieId)}/video/${encodePathPart(block.id)}` : null
515
+ };
516
+ if (source.type === "external") {
517
+ gatedValue.external = { url: null };
518
+ } else {
519
+ gatedValue.file = {
520
+ url: null,
521
+ expiry_time: null
522
+ };
523
+ }
524
+ return {
525
+ ...block,
526
+ video: gatedValue,
527
+ ...children ? { children } : {}
528
+ };
529
+ }
530
+ function isDirectVideoUrl(url) {
531
+ try {
532
+ const parsed = new URL(url);
533
+ return /\.(mp4|webm|mov|m4v)(?:$|\?)/i.test(parsed.pathname);
534
+ } catch {
535
+ return false;
536
+ }
537
+ }
538
+ function videoEmbedUrl(url) {
539
+ try {
540
+ const parsed = new URL(url);
541
+ const host = parsed.hostname.replace(/^www\./, "");
542
+ if (host === "youtube.com" || host === "m.youtube.com") {
543
+ const id = parsed.searchParams.get("v");
544
+ return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;
545
+ }
546
+ if (host === "youtu.be") {
547
+ const id = parsed.pathname.split("/").filter(Boolean)[0];
548
+ return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;
549
+ }
550
+ if (host === "youtube-nocookie.com") {
551
+ return parsed.toString();
552
+ }
553
+ if (host === "vimeo.com") {
554
+ const id = parsed.pathname.split("/").filter(Boolean)[0];
555
+ return id ? `https://player.vimeo.com/video/${encodeURIComponent(id)}` : null;
556
+ }
557
+ if (host === "player.vimeo.com") {
558
+ return parsed.toString();
559
+ }
560
+ } catch {
561
+ return null;
562
+ }
563
+ return null;
564
+ }
565
+
566
+ // src/notion/generic-source.ts
567
+ import { cache } from "react";
568
+
569
+ // src/notion/property-mappers.ts
570
+ function isRecord(value) {
571
+ return Boolean(value && typeof value === "object");
572
+ }
573
+ function getPlainText(parts) {
574
+ if (!Array.isArray(parts)) return "";
575
+ return parts.map((part) => part.plain_text ?? "").join("").trim();
576
+ }
577
+ function getProperty(properties, key) {
578
+ return properties[key];
579
+ }
580
+ function firstPropertyOfType(properties, type) {
581
+ return Object.values(properties).find(
582
+ (property) => isRecord(property) && property.type === type
583
+ );
584
+ }
585
+ function getFirstTitleProperty(properties) {
586
+ const property = firstPropertyOfType(properties, "title");
587
+ return property ? getPlainText(property.title) : "";
588
+ }
589
+ function getRichTextProperty(properties, key) {
590
+ const property = getProperty(properties, key);
591
+ if (!property) return "";
592
+ if (property.type === "title") return getPlainText(property.title);
593
+ if (property.type === "rich_text") return getPlainText(property.rich_text);
594
+ if (property.type === "url") return String(property.url ?? "").trim();
595
+ if (property.type === "email") return String(property.email ?? "").trim();
596
+ if (property.type === "phone_number") {
597
+ return String(property.phone_number ?? "").trim();
598
+ }
599
+ return "";
600
+ }
601
+ function getDateProperty(properties, key) {
602
+ const property = getProperty(properties, key);
603
+ if (property?.type !== "date") return "";
604
+ const date = property.date;
605
+ return String(date?.start ?? "").trim();
606
+ }
607
+ function getFirstDateProperty(properties) {
608
+ const property = firstPropertyOfType(properties, "date");
609
+ if (!property) return "";
610
+ const date = property.date;
611
+ return String(date?.start ?? "").trim();
612
+ }
613
+ function getSelectProperty(properties, key) {
614
+ const property = getProperty(properties, key);
615
+ if (property?.type !== "select") return "";
616
+ const select = property.select;
617
+ return String(select?.name ?? "").trim();
618
+ }
619
+ function getCheckboxProperty(properties, key) {
620
+ const property = getProperty(properties, key);
621
+ if (property?.type !== "checkbox") return false;
622
+ return Boolean(property.checkbox);
623
+ }
624
+ function getRelationPageIds(properties, key) {
625
+ const property = getProperty(properties, key);
626
+ if (property?.type !== "relation" || !Array.isArray(property.relation)) {
627
+ return [];
628
+ }
629
+ return property.relation.map((item) => String(item.id ?? "").trim()).filter(Boolean);
630
+ }
631
+ function getTagsProperty(properties, key) {
632
+ const property = getProperty(properties, key);
633
+ if (property?.type === "multi_select" && Array.isArray(property.multi_select)) {
634
+ return property.multi_select.map((item) => String(item.name ?? "").trim()).filter(Boolean);
635
+ }
636
+ if (property?.type === "select") {
637
+ const select = property.select;
638
+ const name = String(select?.name ?? "").trim();
639
+ return name ? [name] : [];
640
+ }
641
+ return [];
642
+ }
643
+ function getFirstTagsProperty(properties) {
644
+ const multiSelect = firstPropertyOfType(properties, "multi_select");
645
+ if (multiSelect && Array.isArray(multiSelect.multi_select)) {
646
+ return multiSelect.multi_select.map((item) => String(item.name ?? "").trim()).filter(Boolean);
647
+ }
648
+ const select = firstPropertyOfType(properties, "select");
649
+ const name = String(select?.select?.name ?? "").trim();
650
+ return name ? [name] : [];
651
+ }
652
+ function getAuthorProperty(properties, key) {
653
+ const property = getProperty(properties, key);
654
+ if (!property) return "";
655
+ if (property.type === "people" && Array.isArray(property.people)) {
656
+ return property.people.map(
657
+ (person) => String(person.name ?? person.person?.email ?? "").trim()
658
+ ).filter(Boolean).join(", ");
659
+ }
660
+ return getRichTextProperty(properties, key);
661
+ }
662
+ function getFirstPeopleProperty(properties) {
663
+ const property = firstPropertyOfType(properties, "people");
664
+ if (!property || !Array.isArray(property.people)) return "";
665
+ return property.people.map(
666
+ (person) => String(person.name ?? person.person?.email ?? "").trim()
667
+ ).filter(Boolean).join(", ");
668
+ }
669
+ function pickPublishedFlag(properties) {
670
+ const published = getProperty(properties, "Published");
671
+ if (published?.type === "checkbox") {
672
+ return Boolean(published.checkbox);
673
+ }
674
+ const status = getProperty(properties, "Status");
675
+ if (status?.type === "status") {
676
+ const statusValue = status.status;
677
+ return String(statusValue?.name ?? "").trim().toLowerCase() === "published";
678
+ }
679
+ if (status?.type === "select") {
680
+ const statusValue = status.select;
681
+ return String(statusValue?.name ?? "").trim().toLowerCase() === "published";
682
+ }
683
+ return false;
684
+ }
685
+ function pickDescriptionFallback(description, title) {
686
+ return description.trim() || title.trim();
687
+ }
688
+ function isValidPublicSlug(slug) {
689
+ return /^[a-z0-9][a-z0-9-]{0,79}$/.test(slug);
690
+ }
691
+ function notionPageEditUrl(pageId, editBaseUrl) {
692
+ const compactPageId = pageId.replaceAll("-", "");
693
+ if (editBaseUrl?.includes("{pageId}")) {
694
+ return editBaseUrl.replaceAll("{pageId}", compactPageId);
695
+ }
696
+ return `https://www.notion.so/${compactPageId}`;
697
+ }
698
+ function compactNotionId(id) {
699
+ return id.replaceAll("-", "").toLowerCase();
700
+ }
701
+
702
+ // src/notion/generic-source.ts
703
+ function normalizePage(input) {
704
+ if (!input || typeof input !== "object") return null;
705
+ const page = input;
706
+ return page.id ? page : null;
707
+ }
708
+ function firstFieldName(value) {
709
+ if (Array.isArray(value)) return value[0];
710
+ return value;
711
+ }
712
+ function getFieldName(fields, key) {
713
+ return firstFieldName(fields[key]);
714
+ }
715
+ function pickPublishedFlagForModel(properties, fields) {
716
+ const publishedField = getFieldName(fields, "published") ?? "Published";
717
+ const published = properties[publishedField];
718
+ if (published?.type === "checkbox") return Boolean(published.checkbox);
719
+ const statusField = getFieldName(fields, "status") ?? "Status";
720
+ const status = properties[statusField];
721
+ if (status?.type === "status") {
722
+ const value = status.status;
723
+ return String(value?.name ?? "").trim().toLowerCase() === "published";
724
+ }
725
+ if (status?.type === "select") {
726
+ const value = status.select;
727
+ return String(value?.name ?? "").trim().toLowerCase() === "published";
728
+ }
729
+ return true;
730
+ }
731
+ function coverImageUrlForModel(page, fields) {
732
+ const coverField = fields.cover;
733
+ if (Array.isArray(coverField)) {
734
+ for (const field of coverField) {
735
+ const imageUrl = coverImageUrlForPage(page, field);
736
+ if (imageUrl) return imageUrl;
737
+ }
738
+ }
739
+ if (typeof coverField === "string") {
740
+ return coverImageUrlForPage(page, coverField);
741
+ }
742
+ return coverImageUrlForPage(page);
743
+ }
744
+ function mapExtraProperties(properties, fields) {
745
+ const result = {};
746
+ for (const [key, value] of Object.entries(fields)) {
747
+ const field = firstFieldName(value);
748
+ if (!field) continue;
749
+ const tags = getTagsProperty(properties, field);
750
+ result[key] = tags.length > 0 ? tags : getRichTextProperty(properties, field);
751
+ }
752
+ return result;
753
+ }
754
+ function mapNotionPageToGenericContentItem(model, page, options) {
755
+ const fields = model.source.fields;
756
+ const properties = isRecord2(page.properties) ? page.properties : {};
757
+ const titleField = getFieldName(fields, "title");
758
+ const slugField = getFieldName(fields, "slug");
759
+ const descriptionField = getFieldName(fields, "description");
760
+ const dateField = getFieldName(fields, "date");
761
+ const tagsField = getFieldName(fields, "tags");
762
+ const title = titleField ? getRichTextProperty(properties, titleField) : "";
763
+ const slug = slugField ? getRichTextProperty(properties, slugField).toLowerCase() : page.id.replaceAll("-", "").toLowerCase();
764
+ const description = pickDescriptionFallback(
765
+ descriptionField ? getRichTextProperty(properties, descriptionField) : "",
766
+ title
767
+ );
768
+ return {
769
+ pageId: page.id,
770
+ slug,
771
+ title,
772
+ description,
773
+ date: dateField ? getDateProperty(properties, dateField) : "",
774
+ tags: tagsField ? getTagsProperty(properties, tagsField) : [],
775
+ coverImage: coverImageUrlForModel(page, fields),
776
+ published: pickPublishedFlagForModel(properties, fields),
777
+ editUrl: notionPageEditUrl(page.id, options?.editBaseUrl),
778
+ properties: mapExtraProperties(properties, fields)
779
+ };
780
+ }
781
+ function isRenderableGenericContentItem(item) {
782
+ return Boolean(item.published && item.title && item.slug && isValidPublicSlug(item.slug));
783
+ }
784
+ function createGenericNotionContentSource(deps) {
785
+ return {
786
+ async listItems() {
787
+ const pages = [];
788
+ let cursor;
789
+ do {
790
+ const response = await deps.queryDataSource({ startCursor: cursor });
791
+ for (const item of response.results ?? []) {
792
+ const page = normalizePage(item);
793
+ if (page) pages.push(page);
794
+ }
795
+ cursor = response.next_cursor ?? void 0;
796
+ if (!response.has_more) break;
797
+ } while (cursor);
798
+ return pages.map(
799
+ (page) => mapNotionPageToGenericContentItem(deps.model, page, {
800
+ editBaseUrl: deps.editBaseUrl
801
+ })
802
+ ).filter(isRenderableGenericContentItem).sort((a, b) => b.date.localeCompare(a.date));
803
+ },
804
+ async getItemBySlug(slug) {
805
+ const items = await this.listItems();
806
+ const item = items.find((candidate) => candidate.slug === slug);
807
+ if (!item) return null;
808
+ return {
809
+ ...item,
810
+ blocks: await deps.getPageBlocks(item.pageId)
811
+ };
812
+ }
813
+ };
814
+ }
815
+ async function createDefaultGenericSource(model) {
816
+ if (!await hasNotionModelConfig(model)) return null;
817
+ const config = await getNotionConfigForModel(model);
818
+ const client = createNotionClient(config);
819
+ return createGenericNotionContentSource({
820
+ model,
821
+ dataSourceId: config.dataSourceId,
822
+ editBaseUrl: config.editBaseUrl,
823
+ queryDataSource: async ({ startCursor } = {}) => client.dataSources.query({
824
+ data_source_id: config.dataSourceId,
825
+ page_size: model.source.query.pageSize,
826
+ sorts: model.source.query.sorts ? [...model.source.query.sorts] : void 0,
827
+ filter_properties: model.source.query.filterProperties ? [...model.source.query.filterProperties] : void 0,
828
+ ...startCursor ? { start_cursor: startCursor } : {}
829
+ }),
830
+ getPageBlocks: (pageId) => listBlockChildrenDeep(client, pageId)
831
+ });
832
+ }
833
+ var sourceCache = cache(createDefaultGenericSource);
834
+ async function listGenericNotionContent(model) {
835
+ const source = await sourceCache(model);
836
+ if (!source) return [];
837
+ return source.listItems();
838
+ }
839
+ async function getGenericNotionContentBySlug(model, slug) {
840
+ const source = await sourceCache(model);
841
+ if (!source) return null;
842
+ return source.getItemBySlug(slug);
843
+ }
844
+ function isRecord2(value) {
845
+ return Boolean(value && typeof value === "object");
846
+ }
847
+
848
+ // src/notion/webhook.ts
849
+ var WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY = "notion:webhook:verification-token:v1";
850
+ function isRecord3(value) {
851
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
852
+ }
853
+ function readString2(source, key) {
854
+ if (!isRecord3(source)) return "";
855
+ const value = source[key];
856
+ return typeof value === "string" ? value.trim() : "";
857
+ }
858
+ function asRecords(value) {
859
+ if (Array.isArray(value)) return value.filter(isRecord3);
860
+ if (isRecord3(value)) return [value];
861
+ return [];
862
+ }
863
+ function nestedRecords(source, ...keys) {
864
+ const records = [];
865
+ for (const key of keys) {
866
+ const value = source[key];
867
+ if (isRecord3(value)) records.push(value);
868
+ }
869
+ return records;
870
+ }
871
+ function firstString(...values) {
872
+ return values.find(Boolean) ?? "";
873
+ }
874
+ function normalizeId(value) {
875
+ return value.replaceAll("-", "").toLowerCase();
876
+ }
877
+ function findIdByType(input, type) {
878
+ const stack = [input];
879
+ const seen = /* @__PURE__ */ new Set();
880
+ while (stack.length > 0) {
881
+ const current = stack.pop();
882
+ if (!current || seen.has(current)) continue;
883
+ seen.add(current);
884
+ if (readString2(current, "type") === type) {
885
+ const id = readString2(current, "id");
886
+ if (id) return id;
887
+ }
888
+ for (const value of Object.values(current)) {
889
+ if (isRecord3(value)) stack.push(value);
890
+ if (Array.isArray(value)) {
891
+ for (const item of value) {
892
+ if (isRecord3(item)) stack.push(item);
893
+ }
894
+ }
895
+ }
896
+ }
897
+ return "";
898
+ }
899
+ function findDataSourceId(input) {
900
+ const stack = [input];
901
+ const seen = /* @__PURE__ */ new Set();
902
+ while (stack.length > 0) {
903
+ const current = stack.pop();
904
+ if (!current || seen.has(current)) continue;
905
+ seen.add(current);
906
+ const direct = firstString(
907
+ readString2(current, "data_source_id"),
908
+ readString2(current, "source_id")
909
+ );
910
+ if (direct) return direct;
911
+ const type = readString2(current, "type");
912
+ if (type === "data_source") {
913
+ const id = readString2(current, "id");
914
+ if (id) return id;
915
+ }
916
+ for (const value of Object.values(current)) {
917
+ if (isRecord3(value)) stack.push(value);
918
+ if (Array.isArray(value)) {
919
+ for (const item of value) {
920
+ if (isRecord3(item)) stack.push(item);
921
+ }
922
+ }
923
+ }
924
+ }
925
+ return "";
926
+ }
927
+ function firstFieldName2(value) {
928
+ if (Array.isArray(value)) return value[0];
929
+ return value;
930
+ }
931
+ function defaultRouteIdFromProperties(model, page) {
932
+ const properties = isRecord3(page.properties) ? page.properties : {};
933
+ const slugField = firstFieldName2(model.source.fields.slug);
934
+ const slug = slugField ? getRichTextProperty(properties, slugField).toLowerCase() : "";
935
+ return isValidPublicSlug(slug) ? slug : "";
936
+ }
937
+ function defaultLocaleFromProperties(model, page) {
938
+ const properties = isRecord3(page.properties) ? page.properties : {};
939
+ const localeField = firstFieldName2(model.source.fields.locale);
940
+ return localeField ? getSelectProperty(properties, localeField) : "";
941
+ }
942
+ function eventKind(eventType) {
943
+ if (eventType.includes(".deleted")) return "delete";
944
+ if (eventType.includes(".created") || eventType.includes(".undeleted")) {
945
+ return "publish";
946
+ }
947
+ return "update";
948
+ }
949
+ function defaultGetModelDataSourceId(model) {
950
+ const fromEnv = process.env[model.source.dataSourceEnv];
951
+ if (fromEnv) return fromEnv;
952
+ return model.source.defaultDataSourceId ?? null;
953
+ }
954
+ function matchModelByDataSourceId(models, dataSourceId, getDataSourceId) {
955
+ if (!dataSourceId) return null;
956
+ const normalized = normalizeId(dataSourceId);
957
+ return models.find((model) => {
958
+ const configured = getDataSourceId(model);
959
+ return configured ? normalizeId(configured) === normalized : false;
960
+ }) ?? null;
961
+ }
962
+ function findModelForEvent(models, event, page, getDataSourceId) {
963
+ const modelId = firstString(
964
+ readString2(event, "modelId"),
965
+ readString2(event, "model_id"),
966
+ readString2(event.data, "modelId"),
967
+ readString2(event.data, "model_id")
968
+ );
969
+ if (modelId) {
970
+ return models.find((model) => model.id === modelId) ?? null;
971
+ }
972
+ const dataSourceId = firstString(
973
+ findDataSourceId(event),
974
+ findIdByType(event, "data_source"),
975
+ readString2(event, "data_source_id"),
976
+ readString2(event.data, "data_source_id"),
977
+ readString2(page?.parent, "data_source_id"),
978
+ readString2(page?.parent, "database_id")
979
+ );
980
+ return matchModelByDataSourceId(models, dataSourceId, getDataSourceId);
981
+ }
982
+ function pageIdForEvent(event, page) {
983
+ return firstString(
984
+ readString2(page, "id"),
985
+ findIdByType(event, "page"),
986
+ readString2(event, "page_id"),
987
+ readString2(event.data, "page_id")
988
+ );
989
+ }
990
+ function pageForEvent(event) {
991
+ let fallback = null;
992
+ for (const record of [
993
+ ...nestedRecords(isRecord3(event.data) ? event.data : {}, "page", "entity"),
994
+ ...nestedRecords(event, "page", "entity"),
995
+ event
996
+ ]) {
997
+ const type = readString2(record, "type");
998
+ if (type === "page" || readString2(record, "object") === "page") {
999
+ if (!readString2(record, "id")) continue;
1000
+ if (isRecord3(record.properties) || isRecord3(record.parent)) return record;
1001
+ fallback ??= record;
1002
+ }
1003
+ }
1004
+ return fallback;
1005
+ }
1006
+ function parseNotionWebhookPayload(payload, options) {
1007
+ if (!isRecord3(payload)) return { type: "events", events: [] };
1008
+ const verificationToken = readString2(payload, "verification_token");
1009
+ if (verificationToken) {
1010
+ return { type: "verification", verificationToken };
1011
+ }
1012
+ const getDataSourceId = options.getModelDataSourceId ?? defaultGetModelDataSourceId;
1013
+ const events = asRecords(payload.events ?? payload.event ?? payload).map(
1014
+ (event) => parseEvent(event, options.models, getDataSourceId)
1015
+ ).filter((event) => Boolean(event));
1016
+ return { type: "events", events };
1017
+ }
1018
+ function parseEvent(event, models, getDataSourceId) {
1019
+ const eventType = firstString(readString2(event, "type"), readString2(event, "event"));
1020
+ if (!eventType) return null;
1021
+ const page = pageForEvent(event);
1022
+ const model = findModelForEvent(
1023
+ models,
1024
+ event,
1025
+ page ?? void 0,
1026
+ getDataSourceId
1027
+ );
1028
+ if (!model) return null;
1029
+ const routeId = page ? model.resolveRouteId?.(page) ?? defaultRouteIdFromProperties(model, page) : "";
1030
+ const locale = page ? model.resolveLocale?.(page) ?? defaultLocaleFromProperties(model, page) : "";
1031
+ const dataSourceId = firstString(
1032
+ findDataSourceId(event),
1033
+ findIdByType(event, "data_source"),
1034
+ readString2(event, "data_source_id"),
1035
+ readString2(event.data, "data_source_id"),
1036
+ readString2(page?.parent, "data_source_id"),
1037
+ readString2(page?.parent, "database_id")
1038
+ );
1039
+ const dataSourceEvent = eventType.startsWith("data_source.") || eventType.startsWith("database.");
1040
+ return {
1041
+ id: readString2(event, "id") || void 0,
1042
+ eventType,
1043
+ modelId: model.id,
1044
+ pageId: pageIdForEvent(event, page ?? void 0) || void 0,
1045
+ dataSourceId: dataSourceId || void 0,
1046
+ routeId: routeId || void 0,
1047
+ locale: locale || void 0,
1048
+ kind: eventKind(eventType),
1049
+ includeApi: true,
1050
+ reason: page && routeId ? "page" : dataSourceEvent ? "data_source" : "page"
1051
+ };
1052
+ }
1053
+ async function defaultRetrieveNotionPage(pageId, model) {
1054
+ const config = await getNotionConfigForModel(model);
1055
+ const client = createNotionClient(config);
1056
+ const page = await client.pages.retrieve({ page_id: pageId });
1057
+ return page;
1058
+ }
1059
+ function resolveWebhookEventRoute(event, models, retrievePage) {
1060
+ if (event.routeId || !event.pageId) return Promise.resolve(event);
1061
+ if (event.kind === "delete") return Promise.resolve(event);
1062
+ const model = models.find((m) => m.id === event.modelId);
1063
+ if (!model) return Promise.resolve(event);
1064
+ return retrievePage(event.pageId, model).then((page) => {
1065
+ if (!page) return event;
1066
+ const pageRecord = page;
1067
+ const routeId = model.resolveRouteId?.(pageRecord) ?? defaultRouteIdFromProperties(model, pageRecord);
1068
+ const locale = model.resolveLocale?.(pageRecord) ?? defaultLocaleFromProperties(model, pageRecord);
1069
+ if (!routeId) return event;
1070
+ return {
1071
+ ...event,
1072
+ routeId,
1073
+ locale: locale || event.locale,
1074
+ reason: "page"
1075
+ };
1076
+ }).catch((error) => {
1077
+ const err = error;
1078
+ console.warn(
1079
+ JSON.stringify({
1080
+ tag: "notion_webhook_page_lookup_failed",
1081
+ eventId: event.id,
1082
+ eventType: event.eventType,
1083
+ modelId: event.modelId,
1084
+ pageId: event.pageId,
1085
+ code: err?.code,
1086
+ status: err?.status,
1087
+ message: err?.message ?? String(error)
1088
+ })
1089
+ );
1090
+ return event;
1091
+ });
1092
+ }
1093
+ async function parseNotionWebhookPayloadWithPageLookup(payload, options) {
1094
+ const parsed = parseNotionWebhookPayload(payload, options);
1095
+ if (parsed.type === "verification") return parsed;
1096
+ if (options?.lookupPages === false) return parsed;
1097
+ const retrievePage = options?.retrievePage ?? defaultRetrieveNotionPage;
1098
+ return {
1099
+ type: "events",
1100
+ events: await Promise.all(
1101
+ parsed.events.map(
1102
+ (event) => resolveWebhookEventRoute(event, options.models, retrievePage)
1103
+ )
1104
+ )
1105
+ };
1106
+ }
1107
+ function notionWebhookEventToRevalidateRequest(event) {
1108
+ return {
1109
+ modelId: event.modelId,
1110
+ pageId: event.pageId,
1111
+ routeId: event.routeId,
1112
+ locale: event.locale,
1113
+ kind: event.kind,
1114
+ includeApi: event.includeApi
1115
+ };
1116
+ }
1117
+ async function signNotionWebhookBody(body, verificationToken) {
1118
+ const key = await crypto.subtle.importKey(
1119
+ "raw",
1120
+ new TextEncoder().encode(verificationToken),
1121
+ { name: "HMAC", hash: "SHA-256" },
1122
+ false,
1123
+ ["sign"]
1124
+ );
1125
+ const signature = await crypto.subtle.sign(
1126
+ "HMAC",
1127
+ key,
1128
+ new TextEncoder().encode(body)
1129
+ );
1130
+ return Array.from(new Uint8Array(signature)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1131
+ }
1132
+ function normalizeNotionSignature(signature) {
1133
+ const value = String(signature ?? "").trim();
1134
+ const prefixed = value.match(/^sha256=(.+)$/i);
1135
+ return prefixed && prefixed[1] ? prefixed[1].trim() : value;
1136
+ }
1137
+ async function verifyNotionWebhookSignature(input) {
1138
+ const token = String(input.verificationToken ?? "").trim();
1139
+ const signature = normalizeNotionSignature(input.signature);
1140
+ if (!token || !signature) return false;
1141
+ const expected = await signNotionWebhookBody(input.body, token);
1142
+ if (expected.length !== signature.length) return false;
1143
+ let diff = 0;
1144
+ for (let index = 0; index < expected.length; index += 1) {
1145
+ diff |= expected.charCodeAt(index) ^ signature.charCodeAt(index);
1146
+ }
1147
+ return diff === 0;
1148
+ }
1149
+ async function verifyNotionWebhookSignatureWithTokens(input) {
1150
+ const tokens = Array.from(
1151
+ new Set(input.verificationTokens.map((token) => String(token ?? "").trim()))
1152
+ ).filter(Boolean);
1153
+ for (const token of tokens) {
1154
+ if (await verifyNotionWebhookSignature({
1155
+ body: input.body,
1156
+ signature: input.signature,
1157
+ verificationToken: token
1158
+ })) {
1159
+ return true;
1160
+ }
1161
+ }
1162
+ return false;
1163
+ }
1164
+ function readStoredWebhookVerificationToken(value) {
1165
+ if (typeof value === "string") return value.trim() || null;
1166
+ if (!isRecord3(value)) return null;
1167
+ const token = readString2(value, "token");
1168
+ return token || null;
1169
+ }
1170
+ async function getStoredNotionWebhookVerificationToken(cache2) {
1171
+ if (!cache2) return null;
1172
+ try {
1173
+ const value = await cache2.get(
1174
+ WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,
1175
+ { cacheTtl: 60 }
1176
+ );
1177
+ return readStoredWebhookVerificationToken(value);
1178
+ } catch (error) {
1179
+ console.warn(
1180
+ JSON.stringify({
1181
+ tag: "notion_webhook_token_lookup_failed",
1182
+ message: error instanceof Error ? error.message : String(error)
1183
+ })
1184
+ );
1185
+ return null;
1186
+ }
1187
+ }
1188
+ async function putStoredNotionWebhookVerificationToken(cache2, token) {
1189
+ const normalized = token.trim();
1190
+ if (!cache2 || !normalized) return false;
1191
+ await cache2.put(
1192
+ WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,
1193
+ {
1194
+ token: normalized,
1195
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1196
+ },
1197
+ {
1198
+ metadata: {
1199
+ source: "notion-webhook"
1200
+ }
1201
+ }
1202
+ );
1203
+ return true;
1204
+ }
1205
+ export {
1206
+ DEFAULT_NOTION_MOVIES_DATA_SOURCE_ID,
1207
+ NOTION_BLOCKS_CACHE_TTL_SECONDS,
1208
+ NOTION_LIST_CACHE_TTL_SECONDS,
1209
+ compactNotionId,
1210
+ coverImageUrlForPage,
1211
+ createGenericNotionContentSource,
1212
+ createNotionClient,
1213
+ deleteNotionContentCache,
1214
+ fileObjectForMediaBlock,
1215
+ firstImageUrlFromBlocks,
1216
+ flattenNotionBlockText,
1217
+ gatedMediaBlockForApi,
1218
+ getAuthorProperty,
1219
+ getCachedNotionBlocks,
1220
+ getCachedNotionValue,
1221
+ getCheckboxProperty,
1222
+ getDateProperty,
1223
+ getFirstDateProperty,
1224
+ getFirstPeopleProperty,
1225
+ getFirstTagsProperty,
1226
+ getFirstTitleProperty,
1227
+ getGenericNotionContentBySlug,
1228
+ getNotionClientConfig,
1229
+ getNotionConfig,
1230
+ getNotionConfigForModel,
1231
+ getNotionEditBaseUrl,
1232
+ getNotionMovieConfig,
1233
+ getNotionWebhookVerificationToken,
1234
+ getRelationPageIds,
1235
+ getRichTextProperty,
1236
+ getSelectProperty,
1237
+ getStoredNotionWebhookVerificationToken,
1238
+ getTagsProperty,
1239
+ hasNotionConfig,
1240
+ hasNotionModelConfig,
1241
+ hasNotionMovieConfig,
1242
+ isDirectVideoUrl,
1243
+ isNotionHostedFile,
1244
+ isRecord,
1245
+ isRenderableGenericContentItem,
1246
+ isValidPublicSlug,
1247
+ listBlockChildren,
1248
+ listBlockChildrenDeep,
1249
+ listGenericNotionContent,
1250
+ mapNotionPageToGenericContentItem,
1251
+ mediaUrlForBlock,
1252
+ normalizeNotionFileSource,
1253
+ notionBlockMediaPath,
1254
+ notionModelCachePrefix,
1255
+ notionModelListCacheKey,
1256
+ notionPageBlocksCacheKey,
1257
+ notionPageCachePrefix,
1258
+ notionPageCoverMediaPath,
1259
+ notionPageEditUrl,
1260
+ notionPagePropertyMediaPath,
1261
+ notionWebhookEventToRevalidateRequest,
1262
+ parseNotionWebhookPayload,
1263
+ parseNotionWebhookPayloadWithPageLookup,
1264
+ pickDescriptionFallback,
1265
+ pickFirstFilesPropertyValue,
1266
+ pickPageCoverFile,
1267
+ pickPublishedFlag,
1268
+ publicMediaBlockForApi,
1269
+ putCachedNotionBlocks,
1270
+ putCachedNotionValue,
1271
+ putStoredNotionWebhookVerificationToken,
1272
+ resolveNotionFileUrl,
1273
+ signNotionWebhookBody,
1274
+ verifyNotionWebhookSignature,
1275
+ verifyNotionWebhookSignatureWithTokens,
1276
+ videoEmbedUrl
1277
+ };
1278
+ //# sourceMappingURL=index.js.map