@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,490 @@
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
+ function readProcessEnv() {
13
+ const env = {
14
+ NOTION_TOKEN: process.env.NOTION_TOKEN,
15
+ NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,
16
+ NOTION_MOVIES_DATA_SOURCE_ID: process.env.NOTION_MOVIES_DATA_SOURCE_ID,
17
+ NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,
18
+ NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,
19
+ NOTION_WEBHOOK_VERIFICATION_TOKEN: process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN
20
+ };
21
+ for (const [key, value] of Object.entries(process.env)) {
22
+ if (key.startsWith("NOTION_") && typeof value === "string") {
23
+ env[key] = value;
24
+ }
25
+ }
26
+ return env;
27
+ }
28
+ async function readWorkerEnv() {
29
+ try {
30
+ const mod = await import(
31
+ /* webpackIgnore: true */
32
+ "cloudflare:workers"
33
+ );
34
+ const env = {};
35
+ for (const [key, value] of Object.entries(mod.env ?? {})) {
36
+ if (key.startsWith("NOTION_") && typeof value === "string") {
37
+ env[key] = value;
38
+ }
39
+ }
40
+ return env;
41
+ } catch {
42
+ return {};
43
+ }
44
+ }
45
+ function readString(source, name) {
46
+ const value = String(source[name] ?? "").trim();
47
+ return value || void 0;
48
+ }
49
+ function mergeEnv(...sources) {
50
+ const merged = {};
51
+ for (const source of sources) {
52
+ for (const name of Object.keys(source)) {
53
+ if (!name.startsWith("NOTION_")) continue;
54
+ const value = readString(source, name);
55
+ if (value) merged[name] = value;
56
+ }
57
+ }
58
+ return merged;
59
+ }
60
+ async function readEnv() {
61
+ const processEnv = readProcessEnv();
62
+ return mergeEnv(await readWorkerEnv(), processEnv);
63
+ }
64
+ function readRequired(source, name) {
65
+ const value = readString(source, name);
66
+ if (!value) {
67
+ throw new Error(`Missing required Notion env: ${name}`);
68
+ }
69
+ return value;
70
+ }
71
+ async function getNotionConfigForModel(model) {
72
+ const env = await readEnv();
73
+ const dataSourceId = readString(env, model.source.dataSourceEnv) ?? model.source.defaultDataSourceId;
74
+ if (!dataSourceId) {
75
+ throw new Error(`Missing required Notion env: ${model.source.dataSourceEnv}`);
76
+ }
77
+ return {
78
+ token: readRequired(env, model.source.tokenEnv),
79
+ dataSourceId,
80
+ apiBaseUrl: readString(env, "NOTION_API_BASE_URL"),
81
+ editBaseUrl: readString(env, "NOTION_EDIT_BASE_URL"),
82
+ webhookVerificationToken: readString(
83
+ env,
84
+ "NOTION_WEBHOOK_VERIFICATION_TOKEN"
85
+ )
86
+ };
87
+ }
88
+
89
+ // src/notion/property-mappers.ts
90
+ function getPlainText(parts) {
91
+ if (!Array.isArray(parts)) return "";
92
+ return parts.map((part) => part.plain_text ?? "").join("").trim();
93
+ }
94
+ function getProperty(properties, key) {
95
+ return properties[key];
96
+ }
97
+ function getRichTextProperty(properties, key) {
98
+ const property = getProperty(properties, key);
99
+ if (!property) return "";
100
+ if (property.type === "title") return getPlainText(property.title);
101
+ if (property.type === "rich_text") return getPlainText(property.rich_text);
102
+ if (property.type === "url") return String(property.url ?? "").trim();
103
+ if (property.type === "email") return String(property.email ?? "").trim();
104
+ if (property.type === "phone_number") {
105
+ return String(property.phone_number ?? "").trim();
106
+ }
107
+ return "";
108
+ }
109
+ function getSelectProperty(properties, key) {
110
+ const property = getProperty(properties, key);
111
+ if (property?.type !== "select") return "";
112
+ const select = property.select;
113
+ return String(select?.name ?? "").trim();
114
+ }
115
+ function isValidPublicSlug(slug) {
116
+ return /^[a-z0-9][a-z0-9-]{0,79}$/.test(slug);
117
+ }
118
+ function compactNotionId(id) {
119
+ return id.replaceAll("-", "").toLowerCase();
120
+ }
121
+
122
+ // src/notion/webhook.ts
123
+ var WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY = "notion:webhook:verification-token:v1";
124
+ function isRecord(value) {
125
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
126
+ }
127
+ function readString2(source, key) {
128
+ if (!isRecord(source)) return "";
129
+ const value = source[key];
130
+ return typeof value === "string" ? value.trim() : "";
131
+ }
132
+ function asRecords(value) {
133
+ if (Array.isArray(value)) return value.filter(isRecord);
134
+ if (isRecord(value)) return [value];
135
+ return [];
136
+ }
137
+ function nestedRecords(source, ...keys) {
138
+ const records = [];
139
+ for (const key of keys) {
140
+ const value = source[key];
141
+ if (isRecord(value)) records.push(value);
142
+ }
143
+ return records;
144
+ }
145
+ function firstString(...values) {
146
+ return values.find(Boolean) ?? "";
147
+ }
148
+ function normalizeId(value) {
149
+ return value.replaceAll("-", "").toLowerCase();
150
+ }
151
+ function findIdByType(input, type) {
152
+ const stack = [input];
153
+ const seen = /* @__PURE__ */ new Set();
154
+ while (stack.length > 0) {
155
+ const current = stack.pop();
156
+ if (!current || seen.has(current)) continue;
157
+ seen.add(current);
158
+ if (readString2(current, "type") === type) {
159
+ const id = readString2(current, "id");
160
+ if (id) return id;
161
+ }
162
+ for (const value of Object.values(current)) {
163
+ if (isRecord(value)) stack.push(value);
164
+ if (Array.isArray(value)) {
165
+ for (const item of value) {
166
+ if (isRecord(item)) stack.push(item);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ return "";
172
+ }
173
+ function findDataSourceId(input) {
174
+ const stack = [input];
175
+ const seen = /* @__PURE__ */ new Set();
176
+ while (stack.length > 0) {
177
+ const current = stack.pop();
178
+ if (!current || seen.has(current)) continue;
179
+ seen.add(current);
180
+ const direct = firstString(
181
+ readString2(current, "data_source_id"),
182
+ readString2(current, "source_id")
183
+ );
184
+ if (direct) return direct;
185
+ const type = readString2(current, "type");
186
+ if (type === "data_source") {
187
+ const id = readString2(current, "id");
188
+ if (id) return id;
189
+ }
190
+ for (const value of Object.values(current)) {
191
+ if (isRecord(value)) stack.push(value);
192
+ if (Array.isArray(value)) {
193
+ for (const item of value) {
194
+ if (isRecord(item)) stack.push(item);
195
+ }
196
+ }
197
+ }
198
+ }
199
+ return "";
200
+ }
201
+ function firstFieldName(value) {
202
+ if (Array.isArray(value)) return value[0];
203
+ return value;
204
+ }
205
+ function defaultRouteIdFromProperties(model, page) {
206
+ const properties = isRecord(page.properties) ? page.properties : {};
207
+ const slugField = firstFieldName(model.source.fields.slug);
208
+ const slug = slugField ? getRichTextProperty(properties, slugField).toLowerCase() : "";
209
+ return isValidPublicSlug(slug) ? slug : "";
210
+ }
211
+ function defaultLocaleFromProperties(model, page) {
212
+ const properties = isRecord(page.properties) ? page.properties : {};
213
+ const localeField = firstFieldName(model.source.fields.locale);
214
+ return localeField ? getSelectProperty(properties, localeField) : "";
215
+ }
216
+ function eventKind(eventType) {
217
+ if (eventType.includes(".deleted")) return "delete";
218
+ if (eventType.includes(".created") || eventType.includes(".undeleted")) {
219
+ return "publish";
220
+ }
221
+ return "update";
222
+ }
223
+ function defaultGetModelDataSourceId(model) {
224
+ const fromEnv = process.env[model.source.dataSourceEnv];
225
+ if (fromEnv) return fromEnv;
226
+ return model.source.defaultDataSourceId ?? null;
227
+ }
228
+ function matchModelByDataSourceId(models, dataSourceId, getDataSourceId) {
229
+ if (!dataSourceId) return null;
230
+ const normalized = normalizeId(dataSourceId);
231
+ return models.find((model) => {
232
+ const configured = getDataSourceId(model);
233
+ return configured ? normalizeId(configured) === normalized : false;
234
+ }) ?? null;
235
+ }
236
+ function findModelForEvent(models, event, page, getDataSourceId) {
237
+ const modelId = firstString(
238
+ readString2(event, "modelId"),
239
+ readString2(event, "model_id"),
240
+ readString2(event.data, "modelId"),
241
+ readString2(event.data, "model_id")
242
+ );
243
+ if (modelId) {
244
+ return models.find((model) => model.id === modelId) ?? null;
245
+ }
246
+ const dataSourceId = firstString(
247
+ findDataSourceId(event),
248
+ findIdByType(event, "data_source"),
249
+ readString2(event, "data_source_id"),
250
+ readString2(event.data, "data_source_id"),
251
+ readString2(page?.parent, "data_source_id"),
252
+ readString2(page?.parent, "database_id")
253
+ );
254
+ return matchModelByDataSourceId(models, dataSourceId, getDataSourceId);
255
+ }
256
+ function pageIdForEvent(event, page) {
257
+ return firstString(
258
+ readString2(page, "id"),
259
+ findIdByType(event, "page"),
260
+ readString2(event, "page_id"),
261
+ readString2(event.data, "page_id")
262
+ );
263
+ }
264
+ function pageForEvent(event) {
265
+ let fallback = null;
266
+ for (const record of [
267
+ ...nestedRecords(isRecord(event.data) ? event.data : {}, "page", "entity"),
268
+ ...nestedRecords(event, "page", "entity"),
269
+ event
270
+ ]) {
271
+ const type = readString2(record, "type");
272
+ if (type === "page" || readString2(record, "object") === "page") {
273
+ if (!readString2(record, "id")) continue;
274
+ if (isRecord(record.properties) || isRecord(record.parent)) return record;
275
+ fallback ??= record;
276
+ }
277
+ }
278
+ return fallback;
279
+ }
280
+ function parseNotionWebhookPayload(payload, options) {
281
+ if (!isRecord(payload)) return { type: "events", events: [] };
282
+ const verificationToken = readString2(payload, "verification_token");
283
+ if (verificationToken) {
284
+ return { type: "verification", verificationToken };
285
+ }
286
+ const getDataSourceId = options.getModelDataSourceId ?? defaultGetModelDataSourceId;
287
+ const events = asRecords(payload.events ?? payload.event ?? payload).map(
288
+ (event) => parseEvent(event, options.models, getDataSourceId)
289
+ ).filter((event) => Boolean(event));
290
+ return { type: "events", events };
291
+ }
292
+ function parseEvent(event, models, getDataSourceId) {
293
+ const eventType = firstString(readString2(event, "type"), readString2(event, "event"));
294
+ if (!eventType) return null;
295
+ const page = pageForEvent(event);
296
+ const model = findModelForEvent(
297
+ models,
298
+ event,
299
+ page ?? void 0,
300
+ getDataSourceId
301
+ );
302
+ if (!model) return null;
303
+ const routeId = page ? model.resolveRouteId?.(page) ?? defaultRouteIdFromProperties(model, page) : "";
304
+ const locale = page ? model.resolveLocale?.(page) ?? defaultLocaleFromProperties(model, page) : "";
305
+ const dataSourceId = firstString(
306
+ findDataSourceId(event),
307
+ findIdByType(event, "data_source"),
308
+ readString2(event, "data_source_id"),
309
+ readString2(event.data, "data_source_id"),
310
+ readString2(page?.parent, "data_source_id"),
311
+ readString2(page?.parent, "database_id")
312
+ );
313
+ const dataSourceEvent = eventType.startsWith("data_source.") || eventType.startsWith("database.");
314
+ return {
315
+ id: readString2(event, "id") || void 0,
316
+ eventType,
317
+ modelId: model.id,
318
+ pageId: pageIdForEvent(event, page ?? void 0) || void 0,
319
+ dataSourceId: dataSourceId || void 0,
320
+ routeId: routeId || void 0,
321
+ locale: locale || void 0,
322
+ kind: eventKind(eventType),
323
+ includeApi: true,
324
+ reason: page && routeId ? "page" : dataSourceEvent ? "data_source" : "page"
325
+ };
326
+ }
327
+ async function defaultRetrieveNotionPage(pageId, model) {
328
+ const config = await getNotionConfigForModel(model);
329
+ const client = createNotionClient(config);
330
+ const page = await client.pages.retrieve({ page_id: pageId });
331
+ return page;
332
+ }
333
+ function resolveWebhookEventRoute(event, models, retrievePage) {
334
+ if (event.routeId || !event.pageId) return Promise.resolve(event);
335
+ if (event.kind === "delete") return Promise.resolve(event);
336
+ const model = models.find((m) => m.id === event.modelId);
337
+ if (!model) return Promise.resolve(event);
338
+ return retrievePage(event.pageId, model).then((page) => {
339
+ if (!page) return event;
340
+ const pageRecord = page;
341
+ const routeId = model.resolveRouteId?.(pageRecord) ?? defaultRouteIdFromProperties(model, pageRecord);
342
+ const locale = model.resolveLocale?.(pageRecord) ?? defaultLocaleFromProperties(model, pageRecord);
343
+ if (!routeId) return event;
344
+ return {
345
+ ...event,
346
+ routeId,
347
+ locale: locale || event.locale,
348
+ reason: "page"
349
+ };
350
+ }).catch((error) => {
351
+ const err = error;
352
+ console.warn(
353
+ JSON.stringify({
354
+ tag: "notion_webhook_page_lookup_failed",
355
+ eventId: event.id,
356
+ eventType: event.eventType,
357
+ modelId: event.modelId,
358
+ pageId: event.pageId,
359
+ code: err?.code,
360
+ status: err?.status,
361
+ message: err?.message ?? String(error)
362
+ })
363
+ );
364
+ return event;
365
+ });
366
+ }
367
+ async function parseNotionWebhookPayloadWithPageLookup(payload, options) {
368
+ const parsed = parseNotionWebhookPayload(payload, options);
369
+ if (parsed.type === "verification") return parsed;
370
+ if (options?.lookupPages === false) return parsed;
371
+ const retrievePage = options?.retrievePage ?? defaultRetrieveNotionPage;
372
+ return {
373
+ type: "events",
374
+ events: await Promise.all(
375
+ parsed.events.map(
376
+ (event) => resolveWebhookEventRoute(event, options.models, retrievePage)
377
+ )
378
+ )
379
+ };
380
+ }
381
+ function notionWebhookEventToRevalidateRequest(event) {
382
+ return {
383
+ modelId: event.modelId,
384
+ pageId: event.pageId,
385
+ routeId: event.routeId,
386
+ locale: event.locale,
387
+ kind: event.kind,
388
+ includeApi: event.includeApi
389
+ };
390
+ }
391
+ async function signNotionWebhookBody(body, verificationToken) {
392
+ const key = await crypto.subtle.importKey(
393
+ "raw",
394
+ new TextEncoder().encode(verificationToken),
395
+ { name: "HMAC", hash: "SHA-256" },
396
+ false,
397
+ ["sign"]
398
+ );
399
+ const signature = await crypto.subtle.sign(
400
+ "HMAC",
401
+ key,
402
+ new TextEncoder().encode(body)
403
+ );
404
+ return Array.from(new Uint8Array(signature)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
405
+ }
406
+ function normalizeNotionSignature(signature) {
407
+ const value = String(signature ?? "").trim();
408
+ const prefixed = value.match(/^sha256=(.+)$/i);
409
+ return prefixed && prefixed[1] ? prefixed[1].trim() : value;
410
+ }
411
+ async function verifyNotionWebhookSignature(input) {
412
+ const token = String(input.verificationToken ?? "").trim();
413
+ const signature = normalizeNotionSignature(input.signature);
414
+ if (!token || !signature) return false;
415
+ const expected = await signNotionWebhookBody(input.body, token);
416
+ if (expected.length !== signature.length) return false;
417
+ let diff = 0;
418
+ for (let index = 0; index < expected.length; index += 1) {
419
+ diff |= expected.charCodeAt(index) ^ signature.charCodeAt(index);
420
+ }
421
+ return diff === 0;
422
+ }
423
+ async function verifyNotionWebhookSignatureWithTokens(input) {
424
+ const tokens = Array.from(
425
+ new Set(input.verificationTokens.map((token) => String(token ?? "").trim()))
426
+ ).filter(Boolean);
427
+ for (const token of tokens) {
428
+ if (await verifyNotionWebhookSignature({
429
+ body: input.body,
430
+ signature: input.signature,
431
+ verificationToken: token
432
+ })) {
433
+ return true;
434
+ }
435
+ }
436
+ return false;
437
+ }
438
+ function readStoredWebhookVerificationToken(value) {
439
+ if (typeof value === "string") return value.trim() || null;
440
+ if (!isRecord(value)) return null;
441
+ const token = readString2(value, "token");
442
+ return token || null;
443
+ }
444
+ async function getStoredNotionWebhookVerificationToken(cache) {
445
+ if (!cache) return null;
446
+ try {
447
+ const value = await cache.get(
448
+ WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,
449
+ { cacheTtl: 60 }
450
+ );
451
+ return readStoredWebhookVerificationToken(value);
452
+ } catch (error) {
453
+ console.warn(
454
+ JSON.stringify({
455
+ tag: "notion_webhook_token_lookup_failed",
456
+ message: error instanceof Error ? error.message : String(error)
457
+ })
458
+ );
459
+ return null;
460
+ }
461
+ }
462
+ async function putStoredNotionWebhookVerificationToken(cache, token) {
463
+ const normalized = token.trim();
464
+ if (!cache || !normalized) return false;
465
+ await cache.put(
466
+ WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,
467
+ {
468
+ token: normalized,
469
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
470
+ },
471
+ {
472
+ metadata: {
473
+ source: "notion-webhook"
474
+ }
475
+ }
476
+ );
477
+ return true;
478
+ }
479
+ export {
480
+ compactNotionId,
481
+ getStoredNotionWebhookVerificationToken,
482
+ notionWebhookEventToRevalidateRequest,
483
+ parseNotionWebhookPayload,
484
+ parseNotionWebhookPayloadWithPageLookup,
485
+ putStoredNotionWebhookVerificationToken,
486
+ signNotionWebhookBody,
487
+ verifyNotionWebhookSignature,
488
+ verifyNotionWebhookSignatureWithTokens
489
+ };
490
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/notion/client.ts","../../src/notion/config.ts","../../src/notion/property-mappers.ts","../../src/notion/webhook.ts"],"sourcesContent":["import { Client } from \"@notionhq/client\";\nimport type { NotionClientConfig } from \"./config\";\n\nexport function createNotionClient(config: NotionClientConfig) {\n return new Client({\n auth: config.token,\n baseUrl: config.apiBaseUrl,\n notionVersion: \"2026-03-11\",\n });\n}\n","import type { NotionContentModelLike } from \"./types\";\n\ntype NotionEnv = {\n NOTION_TOKEN?: string;\n NOTION_DATA_SOURCE_ID?: string;\n NOTION_MOVIES_DATA_SOURCE_ID?: string;\n NOTION_API_BASE_URL?: string;\n NOTION_EDIT_BASE_URL?: string;\n NOTION_WEBHOOK_VERIFICATION_TOKEN?: string;\n [key: string]: string | undefined;\n};\n\nexport const DEFAULT_NOTION_MOVIES_DATA_SOURCE_ID =\n \"371dc62d-0738-8015-a601-000bc3944fcb\";\n\nexport type NotionClientConfig = {\n token: string;\n apiBaseUrl?: string;\n};\n\nexport type NotionConfig = {\n token: string;\n dataSourceId: string;\n apiBaseUrl?: string;\n editBaseUrl?: string;\n webhookVerificationToken?: string;\n};\n\nfunction readProcessEnv(): NotionEnv {\n const env: NotionEnv = {\n NOTION_TOKEN: process.env.NOTION_TOKEN,\n NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,\n NOTION_MOVIES_DATA_SOURCE_ID: process.env.NOTION_MOVIES_DATA_SOURCE_ID,\n NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,\n NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,\n NOTION_WEBHOOK_VERIFICATION_TOKEN:\n process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN,\n };\n\n for (const [key, value] of Object.entries(process.env)) {\n if (key.startsWith(\"NOTION_\") && typeof value === \"string\") {\n env[key] = value;\n }\n }\n\n return env;\n}\n\nasync function readWorkerEnv(): Promise<NotionEnv> {\n try {\n const mod = (await import(\n /* webpackIgnore: true */ \"cloudflare:workers\"\n )) as unknown as { env?: Record<string, unknown> };\n const env: NotionEnv = {};\n for (const [key, value] of Object.entries(mod.env ?? {})) {\n if (key.startsWith(\"NOTION_\") && typeof value === \"string\") {\n env[key] = value;\n }\n }\n return env;\n } catch {\n return {};\n }\n}\n\nfunction readString(source: NotionEnv, name: string): string | undefined {\n const value = String(source[name] ?? \"\").trim();\n return value || undefined;\n}\n\nfunction mergeEnv(...sources: NotionEnv[]): NotionEnv {\n const merged: NotionEnv = {};\n\n for (const source of sources) {\n for (const name of Object.keys(source)) {\n if (!name.startsWith(\"NOTION_\")) continue;\n const value = readString(source, name);\n if (value) merged[name] = value;\n }\n }\n\n return merged;\n}\n\nasync function readEnv(): Promise<NotionEnv> {\n const processEnv = readProcessEnv();\n return mergeEnv(await readWorkerEnv(), processEnv);\n}\n\nfunction readRequired(\n source: NotionEnv,\n name: string\n): string {\n const value = readString(source, name);\n if (!value) {\n throw new Error(`Missing required Notion env: ${name}`);\n }\n return value;\n}\n\nexport function getNotionEditBaseUrl(): string {\n return readString(readProcessEnv(), \"NOTION_EDIT_BASE_URL\") ?? \"https://www.notion.so\";\n}\n\nexport async function hasNotionConfig(): Promise<boolean> {\n const env = await readEnv();\n return Boolean(\n readString(env, \"NOTION_TOKEN\") && readString(env, \"NOTION_DATA_SOURCE_ID\")\n );\n}\n\nexport async function hasNotionMovieConfig(): Promise<boolean> {\n const env = await readEnv();\n return Boolean(readString(env, \"NOTION_TOKEN\"));\n}\n\nexport async function hasNotionModelConfig(\n model: NotionContentModelLike\n): Promise<boolean> {\n const env = await readEnv();\n return Boolean(\n readString(env, \"NOTION_TOKEN\") &&\n (readString(env, model.source.dataSourceEnv) ||\n model.source.defaultDataSourceId)\n );\n}\n\nexport async function getNotionClientConfig(): Promise<NotionClientConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n };\n}\n\nexport async function getNotionConfig(): Promise<NotionConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n dataSourceId: readRequired(env, \"NOTION_DATA_SOURCE_ID\"),\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n\nexport async function getNotionWebhookVerificationToken(): Promise<\n string | undefined\n> {\n const env = await readEnv();\n return readString(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\");\n}\n\nexport async function getNotionMovieConfig(): Promise<NotionConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n dataSourceId:\n readString(env, \"NOTION_MOVIES_DATA_SOURCE_ID\") ??\n DEFAULT_NOTION_MOVIES_DATA_SOURCE_ID,\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n\nexport async function getNotionConfigForModel(\n model: NotionContentModelLike\n): Promise<NotionConfig> {\n const env = await readEnv();\n const dataSourceId =\n readString(env, model.source.dataSourceEnv) ??\n model.source.defaultDataSourceId;\n if (!dataSourceId) {\n throw new Error(`Missing required Notion env: ${model.source.dataSourceEnv}`);\n }\n\n return {\n token: readRequired(env, model.source.tokenEnv),\n dataSourceId,\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n","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","import type { KeyValueCacheAdapter } from \"../platform/runtime\";\nimport { createNotionClient } from \"./client\";\nimport { getNotionConfigForModel } from \"./config\";\nimport {\n compactNotionId,\n getRichTextProperty,\n getSelectProperty,\n isValidPublicSlug,\n} from \"./property-mappers\";\nimport type {\n NotionFieldMap,\n NotionGenericContentModel,\n NotionPageLike,\n} from \"./types\";\n\ntype JsonRecord = Record<string, unknown>;\n\ntype StoredWebhookVerificationToken = {\n token: string;\n updatedAt: string;\n};\n\nconst WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY =\n \"notion:webhook:verification-token:v1\";\n\nexport type NotionWebhookParseResult =\n | { type: \"verification\"; verificationToken: string }\n | { type: \"events\"; events: NotionWebhookEvent[] };\n\nexport type NotionWebhookEvent = {\n id?: string;\n eventType: string;\n modelId: string;\n pageId?: string;\n dataSourceId?: string;\n routeId?: string;\n locale?: string;\n kind: \"publish\" | \"update\" | \"delete\";\n includeApi: boolean;\n reason: \"page\" | \"data_source\";\n};\n\n/**\n * Generic shape that callers (i.e. the starter) supply so the webhook can\n * route events to the correct content model. Each entry must expose the\n * Notion data source id that the model is bound to, and a hook to derive a\n * route id and locale from the Notion page payload.\n */\nexport type NotionWebhookModelRegistration<\n TFields extends NotionFieldMap = NotionFieldMap,\n> = NotionGenericContentModel & {\n source: { fields: TFields };\n resolveRouteId?: (page: JsonRecord) => string;\n resolveLocale?: (page: JsonRecord) => string;\n};\n\nexport type NotionPageRetriever = (\n pageId: string,\n model: NotionGenericContentModel\n) => Promise<NotionPageLike | null>;\n\nexport type NotionWebhookParseOptions<\n TFields extends NotionFieldMap = NotionFieldMap,\n> = {\n models: ReadonlyArray<NotionWebhookModelRegistration<TFields>>;\n /**\n * Optional override for resolving the data source id of a model. Defaults\n * to `process.env[model.source.dataSourceEnv] ?? model.source.defaultDataSourceId`.\n */\n getModelDataSourceId?: (model: NotionWebhookModelRegistration<TFields>) => string | null;\n};\n\nexport type InvalidationKind = \"publish\" | \"update\" | \"delete\";\n\n/**\n * Request shape consumed by the revalidation pipeline. Mirrors the starter's\n * `ContentRevalidateRequest`; duplicated here so the package does not need\n * a runtime dependency on the starter.\n */\nexport type NotionWebhookRevalidateRequest = {\n modelId: string;\n pageId?: string;\n routeId?: string;\n previousRouteId?: string;\n locale?: string;\n kind?: InvalidationKind;\n includeApi?: boolean;\n};\n\nfunction isRecord(value: unknown): value is JsonRecord {\n return Boolean(value && typeof value === \"object\" && !Array.isArray(value));\n}\n\nfunction readString(source: unknown, key: string) {\n if (!isRecord(source)) return \"\";\n const value = source[key];\n return typeof value === \"string\" ? value.trim() : \"\";\n}\n\nfunction asRecords(value: unknown) {\n if (Array.isArray(value)) return value.filter(isRecord);\n if (isRecord(value)) return [value];\n return [];\n}\n\nfunction nestedRecords(source: JsonRecord, ...keys: string[]) {\n const records: JsonRecord[] = [];\n for (const key of keys) {\n const value = source[key];\n if (isRecord(value)) records.push(value);\n }\n return records;\n}\n\nfunction firstString(...values: string[]) {\n return values.find(Boolean) ?? \"\";\n}\n\nfunction normalizeId(value: string) {\n return value.replaceAll(\"-\", \"\").toLowerCase();\n}\n\nfunction findIdByType(input: JsonRecord, type: string): string {\n const stack = [input];\n const seen = new Set<JsonRecord>();\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (!current || seen.has(current)) continue;\n seen.add(current);\n\n if (readString(current, \"type\") === type) {\n const id = readString(current, \"id\");\n if (id) return id;\n }\n\n for (const value of Object.values(current)) {\n if (isRecord(value)) stack.push(value);\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isRecord(item)) stack.push(item);\n }\n }\n }\n }\n\n return \"\";\n}\n\nfunction findDataSourceId(input: JsonRecord): string {\n const stack = [input];\n const seen = new Set<JsonRecord>();\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (!current || seen.has(current)) continue;\n seen.add(current);\n\n const direct = firstString(\n readString(current, \"data_source_id\"),\n readString(current, \"source_id\")\n );\n if (direct) return direct;\n\n const type = readString(current, \"type\");\n if (type === \"data_source\") {\n const id = readString(current, \"id\");\n if (id) return id;\n }\n\n for (const value of Object.values(current)) {\n if (isRecord(value)) stack.push(value);\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isRecord(item)) stack.push(item);\n }\n }\n }\n }\n\n return \"\";\n}\n\nfunction firstFieldName(value: string | readonly string[] | undefined) {\n if (Array.isArray(value)) return value[0];\n return value;\n}\n\nfunction defaultRouteIdFromProperties(\n model: NotionGenericContentModel,\n page: JsonRecord\n) {\n const properties = isRecord(page.properties) ? page.properties : {};\n const slugField = firstFieldName(model.source.fields.slug);\n const slug = slugField\n ? getRichTextProperty(properties, slugField).toLowerCase()\n : \"\";\n return isValidPublicSlug(slug) ? slug : \"\";\n}\n\nfunction defaultLocaleFromProperties(model: NotionGenericContentModel, page: JsonRecord) {\n const properties = isRecord(page.properties) ? page.properties : {};\n const localeField = firstFieldName(model.source.fields.locale);\n return localeField ? getSelectProperty(properties, localeField) : \"\";\n}\n\nfunction eventKind(eventType: string): \"publish\" | \"update\" | \"delete\" {\n if (eventType.includes(\".deleted\")) return \"delete\";\n if (eventType.includes(\".created\") || eventType.includes(\".undeleted\")) {\n return \"publish\";\n }\n return \"update\";\n}\n\nfunction defaultGetModelDataSourceId(\n model: NotionGenericContentModel\n): string | null {\n const fromEnv = process.env[model.source.dataSourceEnv];\n if (fromEnv) return fromEnv;\n return model.source.defaultDataSourceId ?? null;\n}\n\nfunction matchModelByDataSourceId<\n TFields extends NotionFieldMap,\n>(\n models: ReadonlyArray<NotionWebhookModelRegistration<TFields>>,\n dataSourceId: string,\n getDataSourceId: (model: NotionWebhookModelRegistration<TFields>) => string | null\n) {\n if (!dataSourceId) return null;\n const normalized = normalizeId(dataSourceId);\n return (\n models.find((model) => {\n const configured = getDataSourceId(model);\n return configured ? normalizeId(configured) === normalized : false;\n }) ?? null\n );\n}\n\nfunction findModelForEvent<\n TFields extends NotionFieldMap,\n>(\n models: ReadonlyArray<NotionWebhookModelRegistration<TFields>>,\n event: JsonRecord,\n page: JsonRecord | undefined,\n getDataSourceId: (model: NotionWebhookModelRegistration<TFields>) => string | null\n) {\n const modelId = firstString(\n readString(event, \"modelId\"),\n readString(event, \"model_id\"),\n readString(event.data, \"modelId\"),\n readString(event.data, \"model_id\")\n );\n if (modelId) {\n return models.find((model) => model.id === modelId) ?? null;\n }\n\n const dataSourceId = firstString(\n findDataSourceId(event),\n findIdByType(event, \"data_source\"),\n readString(event, \"data_source_id\"),\n readString(event.data, \"data_source_id\"),\n readString(page?.parent, \"data_source_id\"),\n readString(page?.parent, \"database_id\")\n );\n return matchModelByDataSourceId(models, dataSourceId, getDataSourceId);\n}\n\nfunction pageIdForEvent(event: JsonRecord, page?: JsonRecord) {\n return firstString(\n readString(page, \"id\"),\n findIdByType(event, \"page\"),\n readString(event, \"page_id\"),\n readString(event.data, \"page_id\")\n );\n}\n\nfunction pageForEvent(event: JsonRecord) {\n let fallback: JsonRecord | null = null;\n for (const record of [\n ...nestedRecords(isRecord(event.data) ? event.data : {}, \"page\", \"entity\"),\n ...nestedRecords(event, \"page\", \"entity\"),\n event,\n ]) {\n const type = readString(record, \"type\");\n if (type === \"page\" || readString(record, \"object\") === \"page\") {\n if (!readString(record, \"id\")) continue;\n if (isRecord(record.properties) || isRecord(record.parent)) return record;\n fallback ??= record;\n }\n }\n return fallback;\n}\n\nexport function parseNotionWebhookPayload<\n TFields extends NotionFieldMap = NotionFieldMap,\n>(\n payload: unknown,\n options: NotionWebhookParseOptions<TFields>\n): NotionWebhookParseResult {\n if (!isRecord(payload)) return { type: \"events\", events: [] };\n\n const verificationToken = readString(payload, \"verification_token\");\n if (verificationToken) {\n return { type: \"verification\", verificationToken };\n }\n\n const getDataSourceId = options.getModelDataSourceId ?? defaultGetModelDataSourceId;\n\n const events = asRecords(payload.events ?? payload.event ?? payload)\n .map((event) =>\n parseEvent(event, options.models, getDataSourceId)\n )\n .filter((event): event is NotionWebhookEvent => Boolean(event));\n\n return { type: \"events\", events };\n}\n\nfunction parseEvent<\n TFields extends NotionFieldMap,\n>(\n event: JsonRecord,\n models: ReadonlyArray<NotionWebhookModelRegistration<TFields>>,\n getDataSourceId: (model: NotionWebhookModelRegistration<TFields>) => string | null\n): NotionWebhookEvent | null {\n const eventType = firstString(readString(event, \"type\"), readString(event, \"event\"));\n if (!eventType) return null;\n const page = pageForEvent(event);\n const model = findModelForEvent(\n models,\n event,\n page ?? undefined,\n getDataSourceId\n );\n if (!model) return null;\n\n const routeId = page\n ? (model.resolveRouteId?.(page) ??\n defaultRouteIdFromProperties(model, page))\n : \"\";\n const locale = page\n ? (model.resolveLocale?.(page) ?? defaultLocaleFromProperties(model, page))\n : \"\";\n const dataSourceId = firstString(\n findDataSourceId(event),\n findIdByType(event, \"data_source\"),\n readString(event, \"data_source_id\"),\n readString(event.data, \"data_source_id\"),\n readString(page?.parent, \"data_source_id\"),\n readString(page?.parent, \"database_id\")\n );\n const dataSourceEvent =\n eventType.startsWith(\"data_source.\") || eventType.startsWith(\"database.\");\n return {\n id: readString(event, \"id\") || undefined,\n eventType,\n modelId: model.id,\n pageId: pageIdForEvent(event, page ?? undefined) || undefined,\n dataSourceId: dataSourceId || undefined,\n routeId: routeId || undefined,\n locale: locale || undefined,\n kind: eventKind(eventType),\n includeApi: true,\n reason: page && routeId ? \"page\" : dataSourceEvent ? \"data_source\" : \"page\",\n };\n}\n\nasync function defaultRetrieveNotionPage(\n pageId: string,\n model: NotionGenericContentModel\n) {\n const config = await getNotionConfigForModel(model);\n const client = createNotionClient(config);\n const page = await client.pages.retrieve({ page_id: pageId });\n return page as NotionPageLike;\n}\n\nfunction resolveWebhookEventRoute<\n TFields extends NotionFieldMap,\n>(\n event: NotionWebhookEvent,\n models: ReadonlyArray<NotionWebhookModelRegistration<TFields>>,\n retrievePage: NotionPageRetriever\n): Promise<NotionWebhookEvent> {\n if (event.routeId || !event.pageId) return Promise.resolve(event);\n if (event.kind === \"delete\") return Promise.resolve(event);\n\n const model = models.find((m) => m.id === event.modelId);\n if (!model) return Promise.resolve(event);\n\n return retrievePage(event.pageId, model)\n .then((page) => {\n if (!page) return event;\n const pageRecord = page as unknown as JsonRecord;\n const routeId =\n model.resolveRouteId?.(pageRecord) ??\n defaultRouteIdFromProperties(model, pageRecord);\n const locale =\n model.resolveLocale?.(pageRecord) ??\n defaultLocaleFromProperties(model, pageRecord);\n if (!routeId) return event;\n return {\n ...event,\n routeId,\n locale: locale || event.locale,\n reason: \"page\" as const,\n };\n })\n .catch((error) => {\n const err = error as { code?: string; status?: number; message?: string };\n console.warn(\n JSON.stringify({\n tag: \"notion_webhook_page_lookup_failed\",\n eventId: event.id,\n eventType: event.eventType,\n modelId: event.modelId,\n pageId: event.pageId,\n code: err?.code,\n status: err?.status,\n message: err?.message ?? String(error),\n })\n );\n return event;\n });\n}\n\nexport async function parseNotionWebhookPayloadWithPageLookup<\n TFields extends NotionFieldMap = NotionFieldMap,\n>(\n payload: unknown,\n options: NotionWebhookParseOptions<TFields> & {\n retrievePage?: NotionPageRetriever;\n lookupPages?: boolean;\n }\n): Promise<NotionWebhookParseResult> {\n const parsed = parseNotionWebhookPayload(payload, options);\n if (parsed.type === \"verification\") return parsed;\n if (options?.lookupPages === false) return parsed;\n\n const retrievePage = options?.retrievePage ?? defaultRetrieveNotionPage;\n return {\n type: \"events\",\n events: await Promise.all(\n parsed.events.map((event) =>\n resolveWebhookEventRoute(event, options.models, retrievePage)\n )\n ),\n };\n}\n\nexport function notionWebhookEventToRevalidateRequest(\n event: NotionWebhookEvent\n): NotionWebhookRevalidateRequest {\n return {\n modelId: event.modelId,\n pageId: event.pageId,\n routeId: event.routeId,\n locale: event.locale,\n kind: event.kind,\n includeApi: event.includeApi,\n };\n}\n\nexport async function signNotionWebhookBody(body: string, verificationToken: string) {\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(verificationToken),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"]\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(body)\n );\n return Array.from(new Uint8Array(signature))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction normalizeNotionSignature(signature: string | null) {\n const value = String(signature ?? \"\").trim();\n const prefixed = value.match(/^sha256=(.+)$/i);\n return prefixed && prefixed[1] ? prefixed[1].trim() : value;\n}\n\nexport async function verifyNotionWebhookSignature(input: {\n body: string;\n signature: string | null;\n verificationToken?: string | null;\n}) {\n const token = String(input.verificationToken ?? \"\").trim();\n const signature = normalizeNotionSignature(input.signature);\n if (!token || !signature) return false;\n const expected = await signNotionWebhookBody(input.body, token);\n if (expected.length !== signature.length) return false;\n\n let diff = 0;\n for (let index = 0; index < expected.length; index += 1) {\n diff |= expected.charCodeAt(index) ^ signature.charCodeAt(index);\n }\n return diff === 0;\n}\n\nexport async function verifyNotionWebhookSignatureWithTokens(input: {\n body: string;\n signature: string | null;\n verificationTokens: Array<string | null | undefined>;\n}) {\n const tokens = Array.from(\n new Set(input.verificationTokens.map((token) => String(token ?? \"\").trim()))\n ).filter(Boolean);\n\n for (const token of tokens) {\n if (\n await verifyNotionWebhookSignature({\n body: input.body,\n signature: input.signature,\n verificationToken: token,\n })\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction readStoredWebhookVerificationToken(value: unknown) {\n if (typeof value === \"string\") return value.trim() || null;\n if (!isRecord(value)) return null;\n\n const token = readString(value, \"token\");\n return token || null;\n}\n\nexport async function getStoredNotionWebhookVerificationToken(\n cache: KeyValueCacheAdapter | null | undefined\n) {\n if (!cache) return null;\n\n try {\n const value = await cache.get<StoredWebhookVerificationToken | string>(\n WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,\n { cacheTtl: 60 }\n );\n return readStoredWebhookVerificationToken(value);\n } catch (error) {\n console.warn(\n JSON.stringify({\n tag: \"notion_webhook_token_lookup_failed\",\n message: error instanceof Error ? error.message : String(error),\n })\n );\n return null;\n }\n}\n\nexport async function putStoredNotionWebhookVerificationToken(\n cache: KeyValueCacheAdapter | null | undefined,\n token: string\n) {\n const normalized = token.trim();\n if (!cache || !normalized) return false;\n\n await cache.put<StoredWebhookVerificationToken>(\n WEBHOOK_VERIFICATION_TOKEN_CACHE_KEY,\n {\n token: normalized,\n updatedAt: new Date().toISOString(),\n },\n {\n metadata: {\n source: \"notion-webhook\",\n },\n }\n );\n return true;\n}\n\n// Re-exported for backwards compatibility with the starter.\nexport { compactNotionId };\n"],"mappings":"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","names":["readString"]}
@@ -0,0 +1,34 @@
1
+ import { RuntimeId } from './selection.js';
2
+
3
+ type RuntimeCapability = "server-rendering" | "edge-cache" | "relational-storage" | "object-storage" | "image-optimization" | "secrets" | "observability";
4
+ type RuntimeAdapterDefinition = {
5
+ id: RuntimeId;
6
+ label: string;
7
+ status: "active" | "partial" | "planned";
8
+ services: {
9
+ compute: string;
10
+ relationalStorage: string;
11
+ objectStorage: string;
12
+ imageOptimization: string;
13
+ cache: string;
14
+ authStorage: string;
15
+ };
16
+ capabilities: readonly RuntimeCapability[];
17
+ };
18
+ type RuntimeServiceStatus = {
19
+ database: boolean;
20
+ objectStorage: boolean;
21
+ imageTransformer: boolean;
22
+ publicCache: boolean;
23
+ };
24
+ declare const cloudflareWorkersAdapter: RuntimeAdapterDefinition;
25
+ declare const runtimeAdapters: readonly [RuntimeAdapterDefinition];
26
+ declare function getRuntimeAdapter(id: RuntimeAdapterDefinition["id"]): RuntimeAdapterDefinition | undefined;
27
+ declare function runtimeServiceStatus(platform: {
28
+ database: unknown;
29
+ objectStorage: unknown;
30
+ imageTransformer: unknown;
31
+ publicCache: unknown;
32
+ }): RuntimeServiceStatus;
33
+
34
+ export { type RuntimeAdapterDefinition, type RuntimeCapability, type RuntimeServiceStatus, cloudflareWorkersAdapter, getRuntimeAdapter, runtimeAdapters, runtimeServiceStatus };
@@ -0,0 +1,42 @@
1
+ // src/platform/capabilities.ts
2
+ var cloudflareWorkersAdapter = {
3
+ id: "cloudflare-workers",
4
+ label: "Cloudflare Workers + D1",
5
+ status: "active",
6
+ services: {
7
+ compute: "Cloudflare Workers via vinext",
8
+ relationalStorage: "D1 through the runtime SQL adapter",
9
+ objectStorage: "R2",
10
+ imageOptimization: "Cloudflare Images",
11
+ cache: "vinext CDN/data adapters and caches.default for media",
12
+ authStorage: "D1 users and signed cookies"
13
+ },
14
+ capabilities: [
15
+ "server-rendering",
16
+ "edge-cache",
17
+ "relational-storage",
18
+ "object-storage",
19
+ "image-optimization",
20
+ "secrets",
21
+ "observability"
22
+ ]
23
+ };
24
+ var runtimeAdapters = [cloudflareWorkersAdapter];
25
+ function getRuntimeAdapter(id) {
26
+ return runtimeAdapters.find((adapter) => adapter.id === id);
27
+ }
28
+ function runtimeServiceStatus(platform) {
29
+ return {
30
+ database: Boolean(platform.database),
31
+ objectStorage: Boolean(platform.objectStorage),
32
+ imageTransformer: Boolean(platform.imageTransformer),
33
+ publicCache: Boolean(platform.publicCache)
34
+ };
35
+ }
36
+ export {
37
+ cloudflareWorkersAdapter,
38
+ getRuntimeAdapter,
39
+ runtimeAdapters,
40
+ runtimeServiceStatus
41
+ };
42
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/platform/capabilities.ts"],"sourcesContent":["import type { RuntimeId } from \"./selection\";\n\nexport type RuntimeCapability =\n | \"server-rendering\"\n | \"edge-cache\"\n | \"relational-storage\"\n | \"object-storage\"\n | \"image-optimization\"\n | \"secrets\"\n | \"observability\";\n\nexport type RuntimeAdapterDefinition = {\n id: RuntimeId;\n label: string;\n status: \"active\" | \"partial\" | \"planned\";\n services: {\n compute: string;\n relationalStorage: string;\n objectStorage: string;\n imageOptimization: string;\n cache: string;\n authStorage: string;\n };\n capabilities: readonly RuntimeCapability[];\n};\n\nexport type RuntimeServiceStatus = {\n database: boolean;\n objectStorage: boolean;\n imageTransformer: boolean;\n publicCache: boolean;\n};\n\nexport const cloudflareWorkersAdapter: RuntimeAdapterDefinition = {\n id: \"cloudflare-workers\",\n label: \"Cloudflare Workers + D1\",\n status: \"active\",\n services: {\n compute: \"Cloudflare Workers via vinext\",\n relationalStorage: \"D1 through the runtime SQL adapter\",\n objectStorage: \"R2\",\n imageOptimization: \"Cloudflare Images\",\n cache: \"vinext CDN/data adapters and caches.default for media\",\n authStorage: \"D1 users and signed cookies\",\n },\n capabilities: [\n \"server-rendering\",\n \"edge-cache\",\n \"relational-storage\",\n \"object-storage\",\n \"image-optimization\",\n \"secrets\",\n \"observability\",\n ],\n};\n\nexport const runtimeAdapters = [cloudflareWorkersAdapter] as const;\n\nexport function getRuntimeAdapter(id: RuntimeAdapterDefinition[\"id\"]) {\n return runtimeAdapters.find((adapter) => adapter.id === id);\n}\n\nexport function runtimeServiceStatus(\n platform: {\n database: unknown;\n objectStorage: unknown;\n imageTransformer: unknown;\n publicCache: unknown;\n }\n): RuntimeServiceStatus {\n return {\n database: Boolean(platform.database),\n objectStorage: Boolean(platform.objectStorage),\n imageTransformer: Boolean(platform.imageTransformer),\n publicCache: Boolean(platform.publicCache),\n };\n}\n"],"mappings":";AAiCO,IAAM,2BAAqD;AAAA,EAChE,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,IACR,SAAS;AAAA,IACT,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,mBAAmB;AAAA,IACnB,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,kBAAkB,CAAC,wBAAwB;AAEjD,SAAS,kBAAkB,IAAoC;AACpE,SAAO,gBAAgB,KAAK,CAAC,YAAY,QAAQ,OAAO,EAAE;AAC5D;AAEO,SAAS,qBACd,UAMsB;AACtB,SAAO;AAAA,IACL,UAAU,QAAQ,SAAS,QAAQ;AAAA,IACnC,eAAe,QAAQ,SAAS,aAAa;AAAA,IAC7C,kBAAkB,QAAQ,SAAS,gBAAgB;AAAA,IACnD,aAAa,QAAQ,SAAS,WAAW;AAAA,EAC3C;AACF;","names":[]}