@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,585 @@
1
+ // src/media/routes/notion-media.ts
2
+ import { NextResponse } from "next/server";
3
+ import { getRequestExecutionContext } from "vinext/shims/request-context";
4
+
5
+ // src/cache/cache-keys.ts
6
+ var CACHE_ORIGIN = "https://cache.local";
7
+ var CACHE_NAMESPACE = "/__public-cache/v20260609a";
8
+ var NOTION_MEDIA_R2_PREFIX = "notion-media/v1";
9
+ function normalizePath(pathname) {
10
+ if (pathname === "/") return "/";
11
+ return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
12
+ }
13
+ function publicMediaVariantForAccept(accept) {
14
+ if (accept.includes("image/avif")) return "avif";
15
+ if (accept.includes("image/webp")) return "webp";
16
+ return "source";
17
+ }
18
+ function publicMediaCacheKeyForUrl(input, variant) {
19
+ const url = new URL(
20
+ `${CACHE_NAMESPACE}${normalizePath(input.pathname)}${input.search}`,
21
+ CACHE_ORIGIN
22
+ );
23
+ url.searchParams.set("__variant", variant);
24
+ url.searchParams.sort();
25
+ return url.toString();
26
+ }
27
+ function keySegment(value) {
28
+ return encodeURIComponent(value || "none");
29
+ }
30
+ function notionMediaR2KeyForUrl(input, variant) {
31
+ if (variant === "source") return null;
32
+ const version = input.searchParams.get("v");
33
+ if (!version) return null;
34
+ const path = normalizePath(input.pathname).split("/").filter(Boolean).map(keySegment).join("/");
35
+ const width = input.searchParams.get("w") ?? "source";
36
+ const quality = input.searchParams.get("q") ?? "source";
37
+ return [
38
+ NOTION_MEDIA_R2_PREFIX,
39
+ variant,
40
+ path,
41
+ `v-${keySegment(version)}`,
42
+ `w-${keySegment(width)}`,
43
+ `q-${keySegment(quality)}.${variant}`
44
+ ].join("/");
45
+ }
46
+
47
+ // src/notion/client.ts
48
+ import { Client } from "@notionhq/client";
49
+ function createNotionClient(config) {
50
+ return new Client({
51
+ auth: config.token,
52
+ baseUrl: config.apiBaseUrl,
53
+ notionVersion: "2026-03-11"
54
+ });
55
+ }
56
+
57
+ // src/notion/config.ts
58
+ function readProcessEnv() {
59
+ const env2 = {
60
+ NOTION_TOKEN: process.env.NOTION_TOKEN,
61
+ NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,
62
+ NOTION_MOVIES_DATA_SOURCE_ID: process.env.NOTION_MOVIES_DATA_SOURCE_ID,
63
+ NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,
64
+ NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,
65
+ NOTION_WEBHOOK_VERIFICATION_TOKEN: process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN
66
+ };
67
+ for (const [key, value] of Object.entries(process.env)) {
68
+ if (key.startsWith("NOTION_") && typeof value === "string") {
69
+ env2[key] = value;
70
+ }
71
+ }
72
+ return env2;
73
+ }
74
+ async function readWorkerEnv() {
75
+ try {
76
+ const mod = await import(
77
+ /* webpackIgnore: true */
78
+ "cloudflare:workers"
79
+ );
80
+ const env2 = {};
81
+ for (const [key, value] of Object.entries(mod.env ?? {})) {
82
+ if (key.startsWith("NOTION_") && typeof value === "string") {
83
+ env2[key] = value;
84
+ }
85
+ }
86
+ return env2;
87
+ } catch {
88
+ return {};
89
+ }
90
+ }
91
+ function readString(source, name) {
92
+ const value = String(source[name] ?? "").trim();
93
+ return value || void 0;
94
+ }
95
+ function mergeEnv(...sources) {
96
+ const merged = {};
97
+ for (const source of sources) {
98
+ for (const name of Object.keys(source)) {
99
+ if (!name.startsWith("NOTION_")) continue;
100
+ const value = readString(source, name);
101
+ if (value) merged[name] = value;
102
+ }
103
+ }
104
+ return merged;
105
+ }
106
+ async function readEnv() {
107
+ const processEnv = readProcessEnv();
108
+ return mergeEnv(await readWorkerEnv(), processEnv);
109
+ }
110
+ function readRequired(source, name) {
111
+ const value = readString(source, name);
112
+ if (!value) {
113
+ throw new Error(`Missing required Notion env: ${name}`);
114
+ }
115
+ return value;
116
+ }
117
+ async function getNotionClientConfig() {
118
+ const env2 = await readEnv();
119
+ return {
120
+ token: readRequired(env2, "NOTION_TOKEN"),
121
+ apiBaseUrl: readString(env2, "NOTION_API_BASE_URL")
122
+ };
123
+ }
124
+
125
+ // src/notion/media.ts
126
+ function normalizeNotionFileSource(input) {
127
+ const file = input;
128
+ if (!file || typeof file !== "object") return null;
129
+ if (file.type === "external") {
130
+ const url = String(file.external?.url ?? "").trim();
131
+ return url ? { type: "external", url } : null;
132
+ }
133
+ if (file.type === "file") {
134
+ const url = String(file.file?.url ?? "").trim();
135
+ if (!url) return null;
136
+ return {
137
+ type: "file",
138
+ url,
139
+ expiryTime: String(file.file?.expiry_time ?? "").trim() || null
140
+ };
141
+ }
142
+ return null;
143
+ }
144
+ function pickFirstFilesPropertyValue(property) {
145
+ const value = property;
146
+ if (!value || value.type !== "files" || !Array.isArray(value.files)) {
147
+ return null;
148
+ }
149
+ return value.files[0] ?? null;
150
+ }
151
+ function fileObjectForMediaBlock(block) {
152
+ const typed = block[block.type];
153
+ if (!typed || typeof typed !== "object") return null;
154
+ if (block.type === "image" || block.type === "video" || block.type === "file" || block.type === "pdf" || block.type === "audio") {
155
+ return typed;
156
+ }
157
+ return null;
158
+ }
159
+
160
+ // src/util/env.ts
161
+ import { env } from "cloudflare:workers";
162
+ var workerEnv = env;
163
+
164
+ // src/platform/runtime.ts
165
+ function cacheRequestForKey(key) {
166
+ return new Request(key, { method: "GET" });
167
+ }
168
+ function createCloudflarePublicCacheAdapter(cache) {
169
+ return {
170
+ kind: "cloudflare-cache",
171
+ async match(key) {
172
+ return await cache.match(cacheRequestForKey(key)) ?? null;
173
+ },
174
+ put(key, response) {
175
+ return cache.put(cacheRequestForKey(key), response);
176
+ },
177
+ delete(key) {
178
+ return cache.delete(cacheRequestForKey(key));
179
+ }
180
+ };
181
+ }
182
+ function createCloudflareKeyValueCacheAdapter(namespace) {
183
+ return {
184
+ kind: "workers-kv",
185
+ async get(key, options) {
186
+ return await namespace.get(key, {
187
+ type: "json",
188
+ cacheTtl: options?.cacheTtl
189
+ });
190
+ },
191
+ async put(key, value, options) {
192
+ await namespace.put(key, JSON.stringify(value), {
193
+ expirationTtl: options?.expirationTtl,
194
+ metadata: options?.metadata
195
+ });
196
+ },
197
+ delete(key) {
198
+ return namespace.delete(key);
199
+ },
200
+ async list(options) {
201
+ const result = await namespace.list({
202
+ prefix: options?.prefix,
203
+ limit: options?.limit,
204
+ cursor: options?.cursor
205
+ });
206
+ return {
207
+ keys: result.keys.map((key) => ({ name: key.name })),
208
+ cursor: result.list_complete ? void 0 : result.cursor,
209
+ listComplete: result.list_complete
210
+ };
211
+ }
212
+ };
213
+ }
214
+ function r2ObjectToStoredObject(object) {
215
+ return {
216
+ body: object.body,
217
+ size: object.size,
218
+ etag: object.etag,
219
+ contentType: object.httpMetadata?.contentType
220
+ };
221
+ }
222
+ function createCloudflareRuntimePlatform(env2, options) {
223
+ const database = env2.DB ? {
224
+ kind: "d1",
225
+ prepare(query) {
226
+ return env2.DB.prepare(query);
227
+ },
228
+ async batch(statements) {
229
+ return await env2.DB.batch(
230
+ statements
231
+ );
232
+ }
233
+ } : null;
234
+ const objectStorage = env2.ASSETS_BUCKET ? {
235
+ kind: "r2",
236
+ async get(key) {
237
+ const object = await env2.ASSETS_BUCKET?.get(key);
238
+ return object ? r2ObjectToStoredObject(object) : null;
239
+ },
240
+ async put(key, value, options2) {
241
+ await env2.ASSETS_BUCKET?.put(key, value, {
242
+ httpMetadata: {
243
+ contentType: options2?.contentType,
244
+ cacheControl: options2?.cacheControl
245
+ },
246
+ customMetadata: options2?.metadata
247
+ });
248
+ },
249
+ async delete(key) {
250
+ await env2.ASSETS_BUCKET?.delete(key);
251
+ },
252
+ async list(options2) {
253
+ const listed = await env2.ASSETS_BUCKET?.list({
254
+ prefix: options2?.prefix,
255
+ limit: options2?.limit
256
+ });
257
+ return listed?.objects.map((object) => ({
258
+ key: object.key,
259
+ size: object.size,
260
+ uploaded: object.uploaded
261
+ })) ?? [];
262
+ }
263
+ } : null;
264
+ const imageTransformer = env2.IMAGES ? {
265
+ kind: "cloudflare-images",
266
+ async transform(body, options2) {
267
+ const result = await env2.IMAGES.input(body).transform(options2.width ? { width: options2.width } : {}).output({
268
+ format: options2.format,
269
+ quality: options2.quality
270
+ });
271
+ return {
272
+ body: result.image(),
273
+ contentType: result.contentType(),
274
+ response: () => result.response()
275
+ };
276
+ }
277
+ } : null;
278
+ const keyValueCache = env2.CONTENT_CACHE ? createCloudflareKeyValueCacheAdapter(env2.CONTENT_CACHE) : null;
279
+ return {
280
+ id: "cloudflare-workers",
281
+ database,
282
+ objectStorage,
283
+ imageTransformer,
284
+ keyValueCache,
285
+ publicCache: options?.publicCache ? createCloudflarePublicCacheAdapter(options.publicCache) : null
286
+ };
287
+ }
288
+
289
+ // src/platform/cloudflare-runtime.ts
290
+ function getDefaultCloudflareCache() {
291
+ const globalWithCaches = globalThis;
292
+ return globalWithCaches.caches?.default ?? null;
293
+ }
294
+ function getRuntimePlatform() {
295
+ return createCloudflareRuntimePlatform(workerEnv, {
296
+ publicCache: getDefaultCloudflareCache()
297
+ });
298
+ }
299
+ function getPublicCache() {
300
+ const cache = getDefaultCloudflareCache();
301
+ if (!cache) {
302
+ throw new Error("Cloudflare cache binding not configured");
303
+ }
304
+ return createCloudflarePublicCacheAdapter(cache);
305
+ }
306
+
307
+ // src/platform/current.ts
308
+ function getRuntimePlatform2() {
309
+ return getRuntimePlatform();
310
+ }
311
+ function getPublicCache2() {
312
+ return getPublicCache();
313
+ }
314
+
315
+ // src/media/routes/notion-media.ts
316
+ var DEFAULT_WIDTH = 1200;
317
+ var MAX_WIDTH = 2400;
318
+ var DEFAULT_QUALITY = 75;
319
+ var MIN_QUALITY = 40;
320
+ var MAX_QUALITY = 85;
321
+ var CACHEABLE_STATUS = /* @__PURE__ */ new Set([200]);
322
+ var notionMediaRoute = {
323
+ async GET(_request, props) {
324
+ const { ref } = await props.params;
325
+ if (ref.some((part) => part === ".." || part.includes("/"))) {
326
+ return badRequest();
327
+ }
328
+ const url = new URL(_request.url);
329
+ url.pathname = buildNotionMediaPath(ref);
330
+ return notionMediaRoute.handle(new Request(url.toString(), _request));
331
+ },
332
+ async handle(request) {
333
+ const variant = publicMediaVariantForAccept(
334
+ request.headers.get("accept") ?? ""
335
+ );
336
+ return withEdgeMediaCache(request, variant, () => loadMedia(request));
337
+ }
338
+ };
339
+ function buildNotionMediaPath(ref) {
340
+ return `/api/notion/media/${ref.map(encodeURIComponent).join("/")}`;
341
+ }
342
+ function clampInt(value, min, max, fallback) {
343
+ const parsed = Number.parseInt(value ?? "", 10);
344
+ if (!Number.isFinite(parsed)) return fallback;
345
+ return Math.max(min, Math.min(max, parsed));
346
+ }
347
+ function cacheControl(request) {
348
+ const url = new URL(request.url);
349
+ if (url.searchParams.has("v")) {
350
+ return "public, max-age=31536000, immutable";
351
+ }
352
+ return "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400";
353
+ }
354
+ function canUseMediaCache(request) {
355
+ if (request.method !== "GET") return false;
356
+ return !request.headers.has("range");
357
+ }
358
+ function mediaCacheHeaders(response, request, state, r2State) {
359
+ const headers = new Headers(response.headers);
360
+ headers.set("Cache-Control", cacheControl(request));
361
+ headers.set("X-Notion-Media-Cache", state);
362
+ if (r2State) headers.set("X-Notion-Media-R2", r2State);
363
+ return headers;
364
+ }
365
+ async function responseFromR2Cache(request, variant) {
366
+ const url = new URL(request.url);
367
+ const r2Key = notionMediaR2KeyForUrl(url, variant);
368
+ const storage = getRuntimePlatform2().objectStorage;
369
+ if (!r2Key || !storage) return null;
370
+ const object = await storage.get(r2Key);
371
+ if (!object?.body) return null;
372
+ const contentType = object.contentType ?? (variant === "avif" ? "image/avif" : "image/webp");
373
+ const headers = new Headers();
374
+ headers.set("Content-Type", contentType);
375
+ headers.set("Cache-Control", cacheControl(request));
376
+ headers.set("Vary", "Accept");
377
+ headers.set("X-Notion-Media-Branch", "r2");
378
+ headers.set("X-Notion-Media-R2", "HIT");
379
+ if (object.etag) headers.set("ETag", object.etag);
380
+ return new Response(object.body, { headers });
381
+ }
382
+ async function withEdgeMediaCache(request, variant, load) {
383
+ if (!canUseMediaCache(request)) {
384
+ const response2 = await load();
385
+ return new Response(response2.body, {
386
+ status: response2.status,
387
+ headers: mediaCacheHeaders(response2, request, "BYPASS")
388
+ });
389
+ }
390
+ const url = new URL(request.url);
391
+ const cache = getPublicCache2();
392
+ const cacheKey = publicMediaCacheKeyForUrl(url, variant);
393
+ const cached = await cache.match(cacheKey);
394
+ if (cached) {
395
+ return new Response(cached.body, {
396
+ status: cached.status,
397
+ headers: mediaCacheHeaders(cached, request, "HIT")
398
+ });
399
+ }
400
+ const r2Response = await responseFromR2Cache(request, variant);
401
+ const response = r2Response ?? await load();
402
+ const headers = mediaCacheHeaders(response, request, "MISS");
403
+ const output = new Response(response.body, {
404
+ status: response.status,
405
+ headers
406
+ });
407
+ if (CACHEABLE_STATUS.has(response.status)) {
408
+ const toCache = output.clone();
409
+ getRequestExecutionContext()?.waitUntil(cache.put(cacheKey, toCache));
410
+ }
411
+ return output;
412
+ }
413
+ function mediaRedirect(url) {
414
+ const response = NextResponse.redirect(url, 302);
415
+ response.headers.set(
416
+ "Cache-Control",
417
+ "public, max-age=300, s-maxage=300, stale-while-revalidate=300"
418
+ );
419
+ return response;
420
+ }
421
+ function notFound() {
422
+ return NextResponse.json({ error: "Not found" }, { status: 404 });
423
+ }
424
+ function badRequest() {
425
+ return NextResponse.json({ error: "Invalid media ref" }, { status: 400 });
426
+ }
427
+ function forbidden() {
428
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
429
+ }
430
+ async function serveFileObject(input, request, options) {
431
+ const source = normalizeNotionFileSource(input);
432
+ if (!source) return notFound();
433
+ if (options?.redirectNotionHosted) return mediaRedirect(source.url);
434
+ return proxyNotionHostedFile(source.url, request);
435
+ }
436
+ async function proxyNotionHostedFile(url, request) {
437
+ const range = request.headers.get("range");
438
+ const ifRange = request.headers.get("if-range");
439
+ const upstreamHeaders = new Headers({
440
+ Accept: request.headers.get("accept") ?? "*/*"
441
+ });
442
+ if (range) upstreamHeaders.set("Range", range);
443
+ if (ifRange) upstreamHeaders.set("If-Range", ifRange);
444
+ const upstream = await fetch(url, {
445
+ headers: upstreamHeaders
446
+ });
447
+ if (!upstream.ok && upstream.status !== 416 || !upstream.body) {
448
+ return NextResponse.json(
449
+ { error: "Unable to fetch Notion media" },
450
+ { status: upstream.status || 502 }
451
+ );
452
+ }
453
+ const contentType = upstream.headers.get("content-type") ?? "";
454
+ const isImage = contentType.startsWith("image/");
455
+ const accept = request.headers.get("accept") ?? "";
456
+ const variant = publicMediaVariantForAccept(accept);
457
+ const urlObj = new URL(request.url);
458
+ const width = clampInt(
459
+ urlObj.searchParams.get("w"),
460
+ 64,
461
+ MAX_WIDTH,
462
+ DEFAULT_WIDTH
463
+ );
464
+ const quality = clampInt(
465
+ urlObj.searchParams.get("q"),
466
+ MIN_QUALITY,
467
+ MAX_QUALITY,
468
+ DEFAULT_QUALITY
469
+ );
470
+ let outputFormat = null;
471
+ if (variant === "avif") {
472
+ outputFormat = "image/avif";
473
+ } else if (variant === "webp") {
474
+ outputFormat = "image/webp";
475
+ }
476
+ const platform = getRuntimePlatform2();
477
+ const imageTransformer = platform.imageTransformer;
478
+ if (isImage && !range && outputFormat && imageTransformer) {
479
+ const r2Key = notionMediaR2KeyForUrl(urlObj, variant);
480
+ try {
481
+ const result = await imageTransformer.transform(upstream.body, {
482
+ width,
483
+ format: outputFormat,
484
+ quality
485
+ });
486
+ const transformed = result.response();
487
+ const headers2 = new Headers(transformed.headers);
488
+ headers2.set("Content-Type", result.contentType);
489
+ headers2.set("Cache-Control", cacheControl(request));
490
+ headers2.set("Vary", "Accept");
491
+ headers2.set("X-Notion-Media-Branch", "transformed");
492
+ headers2.set("X-Notion-Media-R2", r2Key ? "MISS" : "BYPASS");
493
+ headers2.set("X-Optimized-Width", String(width));
494
+ headers2.set("X-Optimized-Quality", String(quality));
495
+ if (transformed.body && r2Key && platform.objectStorage) {
496
+ const [clientBody, r2Body] = transformed.body.tee();
497
+ getRequestExecutionContext()?.waitUntil(
498
+ platform.objectStorage.put(r2Key, r2Body, {
499
+ contentType: result.contentType,
500
+ cacheControl: "public, max-age=31536000, immutable",
501
+ metadata: {
502
+ source: "notion",
503
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
504
+ width: String(width),
505
+ quality: String(quality)
506
+ }
507
+ })
508
+ );
509
+ return new Response(clientBody, { headers: headers2 });
510
+ }
511
+ return new Response(transformed.body, { headers: headers2 });
512
+ } catch {
513
+ }
514
+ }
515
+ const headers = new Headers();
516
+ for (const header of [
517
+ "accept-ranges",
518
+ "content-disposition",
519
+ "content-encoding",
520
+ "content-length",
521
+ "content-range",
522
+ "content-type",
523
+ "etag",
524
+ "last-modified"
525
+ ]) {
526
+ const value = upstream.headers.get(header);
527
+ if (value) headers.set(header, value);
528
+ }
529
+ if (contentType && !headers.has("Content-Type")) {
530
+ headers.set("Content-Type", contentType);
531
+ }
532
+ headers.set("Cache-Control", cacheControl(request));
533
+ headers.set("X-Notion-Media-Branch", "proxied");
534
+ return new Response(upstream.body, { status: upstream.status, headers });
535
+ }
536
+ async function loadMedia(request) {
537
+ const url = new URL(request.url);
538
+ const ref = readRefFromPathname(url.pathname);
539
+ if (!ref) return badRequest();
540
+ const client = createNotionClient(await getNotionClientConfig());
541
+ if (ref[0] === "page" && ref[1] && ref[2] === "cover") {
542
+ const page = await client.pages.retrieve({
543
+ page_id: ref[1]
544
+ });
545
+ return serveFileObject(page.cover, request);
546
+ }
547
+ if (ref[0] === "page" && ref[1] && ref[2] === "property" && ref[3]) {
548
+ const page = await client.pages.retrieve({
549
+ page_id: ref[1]
550
+ });
551
+ const propertyName = decodeURIComponent(ref.slice(3).join("/"));
552
+ return serveFileObject(
553
+ pickFirstFilesPropertyValue(page.properties?.[propertyName]),
554
+ request
555
+ );
556
+ }
557
+ if (ref[0] === "block" && ref[1]) {
558
+ const block = await client.blocks.retrieve({
559
+ block_id: ref[1]
560
+ });
561
+ if (block.type === "video") {
562
+ return forbidden();
563
+ }
564
+ return serveFileObject(fileObjectForMediaBlock(block), request, {
565
+ redirectNotionHosted: block.type === "audio" || block.type === "pdf" || block.type === "file"
566
+ });
567
+ }
568
+ return badRequest();
569
+ }
570
+ function readRefFromPathname(pathname) {
571
+ const prefix = "/api/notion/media/";
572
+ if (!pathname.startsWith(prefix)) return null;
573
+ const encoded = pathname.slice(prefix.length);
574
+ if (!encoded) return null;
575
+ return encoded.split("/").map((part) => decodeURIComponent(part));
576
+ }
577
+ var GET = notionMediaRoute.GET;
578
+ async function notionMediaRouteHandle(request) {
579
+ return notionMediaRoute.handle(request);
580
+ }
581
+ export {
582
+ notionMediaRoute,
583
+ notionMediaRouteHandle
584
+ };
585
+ //# sourceMappingURL=index.js.map