vybekiit 0.7.26 → 0.7.28

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 (274) hide show
  1. package/dist/bin.js +3910 -1300
  2. package/dist/global-guidance/language.md +443 -0
  3. package/dist/global-skills/add-ai/SKILL.md +1 -1
  4. package/dist/global-skills/add-analytics/SKILL.md +1 -1
  5. package/dist/global-skills/add-blog/SKILL.md +1 -1
  6. package/dist/global-skills/add-crud/SKILL.md +1 -1
  7. package/dist/global-skills/add-files/SKILL.md +1 -1
  8. package/dist/global-skills/add-images/SKILL.md +1 -1
  9. package/dist/global-skills/add-language/SKILL.md +1 -1
  10. package/dist/global-skills/add-notifications/SKILL.md +1 -1
  11. package/dist/global-skills/add-realtime/SKILL.md +1 -1
  12. package/dist/global-skills/add-route/SKILL.md +1 -1
  13. package/dist/global-skills/add-search/SKILL.md +1 -1
  14. package/dist/global-skills/add-signin/SKILL.md +1 -1
  15. package/dist/global-skills/add-teams/SKILL.md +1 -1
  16. package/dist/global-skills/add-upload/SKILL.md +1 -1
  17. package/dist/global-skills/aws-cdk/SKILL.md +19 -5
  18. package/dist/global-skills/aws-cdk/references/fast-deployments.md +191 -0
  19. package/dist/global-skills/aws-cdk/references/troubleshooting-deployment.md +16 -0
  20. package/dist/global-skills/aws-cloudformation/SKILL.md +16 -26
  21. package/dist/global-skills/aws-cloudformation/references/check-cloudformation-template-compliance.script.md +7 -3
  22. package/dist/global-skills/aws-cloudformation/references/cloudformation-language-server.md +177 -0
  23. package/dist/global-skills/aws-cloudformation/references/cloudformation-pre-deploy-validation.script.md +8 -2
  24. package/dist/global-skills/aws-cloudformation/references/persist-template-context.script.md +5 -8
  25. package/dist/global-skills/aws-cloudformation/references/retrieve-template-context.script.md +1 -1
  26. package/dist/global-skills/aws-cloudformation/references/security-considerations.md +51 -0
  27. package/dist/global-skills/aws-cloudformation/references/troubleshoot-failed-stack.script.md +138 -0
  28. package/dist/global-skills/aws-cloudformation/references/{validate-cloudformation-template.script.md → validate-with-cfn-lint.script.md} +15 -27
  29. package/dist/global-skills/aws-cloudformation/references/validate-with-cloudformation-validate.script.md +181 -0
  30. package/dist/global-skills/aws-cloudformation/references/validation-tool-selection.md +44 -0
  31. package/dist/global-skills/aws-serverless/SKILL.md +9 -1
  32. package/dist/global-skills/aws-serverless/references/architecture.md +3 -1
  33. package/dist/global-skills/aws-serverless/references/lambda.md +3 -1
  34. package/dist/global-skills/aws-serverless/references/orchestration.md +1 -0
  35. package/dist/global-skills/back-up-my-code/SKILL.md +1 -1
  36. package/dist/global-skills/better-auth-best-practices/SKILL.md +18 -8
  37. package/dist/global-skills/buy-domain/SKILL.md +1 -1
  38. package/dist/global-skills/check-safety/SKILL.md +1 -1
  39. package/dist/global-skills/configure-capabilities/SKILL.md +1 -1
  40. package/dist/global-skills/connect-account/SKILL.md +1 -1
  41. package/dist/global-skills/connect-account-backend/SKILL.md +1 -1
  42. package/dist/global-skills/design-my-data/SKILL.md +1 -1
  43. package/dist/global-skills/doctor/SKILL.md +1 -1
  44. package/dist/global-skills/eas-app-stores/SKILL.md +31 -15
  45. package/dist/global-skills/eas-app-stores/agents/openai.yaml +2 -2
  46. package/dist/global-skills/eas-app-stores/references/ios-app-store.md +37 -32
  47. package/dist/global-skills/eas-app-stores/references/native-ios.md +167 -0
  48. package/dist/global-skills/eas-app-stores/references/play-store.md +3 -7
  49. package/dist/global-skills/eas-app-stores/references/testflight.md +39 -35
  50. package/dist/global-skills/eas-simulator/SKILL.md +48 -26
  51. package/dist/global-skills/eas-simulator/references/controllers.md +32 -3
  52. package/dist/global-skills/eas-simulator/references/run-your-app.md +34 -4
  53. package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -4
  54. package/dist/global-skills/eas-update/SKILL.md +146 -0
  55. package/dist/global-skills/eas-update/agents/openai.yaml +4 -0
  56. package/dist/global-skills/expo-animation/RECIPES.md +2 -2
  57. package/dist/global-skills/expo-animation/SKILL.md +9 -2
  58. package/dist/global-skills/expo-brownfield/SKILL.md +18 -11
  59. package/dist/global-skills/expo-brownfield/agents/openai.yaml +2 -2
  60. package/dist/global-skills/expo-brownfield/references/brownfield-integrated.md +94 -69
  61. package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +40 -42
  62. package/dist/global-skills/expo-brownfield/references/comparison.md +5 -5
  63. package/dist/global-skills/expo-brownfield/references/feature-integration.md +163 -0
  64. package/dist/global-skills/expo-brownfield/references/troubleshooting.md +17 -17
  65. package/dist/global-skills/expo-brownfield/references/version-compatibility.md +40 -0
  66. package/dist/global-skills/expo-data-fetching/SKILL.md +27 -6
  67. package/dist/global-skills/expo-design-system/SKILL.md +27 -7
  68. package/dist/global-skills/expo-design-system/references/audit.md +7 -2
  69. package/dist/global-skills/expo-design-system/references/native-slop.md +74 -0
  70. package/dist/global-skills/expo-examples/SKILL.md +0 -1
  71. package/dist/global-skills/expo-examples/references/catalog.md +1 -1
  72. package/dist/global-skills/expo-migrate-module/SKILL.md +21 -10
  73. package/dist/global-skills/expo-migrate-module/references/compatibility.md +80 -23
  74. package/dist/global-skills/expo-migrate-module/references/migration-map.md +162 -11
  75. package/dist/global-skills/expo-native-ui/SKILL.md +25 -16
  76. package/dist/global-skills/expo-native-ui/agents/openai.yaml +2 -2
  77. package/dist/global-skills/expo-native-ui/references/controls.md +5 -46
  78. package/dist/global-skills/expo-native-ui/references/icons.md +21 -2
  79. package/dist/global-skills/expo-native-ui/references/media.md +15 -20
  80. package/dist/global-skills/expo-native-ui/references/visual-effects.md +12 -11
  81. package/dist/global-skills/expo-overview/SKILL.md +17 -12
  82. package/dist/global-skills/expo-router/SKILL.md +5 -3
  83. package/dist/global-skills/expo-router/references/tabs.md +5 -5
  84. package/dist/global-skills/expo-upgrade/SKILL.md +3 -1
  85. package/dist/global-skills/expo-web-to-native/references/false-friends.md +2 -2
  86. package/dist/global-skills/expo-web-to-native/references/native-patterns.md +1 -1
  87. package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +13 -16
  88. package/dist/global-skills/firebase-ai-logic-basics/references/ios_setup.md +4 -5
  89. package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_android.md +4 -4
  90. package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_web.md +3 -3
  91. package/dist/global-skills/firebase-auth-basics/SKILL.md +11 -6
  92. package/dist/global-skills/firebase-auth-basics/references/client_sdk_android.md +4 -5
  93. package/dist/global-skills/firebase-auth-basics/references/client_sdk_web.md +3 -3
  94. package/dist/global-skills/firebase-auth-basics/references/flutter_setup.md +24 -25
  95. package/dist/global-skills/firebase-auth-basics/references/security_rules.md +4 -2
  96. package/dist/global-skills/firebase-crashlytics/references/android_setup.md +7 -4
  97. package/dist/global-skills/firebase-crashlytics/references/ios_setup.md +2 -3
  98. package/dist/global-skills/firebase-data-connect/SKILL.md +2 -1
  99. package/dist/global-skills/firebase-data-connect/examples.md +4 -4
  100. package/dist/global-skills/firebase-data-connect/reference/config.md +5 -4
  101. package/dist/global-skills/firebase-data-connect/reference/realtime.md +1 -2
  102. package/dist/global-skills/firebase-data-connect/reference/sdk_flutter.md +2 -2
  103. package/dist/global-skills/firebase-data-connect/reference/sdk_ios.md +2 -2
  104. package/dist/global-skills/firebase-data-connect/reference/sdk_web.md +17 -6
  105. package/dist/global-skills/firebase-data-connect/reference/security.md +5 -5
  106. package/dist/global-skills/firebase-data-connect/templates.md +2 -1
  107. package/dist/global-skills/firebase-firestore/SKILL.md +20 -8
  108. package/dist/global-skills/firebase-firestore/references/enterprise/android_sdk_usage.md +5 -4
  109. package/dist/global-skills/firebase-firestore/references/enterprise/data_model.md +12 -3
  110. package/dist/global-skills/firebase-firestore/references/enterprise/indexes.md +16 -18
  111. package/dist/global-skills/firebase-firestore/references/enterprise/provisioning.md +1 -1
  112. package/dist/global-skills/firebase-firestore/references/enterprise/python_sdk_usage.md +5 -1
  113. package/dist/global-skills/firebase-firestore/references/enterprise/web_sdk_usage.md +7 -7
  114. package/dist/global-skills/firebase-firestore/references/standard/android_sdk_usage.md +5 -5
  115. package/dist/global-skills/firebase-firestore/references/standard/flutter_setup.md +4 -4
  116. package/dist/global-skills/firebase-firestore/references/standard/indexes.md +16 -18
  117. package/dist/global-skills/firebase-firestore/references/standard/provisioning.md +1 -1
  118. package/dist/global-skills/firebase-remote-config-basics/SKILL.md +0 -5
  119. package/dist/global-skills/firebase-remote-config-basics/references/android_setup.md +36 -8
  120. package/dist/global-skills/firebase-remote-config-basics/references/ios_setup.md +1 -7
  121. package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +17 -6
  122. package/dist/global-skills/{firebase-firestore/references/standard/security_rules.md → firestore-rules-creation/SKILL.md} +24 -13
  123. package/dist/global-skills/go-live/SKILL.md +1 -1
  124. package/dist/global-skills/grow-my-customers/SKILL.md +23 -0
  125. package/dist/global-skills/harden/SKILL.md +1 -1
  126. package/dist/global-skills/instrument-feature-flags/SKILL.md +25 -25
  127. package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +141 -285
  128. package/dist/global-skills/instrument-feature-flags/references/android.md +6 -15
  129. package/dist/global-skills/instrument-feature-flags/references/api.md +4 -11
  130. package/dist/global-skills/instrument-feature-flags/references/best-practices.md +1 -13
  131. package/dist/global-skills/instrument-feature-flags/references/django.md +14 -27
  132. package/dist/global-skills/instrument-feature-flags/references/dotnet.md +20 -79
  133. package/dist/global-skills/instrument-feature-flags/references/elixir.md +1 -9
  134. package/dist/global-skills/instrument-feature-flags/references/flask.md +13 -13
  135. package/dist/global-skills/instrument-feature-flags/references/flutter.md +3 -24
  136. package/dist/global-skills/instrument-feature-flags/references/go.md +3 -15
  137. package/dist/global-skills/instrument-feature-flags/references/ios.md +4 -17
  138. package/dist/global-skills/instrument-feature-flags/references/java.md +5 -13
  139. package/dist/global-skills/instrument-feature-flags/references/laravel.md +13 -17
  140. package/dist/global-skills/instrument-feature-flags/references/next-js.md +25 -32
  141. package/dist/global-skills/instrument-feature-flags/references/nodejs.md +8 -15
  142. package/dist/global-skills/instrument-feature-flags/references/php.md +1 -15
  143. package/dist/global-skills/instrument-feature-flags/references/python.md +2 -15
  144. package/dist/global-skills/instrument-feature-flags/references/react-native.md +13 -15
  145. package/dist/global-skills/instrument-feature-flags/references/react.md +17 -21
  146. package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +37 -83
  147. package/dist/global-skills/instrument-feature-flags/references/ruby.md +2 -15
  148. package/dist/global-skills/instrument-feature-flags/references/rust.md +13 -25
  149. package/dist/global-skills/instrument-feature-flags/references/usage.md +14 -63
  150. package/dist/global-skills/instrument-feature-flags/references/web.md +9 -14
  151. package/dist/global-skills/instrument-product-analytics/SKILL.md +29 -29
  152. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +3 -1
  153. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +3 -1
  154. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-static.md +3 -1
  155. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-view-transitions.md +3 -1
  156. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +3 -1
  157. package/dist/global-skills/instrument-product-analytics/references/android.md +72 -107
  158. package/dist/global-skills/instrument-product-analytics/references/angular.md +26 -28
  159. package/dist/global-skills/instrument-product-analytics/references/astro.md +13 -24
  160. package/dist/global-skills/instrument-product-analytics/references/configuration.md +45 -63
  161. package/dist/global-skills/instrument-product-analytics/references/django.md +14 -27
  162. package/dist/global-skills/instrument-product-analytics/references/dotnet.md +20 -79
  163. package/dist/global-skills/instrument-product-analytics/references/elixir.md +47 -49
  164. package/dist/global-skills/instrument-product-analytics/references/flask.md +13 -13
  165. package/dist/global-skills/instrument-product-analytics/references/flutter.md +60 -90
  166. package/dist/global-skills/instrument-product-analytics/references/go.md +17 -56
  167. package/dist/global-skills/instrument-product-analytics/references/identify-users.md +15 -15
  168. package/dist/global-skills/instrument-product-analytics/references/ios.md +11 -15
  169. package/dist/global-skills/instrument-product-analytics/references/laravel.md +13 -17
  170. package/dist/global-skills/instrument-product-analytics/references/next-js.md +25 -32
  171. package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +13 -27
  172. package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +14 -28
  173. package/dist/global-skills/instrument-product-analytics/references/php.md +33 -84
  174. package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +229 -9
  175. package/dist/global-skills/instrument-product-analytics/references/python.md +415 -106
  176. package/dist/global-skills/instrument-product-analytics/references/react-native.md +161 -155
  177. package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +12 -33
  178. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +15 -33
  179. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +12 -33
  180. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +26 -41
  181. package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +37 -83
  182. package/dist/global-skills/instrument-product-analytics/references/ruby.md +48 -108
  183. package/dist/global-skills/instrument-product-analytics/references/svelte.md +18 -24
  184. package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +17 -19
  185. package/dist/global-skills/instrument-product-analytics/references/usage.md +14 -63
  186. package/dist/global-skills/instrument-product-analytics/references/vue-js.md +29 -28
  187. package/dist/global-skills/manifest.json +8 -2
  188. package/dist/global-skills/mongodb-search-and-ai/SKILL.md +28 -37
  189. package/dist/global-skills/mongodb-search-and-ai/references/automated-embedding.md +438 -0
  190. package/dist/global-skills/mongodb-search-and-ai/references/hybrid-search.md +60 -4
  191. package/dist/global-skills/mongodb-search-and-ai/references/vector-search.md +46 -108
  192. package/dist/global-skills/neon/SKILL.md +207 -213
  193. package/dist/global-skills/neon/references/auth.md +12 -0
  194. package/dist/global-skills/neon/references/claimable-neon.md +10 -14
  195. package/dist/global-skills/neon/references/function-triggers.md +53 -0
  196. package/dist/global-skills/neon/references/logs-loki.md +61 -0
  197. package/dist/global-skills/neon/references/parse-env.md +32 -0
  198. package/dist/global-skills/neon/references/sdk.md +7 -0
  199. package/dist/global-skills/neon-ai-gateway/SKILL.md +14 -16
  200. package/dist/global-skills/neon-auth/SKILL.md +155 -0
  201. package/dist/global-skills/neon-auth/references/managed-auth.md +173 -0
  202. package/dist/global-skills/neon-auth/references/self-managed.md +25 -0
  203. package/dist/global-skills/neon-functions/SKILL.md +159 -84
  204. package/dist/global-skills/neon-functions/references/ai-sdk.md +4 -6
  205. package/dist/global-skills/neon-functions/references/function-triggers.md +249 -0
  206. package/dist/global-skills/neon-functions/references/mastra-studio.md +3 -3
  207. package/dist/global-skills/neon-functions/references/mcp.md +1 -1
  208. package/dist/global-skills/neon-functions/references/production-hardening.md +340 -0
  209. package/dist/global-skills/neon-functions/references/sse.md +8 -5
  210. package/dist/global-skills/neon-object-storage/SKILL.md +10 -11
  211. package/dist/global-skills/neon-postgres/SKILL.md +120 -17
  212. package/dist/global-skills/neon-postgres/references/full-text-search.md +99 -0
  213. package/dist/global-skills/neon-postgres/references/hybrid-search.md +90 -0
  214. package/dist/global-skills/neon-postgres/references/lakebase-search-drizzle.md +172 -0
  215. package/dist/global-skills/neon-postgres/references/vector-search.md +137 -0
  216. package/dist/global-skills/neon-postgres-branches/SKILL.md +3 -3
  217. package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +1 -1
  218. package/dist/global-skills/onboarding/SKILL.md +16 -14
  219. package/dist/global-skills/plan-my-idea/SKILL.md +1 -1
  220. package/dist/global-skills/publish-app/SKILL.md +1 -1
  221. package/dist/global-skills/publish-extension/SKILL.md +1 -1
  222. package/dist/global-skills/resend/SKILL.md +4 -2
  223. package/dist/global-skills/resend/references/broadcasts.md +6 -1
  224. package/dist/global-skills/resend/references/receiving.md +29 -10
  225. package/dist/global-skills/resend/references/sending/email-management.md +14 -4
  226. package/dist/global-skills/resend/references/topics.md +9 -6
  227. package/dist/global-skills/resend/references/usage.md +117 -0
  228. package/dist/global-skills/resend/references/webhooks.md +59 -2
  229. package/dist/global-skills/reset-password/SKILL.md +1 -1
  230. package/dist/global-skills/save-data/SKILL.md +1 -1
  231. package/dist/global-skills/setup-email/SKILL.md +1 -1
  232. package/dist/global-skills/setup-payments/SKILL.md +1 -1
  233. package/dist/global-skills/setup-sms/SKILL.md +1 -1
  234. package/dist/global-skills/sign-in-with-email-link/SKILL.md +1 -1
  235. package/dist/global-skills/sign-in-with-google/SKILL.md +1 -1
  236. package/dist/global-skills/sign-in-with-phone/SKILL.md +1 -1
  237. package/dist/global-skills/stripe-best-practices/SKILL.md +35 -29
  238. package/dist/global-skills/stripe-best-practices/references/billing.md +9 -2
  239. package/dist/global-skills/stripe-best-practices/references/payments.md +4 -2
  240. package/dist/global-skills/stripe-best-practices/references/security.md +3 -1
  241. package/dist/global-skills/stripe-best-practices/references/tax.md +39 -20
  242. package/dist/global-skills/supabase/SKILL.md +6 -0
  243. package/dist/global-skills/track-errors/SKILL.md +1 -1
  244. package/dist/global-skills/update-kit/SKILL.md +1 -1
  245. package/dist/global-skills/use-railway/SKILL.md +42 -22
  246. package/dist/global-skills/use-railway/references/analyze-db.md +7 -6
  247. package/dist/global-skills/use-railway/references/cloud-agents.md +70 -0
  248. package/dist/global-skills/use-railway/references/configure.md +17 -2
  249. package/dist/global-skills/use-railway/references/databases.md +107 -0
  250. package/dist/global-skills/use-railway/references/deploy.md +5 -5
  251. package/dist/global-skills/use-railway/references/feature-flags.md +25 -13
  252. package/dist/global-skills/use-railway/references/iac.md +66 -77
  253. package/dist/global-skills/use-railway/references/operate.md +26 -3
  254. package/dist/global-skills/use-railway/references/request.md +31 -23
  255. package/dist/global-skills/use-railway/references/setup.md +16 -5
  256. package/dist/global-skills/use-railway/references/tracing.md +261 -0
  257. package/dist/global-skills/use-railway/references/usage.md +52 -0
  258. package/dist/global-skills/validate-my-idea/SKILL.md +54 -0
  259. package/dist/global-skills/{feedback → vybekiit-feedback}/SKILL.md +17 -13
  260. package/dist/global-skills/watch-my-app/SKILL.md +53 -0
  261. package/dist/global-skills/wire-auth/SKILL.md +1 -1
  262. package/dist/global-skills/wire-database/SKILL.md +1 -1
  263. package/dist/global-skills/wire-email/SKILL.md +1 -1
  264. package/dist/global-skills/wire-payments/SKILL.md +1 -1
  265. package/dist/global-skills/workers-best-practices/SKILL.md +36 -103
  266. package/dist/global-skills/workers-best-practices/references/configuration.md +139 -0
  267. package/dist/global-skills/workers-best-practices/references/platform-apis.md +51 -0
  268. package/dist/global-skills/workers-best-practices/references/{rules.md → runtime-patterns.md} +13 -137
  269. package/dist/global-skills/wrangler/SKILL.md +48 -901
  270. package/dist/global-skills/xcode-project-setup/scripts/xcode_spm_setup/Sources/main.swift +19 -15
  271. package/package.json +9 -8
  272. package/dist/global-skills/expo-native-ui/references/animations.md +0 -220
  273. package/dist/global-skills/firebase-firestore/references/enterprise/security_rules.md +0 -577
  274. package/dist/global-skills/workers-best-practices/references/review.md +0 -174
@@ -0,0 +1,249 @@
1
+ # Function Triggers
2
+
3
+ A Function Trigger is a branch-scoped rule that POSTs to a Neon Function so recurring work does not need a separate scheduler. The request is a normal `fetch` invocation: same public URL, same 15-minute time-to-first-byte limit, same injected env (`DATABASE_URL`, …).
4
+
5
+ Same regions as Functions: `aws-us-east-2`, `aws-us-east-1`, `aws-eu-central-1`, and `aws-ap-southeast-1`. Needs Neon CLI 4.21 or newer to declare triggers in `neon.ts`.
6
+
7
+ If `neon deploy` returns 404 `function triggers not available for this project`, the project does not have Function Triggers yet. Deploy the function without applying the trigger (`neon functions deploy <slug> --src <entry>`) and retry `neon deploy` once the project has them.
8
+
9
+ ## Supported types
10
+
11
+ `triggers` is a keyed map on `defineConfig`. Types:
12
+
13
+ | `type` | When it fires | `neon.ts` fields | CLI create |
14
+ | ------------------------ | -------------------------------------------------- | ---------------------------------------- | ----------------------------------------------- |
15
+ | `schedule` | On a five-field UTC cron expression | `function`, `cron` | `neon triggers create --cron '…'` |
16
+ | `storage_object_created` | When an object is created in a declared bucket | `function`, `bucket`, optional `prefix` | `neon triggers create --bucket <name>` |
17
+
18
+ `create` takes `--cron` or `--bucket`, not both. `@neon/functions` ≥ 0.11.0: `parseTriggerDelivery` accepts both types; `parseTriggerInvocation` and Hono `parseTrigger(c)` stay schedule-only (`storage_object_created` is `invalid_body` there).
19
+
20
+ ## Fields
21
+
22
+ The trigger name is the `neon.ts` map key (CLI `--name`). It must be unique among every trigger visible on the branch, including other functions.
23
+
24
+ | Field | Required | Notes |
25
+ | -------------- | -------- | --------------------------------------------------------------------- |
26
+ | `type` | yes | `"schedule"` or `"storage_object_created"` |
27
+ | `function` | yes | Function slug. REST/MCP: `function_slug` |
28
+ | `cron` | schedule | Five-field UTC expression, e.g. `0 * * * *`, `*/15 * * * *` |
29
+ | `bucket` | storage | Bucket name. REST: `storage_object_created.bucket_name` |
30
+ | `prefix` | no | Object-key prefix filter. REST: `storage_object_created.prefix` |
31
+ | `functionPath` | no | Path on the function. Default `/`. CLI: `--function-path` |
32
+ | `enabled` | no | Default `true`. CLI: `--enabled false` to create disabled |
33
+
34
+ ## neon.ts (preferred)
35
+
36
+ Declare `triggers` next to `functions` (and `buckets` when using storage). `neon deploy` applies triggers **after** the functions they target. Triggers that exist remotely but are omitted from `neon.ts` are left alone.
37
+
38
+ ```typescript
39
+ import { defineConfig } from "@neon/config/v1";
40
+
41
+ export default defineConfig({
42
+ functions: {
43
+ ingest: { name: "Object ingest", source: "src/index.ts" },
44
+ cron: { name: "Cron", source: "src/cron.ts" },
45
+ },
46
+ buckets: { assets: { access: "public_read" } },
47
+ triggers: {
48
+ "on-upload": {
49
+ type: "storage_object_created",
50
+ function: "ingest",
51
+ bucket: "assets",
52
+ prefix: "logos/",
53
+ functionPath: "/object",
54
+ },
55
+ "every-minute": {
56
+ type: "schedule",
57
+ function: "cron",
58
+ cron: "* * * * *",
59
+ functionPath: "/cron",
60
+ },
61
+ },
62
+ });
63
+ ```
64
+
65
+ ```bash
66
+ neon deploy
67
+ ```
68
+
69
+ Change the cron string, bucket, or prefix and deploy again to reschedule. Starter: `neon bootstrap --template cron-job`.
70
+
71
+ ## CLI
72
+
73
+ Use when you are not applying `neon.ts`, or to list, enable, disable, or delete.
74
+
75
+ ```bash
76
+ neon triggers create --function-slug cron --name hourly --cron '0 * * * *' --function-path /cron
77
+ neon triggers create --function-slug ingest --name on-upload --bucket assets --prefix 'logos/' --function-path /object
78
+ neon triggers list
79
+ neon triggers list --output json
80
+ neon triggers update <id> --branch <branch> --cron '*/30 * * * *'
81
+ neon triggers update <id> --branch <branch> --bucket assets --prefix 'incoming/'
82
+ neon triggers enable <id> --branch <branch>
83
+ neon triggers disable <id> --branch <branch>
84
+ neon triggers delete <id> --branch <branch>
85
+ ```
86
+
87
+ `enable` / `disable` wrap `update --enabled`. Updating the cron recomputes `Next Run At`. Disabling clears `Next Run At`. Alias: `neon trigger`. `--cron` on a storage trigger, or `--bucket` / `--prefix` on a schedule trigger, is rejected.
88
+
89
+ Inspect a trigger with `neon triggers list --output json`. Pass `--branch` on get/update/enable/disable/delete: without it the CLI resolves the trigger id as a branch name.
90
+
91
+ Project and branch otherwise resolve from `--project-id` / `--branch`, then `.neon`, then a single-project auto-detect.
92
+
93
+ ## MCP backup
94
+
95
+ The Neon MCP server (`?category=functions`) exposes `list_triggers`, `get_trigger`, `create_trigger`, `update_trigger`, and `delete_trigger`. `branch_id` is a `br-…` id, not a branch name (`list_branches` to resolve). Create a schedule trigger with snake_case:
96
+
97
+ ```json
98
+ {
99
+ "type": "schedule",
100
+ "function_slug": "cron",
101
+ "name": "hourly",
102
+ "function_path": "/cron",
103
+ "schedule": { "cron": "0 * * * *" },
104
+ "enabled": true
105
+ }
106
+ ```
107
+
108
+ `create_trigger` required fields for schedule: `type`, `function_slug`, `name`, `schedule`. REST is the same payload at `POST /projects/{project_id}/branches/{branch_id}/triggers`. For `storage_object_created`, use CLI or REST with `"type": "storage_object_created"` and `storage_object_created: { "bucket_name": "assets", "prefix": "logos/" }`. CLI docs: https://neon.com/docs/cli/triggers.md.
109
+
110
+ ## Delivery payload
111
+
112
+ Neon POSTs JSON. The Functions proxy drops client-supplied `x-neon-*` headers, so a present `x-neon-trigger-invocation-id` is from a trigger delivery. It must match `invocation_id` in the body.
113
+
114
+ A Function that also serves app or public HTTP must not apply JWT or `X-Secret` middleware to the trigger path. Neon trigger POSTs do not send those. Caller shapes: [production-hardening.md](production-hardening.md).
115
+
116
+ Schedule wire JSON (snake_case):
117
+
118
+ ```json
119
+ {
120
+ "version": 1,
121
+ "invocation_id": "…",
122
+ "trigger": {
123
+ "type": "schedule",
124
+ "id": "trigger-…",
125
+ "name": "hourly"
126
+ },
127
+ "data": { "scheduled_at": "2026-09-15T23:35:00Z" }
128
+ }
129
+ ```
130
+
131
+ Storage-object-created wire JSON:
132
+
133
+ ```json
134
+ {
135
+ "version": 1,
136
+ "invocation_id": "…",
137
+ "trigger": {
138
+ "type": "storage_object_created",
139
+ "id": "trigger-…",
140
+ "name": "on-upload"
141
+ },
142
+ "data": { "bucket_name": "uploads", "object_key": "smoke.txt" }
143
+ }
144
+ ```
145
+
146
+ Parsed (`@neon/functions` ≥ 0.11.0) is camelCase. `parseTriggerDelivery` also sets a top-level `type`. Schedule: `data.scheduledAt`. Storage: `data.bucketName`, `data.objectKey`. Narrow on `invocation.type` (or `isScheduleTriggerInvocation` / `isStorageObjectCreatedTriggerInvocation`) before reading `data` — a check on `trigger.type` does not narrow the sibling `data` field.
147
+
148
+ ### `parseTriggerDelivery` (both types)
149
+
150
+ ```typescript
151
+ import { parseTriggerDelivery } from "@neon/functions/triggers";
152
+
153
+ export default {
154
+ async fetch(request: Request): Promise<Response> {
155
+ const parsed = await parseTriggerDelivery(request);
156
+ if (!parsed.ok) {
157
+ const status = parsed.error === "invalid_body" ? 400 : 401;
158
+ return new Response(parsed.error, { status });
159
+ }
160
+
161
+ const invocation = parsed.invocation;
162
+ if (invocation.type === "storage_object_created") {
163
+ return Response.json({
164
+ bucketName: invocation.data.bucketName,
165
+ objectKey: invocation.data.objectKey,
166
+ });
167
+ }
168
+
169
+ return Response.json({
170
+ scheduledAt: invocation.data.scheduledAt,
171
+ });
172
+ },
173
+ };
174
+ ```
175
+
176
+ `parseTriggerDelivery(request)` clones the Request before `json()`, so `request.json()` still works. If you already have the body: `parseTriggerDelivery({ headers, body })` (sync). `parsed.error` is `missing_header`, `invalid_body`, or `invocation_id_mismatch`. Unknown `trigger.type` values fail as `invalid_body`.
177
+
178
+ Hono: `parseTriggerDelivery(c.req.raw)`.
179
+
180
+ ### `parseTrigger` (Hono, schedule only)
181
+
182
+ Throws `HTTPException`. `c.req.json()` still works afterwards. Returns `ScheduleTriggerInvocation`. A `storage_object_created` delivery is `invalid_body`.
183
+
184
+ | Failure | Status | Message |
185
+ | ------------------------ | ------ | --------------------------------------------- |
186
+ | missing header | 401 | `Missing x-neon-trigger-invocation-id header` |
187
+ | header ≠ `invocation_id` | 401 | `Invocation id mismatch` |
188
+ | invalid JSON or payload | 400 | `Invalid trigger payload` |
189
+
190
+ ```typescript
191
+ import { parseTrigger } from "@neon/functions/hono";
192
+
193
+ app.post("/cron", async (c) => {
194
+ const invocation = await parseTrigger(c);
195
+ return c.json({ ok: true, invocationId: invocation.invocationId });
196
+ });
197
+ ```
198
+
199
+ ### `parseTriggerInvocation` (`fetch`, schedule only)
200
+
201
+ ```typescript
202
+ import { parseTriggerInvocation } from "@neon/functions/triggers";
203
+
204
+ export default {
205
+ async fetch(request: Request): Promise<Response> {
206
+ const parsed = await parseTriggerInvocation(request);
207
+ if (!parsed.ok) {
208
+ const status = parsed.error === "invalid_body" ? 400 : 401;
209
+ return new Response(parsed.error, { status });
210
+ }
211
+ return Response.json({
212
+ ok: true,
213
+ invocationId: parsed.invocation.invocationId,
214
+ });
215
+ },
216
+ };
217
+ ```
218
+
219
+ ## Local `neon dev`
220
+
221
+ `neon dev` forwards `x-neon-trigger-invocation-id`, so you can simulate a tick:
222
+
223
+ ```bash
224
+ curl -X POST http://localhost:8787/cron \
225
+ -H 'content-type: application/json' \
226
+ -H 'x-neon-trigger-invocation-id: local-dev' \
227
+ -d '{
228
+ "version": 1,
229
+ "invocation_id": "local-dev",
230
+ "trigger": { "type": "schedule", "id": "trigger-local", "name": "hourly" },
231
+ "data": { "scheduled_at": "2026-09-15T00:00:00Z" }
232
+ }'
233
+ ```
234
+
235
+ A public POST to the **deployed** function that includes that header still returns 401: the proxy strips client `x-neon-*` headers.
236
+
237
+ ## Inheritance
238
+
239
+ Triggers are branch-scoped. A trigger created on a parent is visible on children (`inherited: true`, `source_branch_id` points at the origin) and starts disabled there.
240
+
241
+ `neon deploy` of a `neon.ts` that declares the same trigger (default `enabled: true`) enables that inherited copy on the child. Omit it from `neon.ts` to leave the inherited trigger disabled. Enable without applying `neon.ts` with `neon triggers enable <id> --branch <branch>`.
242
+
243
+ ## Logs
244
+
245
+ ```bash
246
+ neon logs query --source function --since 1h
247
+ ```
248
+
249
+ Pass `--branch` when the function is not on the branch in `.neon`.
@@ -6,7 +6,7 @@ The shape mirrors any other Node integration (see [sentry.md](sentry.md)): insta
6
6
 
7
7
  ## 1. Define the agent against the Neon AI Gateway
8
8
 
9
- With `@mastra/core` 1.47+, use a `neon/<model>` magic string — Mastra reads `NEON_AI_GATEWAY_BASE_URL` and `NEON_AI_GATEWAY_TOKEN` from the environment (injected by `neon deploy` / `neon env pull` when `preview.aiGateway` is enabled in `neon.ts`). No manual `url`/`apiKey` or MLflow dialect swap is needed; Mastra routes each model to the correct gateway endpoint.
9
+ With `@mastra/core` 1.47+, use a `neon/<model>` magic string — Mastra reads `NEON_AI_GATEWAY_BASE_URL` and `NEON_AI_GATEWAY_TOKEN` from the environment (injected by `neon deploy` / `neon env pull` when `aiGateway` is enabled in `neon.ts`). No manual `url`/`apiKey` or MLflow dialect swap is needed; Mastra routes each model to the correct gateway endpoint.
10
10
 
11
11
  ```typescript
12
12
  // src/mastra/agents/pricing.ts
@@ -102,8 +102,8 @@ functions: {
102
102
  name: "my app",
103
103
  source: "src/index.ts",
104
104
  env: {
105
- MASTRA_PROJECT_ID: process.env.MASTRA_PROJECT_ID ?? "",
106
- MASTRA_PLATFORM_ACCESS_TOKEN: process.env.MASTRA_PLATFORM_ACCESS_TOKEN ?? "",
105
+ MASTRA_PROJECT_ID: process.env.MASTRA_PROJECT_ID!,
106
+ MASTRA_PLATFORM_ACCESS_TOKEN: process.env.MASTRA_PLATFORM_ACCESS_TOKEN!,
107
107
  },
108
108
  },
109
109
  }
@@ -89,7 +89,7 @@ Key points:
89
89
  > [!WARNING]
90
90
  > A Neon Function has a **public HTTPS URL — anyone can reach it.** An unauthenticated MCP server hands every caller your tools (and the database behind them). Authenticate at the top of the handler before touching the transport, exactly as for [any client-facing function](../SKILL.md#functions-as-an-agent-backend-nextjs-and-similar-frameworks).
91
91
 
92
- [Better Auth](https://better-auth.com) (self-hostable, runs alongside your app) is a good fit, and it covers both common shapes. **Better Auth is evolving quickly** — the MCP plugin is moving out of `better-auth/plugins` into its own `@better-auth/mcp` package (built on the OAuth Provider plugin), which renames `withMcpAuth` → `requireMcpAuth` and `createMcpAuthClient` → `createMcpResourceClient`. Verify the current package and import paths against the [Better Auth MCP docs](https://better-auth.com/docs/plugins/mcp) before wiring it up.
92
+ [Better Auth](https://better-auth.com) covers both common MCP shapes when you need an OAuth authorization server or API keys. Managed Auth does not. Keep existing app login (Clerk, Managed Auth, or Better Auth) unless the user asked to migrate it. Confirm the **installed** Better Auth version before copying imports: the MCP plugin is moving out of `better-auth/plugins` into `@better-auth/mcp` (`withMcpAuth` → `requireMcpAuth`, `createMcpAuthClient` → `createMcpResourceClient`). Docs: https://better-auth.com/docs/plugins/mcp
93
93
 
94
94
  ### Option 1 — OAuth via the Better Auth MCP plugin (best for third-party clients)
95
95
 
@@ -0,0 +1,340 @@
1
+ # Production hardening for Neon Functions
2
+
3
+ A Function has a public HTTPS URL. Authenticate the caller, then pick extra
4
+ protection from who actually calls it. This file is the production path; JWT
5
+ and trigger parsers stay in [SKILL.md](../SKILL.md) and
6
+ [function-triggers.md](function-triggers.md).
7
+
8
+ https://neon.com/docs/compute/functions/authentication.md
9
+
10
+ ## Choose the caller shape
11
+
12
+ Pick a row before writing code. Mixed routes: apply the matching row per
13
+ path, not one middleware for the whole Function.
14
+
15
+ | Caller | What to do | Do not |
16
+ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | Trusted app server (Vercel/Netlify route, server action, queue worker) that finishes within that host's duration | The server calls the Function. Browser never sees the Function URL or the origin secret. Function returns 401 before any work. | Put the secret in client code. Proxy a long agent, WebSocket, or SSE stream through the app host without checking that host's duration. |
18
+ | Function Triggers only | `parseTriggerDelivery` (both types). Keep this path outside JWT and `X-Secret` middleware. | Require a user JWT or `X-Secret` on the trigger path. Invent `x-neon-invocation-id`. Treat `invocation_id` as a secret. |
19
+ | Public consumers (Open API, MCP, third-party HTTP) | A Cloudflare Worker Custom Domain you own, **not** registered as a Neon custom domain, proxies to the native invocation URL, sets `X-Secret`, and rate-limits at the edge. Function requires `X-Secret`, then the route's existing consumer auth. | Orange-cloud a Neon-registered custom-domain CNAME. Assume the native URL is closed. Put a human challenge in front of MCP. |
20
+
21
+ Browser-direct JWT agents stay on the client-direct path in SKILL.md. They
22
+ are not the trusted-app-server row.
23
+
24
+ ## Native URL
25
+
26
+ The native invocation URL stays reachable after you add a custom domain or a
27
+ Worker. Application checks reject work; the request still occupies a Function
28
+ invocation until the handler returns.
29
+
30
+ Neon also enforces a default account-wide cap of 100 concurrent invocations
31
+ (`429`, body `per-account concurrency limit reached`, `Retry-After` in
32
+ seconds). That is not per-client DDoS protection.
33
+ https://neon.com/docs/compute/functions/reference/runtime-limits.md
34
+
35
+ Functions docs do not document a customer-configurable WAF, a switch to
36
+ disable the native URL, or a Functions ingress IP allowlist. Do not invent
37
+ those.
38
+
39
+ ## Trusted app server
40
+
41
+ Keep the Function URL and origin secret in **server** env on the app host and
42
+ in Function `env`. Authenticate the app route first. Then `fetch` the
43
+ Function.
44
+
45
+ Headers:
46
+
47
+ ```text
48
+ X-Secret: <server-only origin secret>
49
+ Authorization: <existing consumer credential, unchanged>
50
+ ```
51
+
52
+ `X-Secret` is application-defined. Use it for the server hop when
53
+ `Authorization` already carries a user or consumer bearer token.
54
+
55
+ If the Function currently only checks `Authorization: Bearer <API_KEY>` and
56
+ nothing forwards a user token, keep that check. Do not migrate that header.
57
+
58
+ Compare `X-Secret` before parsing the body or touching Postgres. Missing
59
+ `ORIGIN_SECRET` fails at startup. Do not add CORS if browsers must not call
60
+ this Function.
61
+
62
+ ```typescript
63
+ import { timingSafeEqual } from "node:crypto";
64
+
65
+ const originSecret = process.env.ORIGIN_SECRET;
66
+ if (!originSecret) throw new Error("ORIGIN_SECRET is required");
67
+ const expected = Buffer.from(originSecret);
68
+
69
+ function hasOriginSecret(request: Request): boolean {
70
+ const header = request.headers.get("x-secret");
71
+ if (header === null) return false;
72
+ const provided = Buffer.from(header);
73
+ if (provided.byteLength !== expected.byteLength) return false;
74
+ return timingSafeEqual(provided, expected);
75
+ }
76
+
77
+ export default {
78
+ async fetch(request: Request): Promise<Response> {
79
+ if (!hasOriginSecret(request)) {
80
+ return new Response("Unauthorized", { status: 401 });
81
+ }
82
+ return Response.json({ ok: true });
83
+ },
84
+ };
85
+ ```
86
+
87
+ Declare `ORIGIN_SECRET` in `neon.ts` `env` and on the app host.
88
+
89
+ Buffer the incoming body on the app-server `fetch`. Node `fetch` throws
90
+ `duplex option is required when sending a body` if you pass a streamed
91
+ `request.body`. `duplex: "half"` is Node-only; this hop is short, so
92
+ buffer instead. `redirect: "manual"` keeps `X-Secret` from following a
93
+ cross-origin redirect.
94
+
95
+ `NEON_FUNCTION_URL` is `invocation_url` from `neon functions get`. It ends
96
+ with `/`. This example calls that root. For a Function path, concatenate
97
+ onto that slash (`new URL("orders?limit=2", functionUrl)`). Do not copy the
98
+ app request's host or pathname onto the Function; a Next.js `/api/...` route
99
+ is not the Function path.
100
+
101
+ ```typescript
102
+ const functionUrl = process.env.NEON_FUNCTION_URL;
103
+ const originSecret = process.env.ORIGIN_SECRET;
104
+ if (!functionUrl || !originSecret) {
105
+ throw new Error("NEON_FUNCTION_URL and ORIGIN_SECRET are required");
106
+ }
107
+
108
+ const headers = new Headers({ "x-secret": originSecret });
109
+ const contentType = request.headers.get("content-type");
110
+ if (contentType) headers.set("content-type", contentType);
111
+ const authorization = request.headers.get("authorization");
112
+ if (authorization) headers.set("authorization", authorization);
113
+
114
+ return fetch(functionUrl, {
115
+ method: request.method,
116
+ headers,
117
+ body: request.body ? await request.arrayBuffer() : undefined,
118
+ redirect: "manual",
119
+ });
120
+ ```
121
+
122
+ ## Function Triggers only
123
+
124
+ Use the parsers in [function-triggers.md](function-triggers.md).
125
+ `parseTriggerDelivery` covers `schedule` and `storage_object_created`.
126
+ Hono `parseTrigger` and `parseTriggerInvocation` are schedule-only.
127
+
128
+ Neon POSTs to the native URL and does not send `X-Secret` or a user JWT. A
129
+ trigger path that requires those credentials drops real deliveries.
130
+
131
+ `invocation_id` is a correlation id. Presence of `x-neon-trigger-invocation-id`
132
+ after Neon strips client `x-neon-*` headers is the provenance check; matching
133
+ the body is consistency. https://neon.com/docs/compute/functions/triggers/overview.md
134
+
135
+ Local `neon dev` can send that header to simulate a tick. On the deployed
136
+ Function a client-supplied `x-neon-*` header is stripped.
137
+
138
+ ## Public consumers through a Worker
139
+
140
+ Consumers call a hostname you control. Volumetric filtering happens there.
141
+ The Function still authenticates.
142
+
143
+ DNS:
144
+
145
+ - A Neon Function custom-domain CNAME must be DNS-only (grey cloud). A proxied
146
+ (orange-cloud) record blocks Neon domain validation.
147
+ https://neon.com/docs/compute/functions/custom-domains.md
148
+ - Attach a [Cloudflare Worker Custom Domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/)
149
+ that is **not** registered with Neon. The Worker fetches the **native**
150
+ invocation URL.
151
+
152
+ Worker:
153
+
154
+ - Preserve method, pathname, query, body, and consumer `Authorization`.
155
+ - Set `X-Secret`. Do not use it as the consumer credential.
156
+ - Build the upstream URL from a fixed origin. Assign `pathname` and `search`
157
+ separately so a request path cannot change the authority.
158
+ - Apply Cloudflare DDoS / WAF / rate-limiting rules on that Worker hostname.
159
+ The forwarding snippet does not configure those rules.
160
+ - Disable the Worker's `workers.dev` route and Preview URLs. Hostname-scoped
161
+ rules do not apply to those endpoints, and they still run this forwarding
162
+ code. Inventory clients on those URLs first.
163
+ https://developers.cloudflare.com/workers/configuration/routing/workers-dev.md
164
+ Dashboard: Worker → Settings → Domains & Routes. A later Wrangler deploy
165
+ without `workers_dev: false` turns `workers.dev` back on.
166
+ - Do not buffer a streaming body. Do not add a browser challenge MCP or API
167
+ clients cannot pass.
168
+
169
+ Wrangler:
170
+
171
+ ```jsonc
172
+ {
173
+ "workers_dev": false,
174
+ "preview_urls": false
175
+ }
176
+ ```
177
+
178
+ ```typescript
179
+ const FUNCTION_ORIGIN = "https://<invocation-host>"; // neon functions get
180
+
181
+ export default {
182
+ async fetch(
183
+ request: Request,
184
+ env: { ORIGIN_SECRET: string },
185
+ ): Promise<Response> {
186
+ const incoming = new URL(request.url);
187
+ const upstream = new URL(FUNCTION_ORIGIN);
188
+ upstream.pathname = incoming.pathname;
189
+ upstream.search = incoming.search;
190
+
191
+ const headers = new Headers(request.headers);
192
+ headers.set("x-secret", env.ORIGIN_SECRET);
193
+ headers.delete("host");
194
+
195
+ return fetch(upstream, {
196
+ method: request.method,
197
+ headers,
198
+ body: request.body,
199
+ redirect: "manual",
200
+ });
201
+ },
202
+ };
203
+ ```
204
+
205
+ Function: require `X-Secret` first (same helper as above), then apply **that
206
+ route's existing** consumer authentication. Browser preflight, OAuth
207
+ discovery, and intentionally public routes keep their current behavior. An
208
+ origin secret is not a user API key.
209
+
210
+ Requiring `X-Secret` on the native URL is a migration. Move legitimate
211
+ native-URL clients to the Worker first. Anyone who still hits the native URL
212
+ without the secret gets 401 after the request has reached Function compute.
213
+
214
+ HTTP-triggered Workers have no documented hard wall-clock duration while the
215
+ client stays connected. Still verify WebSocket, SSE, and long agent streams
216
+ against the chosen Worker before using this row for those workloads. If the
217
+ edge cannot hold the stream, keep client-direct JWT.
218
+
219
+ MCP: after switching consumers to the Worker hostname, confirm advertised
220
+ resource URLs, token audiences, existing client registrations, and one live
221
+ session as well as a fresh authorization. https://developers.cloudflare.com/workers/platform/limits/
222
+
223
+ ## Limit authenticated application work
224
+
225
+ Edge rate limiting drops traffic before Neon. A limiter **inside** the
226
+ Function runs after the request arrived. Use it to cap expensive work for a
227
+ **verified** principal (user id, org id, API-key hash). Never use a
228
+ caller-supplied id. A global budget can cap total capacity; one caller can
229
+ consume it for everyone.
230
+
231
+ Request-rate limits do not cap simultaneous long-running work. Bound in-flight
232
+ expensive operations separately, in a shared store, not in module-scope
233
+ counters (those are per-isolate and vanish on eviction).
234
+
235
+ A query to Postgres on every anonymous request adds database work to a flood.
236
+ Postgres can hold per-principal counters at low-to-moderate authenticated
237
+ volume when you already have it; measure before relying on it. Prefer Redis
238
+ or edge limits when a database round trip per request is too expensive.
239
+
240
+ Optional Upstash quota after credential checks, before expensive work.
241
+ `60` per minute and `timeout: 1_000` are example policy. `RATE_LIMIT_PREFIX`
242
+ is the application + environment + quota name; `NEON_BRANCH` (a branch
243
+ **name**) is appended so branches do not share counters.
244
+
245
+ `Redis.fromEnv()` reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.
246
+ Upstash `reset` is milliseconds since epoch. Upstash can report a timeout as
247
+ a successful `limit()` result; check `reason` before `success`. Await the
248
+ admission decision; pass `pending` to `waitUntil` so background writes do not
249
+ delay the response.
250
+
251
+ ```typescript
252
+ import { waitUntil } from "@neon/functions";
253
+ import { Ratelimit } from "@upstash/ratelimit";
254
+ import { Redis } from "@upstash/redis";
255
+
256
+ const namespace = process.env.RATE_LIMIT_PREFIX;
257
+ if (!namespace) throw new Error("RATE_LIMIT_PREFIX is required");
258
+
259
+ const limiter = new Ratelimit({
260
+ redis: Redis.fromEnv(),
261
+ limiter: Ratelimit.slidingWindow(60, "1 m"),
262
+ prefix: `${namespace}:${process.env.NEON_BRANCH ?? "local"}`,
263
+ timeout: 1_000,
264
+ analytics: false,
265
+ });
266
+
267
+ export async function checkQuota(
268
+ authenticatedPrincipalId: string,
269
+ ): Promise<Response | null> {
270
+ let result: Awaited<ReturnType<typeof limiter.limit>>;
271
+ try {
272
+ result = await limiter.limit(authenticatedPrincipalId);
273
+ } catch (error) {
274
+ console.error("Rate-limit store failed", error);
275
+ return new Response("Rate limiter unavailable", {
276
+ status: 503,
277
+ headers: { "Retry-After": "1" },
278
+ });
279
+ }
280
+
281
+ waitUntil(result.pending);
282
+
283
+ if (result.reason === "timeout") {
284
+ console.error("Rate-limit store timed out");
285
+ return new Response("Rate limiter unavailable", {
286
+ status: 503,
287
+ headers: { "Retry-After": "1" },
288
+ });
289
+ }
290
+
291
+ if (result.success) return null;
292
+
293
+ const retryAfterSeconds = Math.max(
294
+ 1,
295
+ Math.ceil((result.reset - Date.now()) / 1000),
296
+ );
297
+ return new Response("Too Many Requests", {
298
+ status: 429,
299
+ headers: {
300
+ "Retry-After": String(retryAfterSeconds),
301
+ "RateLimit-Limit": String(result.limit),
302
+ "RateLimit-Remaining": String(result.remaining),
303
+ "RateLimit-Reset": String(retryAfterSeconds),
304
+ },
305
+ });
306
+ }
307
+ ```
308
+
309
+ Preserve CORS headers on these responses when the route already sets them.
310
+ Do not relabel limiter failures as `401` or as quota exhaustion. `Retry-After`
311
+ is required on `429`. `RateLimit-*` is optional metadata; `RateLimit-Reset`
312
+ here is seconds until the window renews.
313
+
314
+ Do not rate-limit in the Function by `X-Forwarded-For`. That header is not a
315
+ documented trustworthy client-IP on Functions. IP limits belong at the edge.
316
+
317
+ ## Preserve existing clients
318
+
319
+ - Direct-client JWT agents keep their token, JWKS, issuer, and audience.
320
+ Do not require `X-Secret` on those routes.
321
+ - CORS still has to succeed for legitimate browser origins, including on
322
+ 401/429. CORS is not authentication.
323
+ - Inventory production and preview origins before tightening an allowlist.
324
+ - WebSocket and SSE handshake, heartbeat, and reconnect stay as documented
325
+ in SKILL.md.
326
+ - MCP OAuth discovery, tokens, methods, and streaming stay as documented in
327
+ [mcp.md](mcp.md). Authenticate before the transport.
328
+ - Adding a Worker does not by itself change the API's consumer credentials.
329
+ - Stored rows stay authorized by the verified identity, not by a quota key.
330
+
331
+ ## Reject these
332
+
333
+ - Orange-cloud proxying a **Neon-registered** custom-domain CNAME.
334
+ - Shipping a server origin secret to the browser.
335
+ - Putting a long stream behind a host or Worker whose duration or transport
336
+ cannot hold it.
337
+ - Treating header/body equality as trigger provenance outside Neon's edge.
338
+ - In-memory counters as a cross-isolate quota.
339
+ - A Postgres lookup on every anonymous request as DDoS protection.
340
+ - Treating a native-URL `401` as traffic blocked before Function compute.
@@ -17,17 +17,20 @@ export default {
17
17
  const url = new URL(request.url);
18
18
  if (url.pathname !== "/events") return new Response("ok");
19
19
 
20
+ let timer: ReturnType<typeof setInterval>;
20
21
  const stream = new ReadableStream<Uint8Array>({
21
22
  start(controller) {
22
23
  // An SSE frame is `data: <payload>\n\n`. A line starting with `:` is a
23
24
  // comment — used here as a heartbeat to keep the stream from going idle.
24
25
  controller.enqueue(encoder.encode("data: hello\n\n"));
25
- const timer = setInterval(
26
+ timer = setInterval(
26
27
  () => controller.enqueue(encoder.encode(": ping\n\n")),
27
28
  25_000,
28
29
  );
29
- // cancel() fires when the client disconnects.
30
- return () => clearInterval(timer);
30
+ },
31
+ // cancel() fires when the client disconnects.
32
+ cancel() {
33
+ clearInterval(timer);
31
34
  },
32
35
  });
33
36
 
@@ -42,7 +45,7 @@ export default {
42
45
  };
43
46
  ```
44
47
 
45
- > `cancel()` is returned from `start()` here for brevity; you can also declare it as a separate `cancel()` method on the stream's underlying source. Either way, use it to drop the client from any broadcast set and clear timers.
48
+ > `cancel()` is a method on the stream's underlying source; it fires when the client disconnects. Use it to drop the client from any broadcast set and clear timers. A cleanup function returned from `start()` is ignored, so it has to be a real `cancel()` method.
46
49
 
47
50
  ## With Hono
48
51
 
@@ -91,7 +94,7 @@ const CHANNEL = "events";
91
94
  // One dedicated DIRECT connection per isolate to receive events (LISTEN needs a
92
95
  // real session — use DATABASE_URL_UNPOOLED, not the pooled URL).
93
96
  // Don't call attachDatabasePool here: it would silence the idle drop that killed the feed.
94
- // An error listener keeps the isolate alive; the feed stays down until the isolate restarts.
97
+ // The error listener keeps the process alive; reconnect the client on error in production (omitted here).
95
98
  const listener = new Client({
96
99
  connectionString: process.env.DATABASE_URL_UNPOOLED,
97
100
  });