vybekiit 0.7.25 → 0.7.27
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.
- package/dist/bin.js +3891 -1301
- package/dist/global-skills/aws-cdk/SKILL.md +19 -5
- package/dist/global-skills/aws-cdk/references/fast-deployments.md +191 -0
- package/dist/global-skills/aws-cdk/references/troubleshooting-deployment.md +16 -0
- package/dist/global-skills/aws-cloudformation/SKILL.md +16 -26
- package/dist/global-skills/aws-cloudformation/references/check-cloudformation-template-compliance.script.md +7 -3
- package/dist/global-skills/aws-cloudformation/references/cloudformation-language-server.md +177 -0
- package/dist/global-skills/aws-cloudformation/references/cloudformation-pre-deploy-validation.script.md +8 -2
- package/dist/global-skills/aws-cloudformation/references/persist-template-context.script.md +5 -8
- package/dist/global-skills/aws-cloudformation/references/retrieve-template-context.script.md +1 -1
- package/dist/global-skills/aws-cloudformation/references/security-considerations.md +51 -0
- package/dist/global-skills/aws-cloudformation/references/troubleshoot-failed-stack.script.md +138 -0
- package/dist/global-skills/aws-cloudformation/references/{validate-cloudformation-template.script.md → validate-with-cfn-lint.script.md} +15 -27
- package/dist/global-skills/aws-cloudformation/references/validate-with-cloudformation-validate.script.md +181 -0
- package/dist/global-skills/aws-cloudformation/references/validation-tool-selection.md +44 -0
- package/dist/global-skills/aws-serverless/SKILL.md +9 -1
- package/dist/global-skills/aws-serverless/references/architecture.md +3 -1
- package/dist/global-skills/aws-serverless/references/lambda.md +3 -1
- package/dist/global-skills/aws-serverless/references/orchestration.md +1 -0
- package/dist/global-skills/better-auth-best-practices/SKILL.md +18 -8
- package/dist/global-skills/eas-app-stores/SKILL.md +31 -15
- package/dist/global-skills/eas-app-stores/agents/openai.yaml +2 -2
- package/dist/global-skills/eas-app-stores/references/ios-app-store.md +37 -32
- package/dist/global-skills/eas-app-stores/references/native-ios.md +167 -0
- package/dist/global-skills/eas-app-stores/references/play-store.md +3 -7
- package/dist/global-skills/eas-app-stores/references/testflight.md +39 -35
- package/dist/global-skills/eas-simulator/SKILL.md +48 -26
- package/dist/global-skills/eas-simulator/references/controllers.md +32 -3
- package/dist/global-skills/eas-simulator/references/run-your-app.md +34 -4
- package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -4
- package/dist/global-skills/eas-update/SKILL.md +146 -0
- package/dist/global-skills/eas-update/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-animation/RECIPES.md +2 -2
- package/dist/global-skills/expo-animation/SKILL.md +9 -2
- package/dist/global-skills/expo-brownfield/SKILL.md +18 -11
- package/dist/global-skills/expo-brownfield/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-brownfield/references/brownfield-integrated.md +94 -69
- package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +40 -42
- package/dist/global-skills/expo-brownfield/references/comparison.md +5 -5
- package/dist/global-skills/expo-brownfield/references/feature-integration.md +163 -0
- package/dist/global-skills/expo-brownfield/references/troubleshooting.md +17 -17
- package/dist/global-skills/expo-brownfield/references/version-compatibility.md +40 -0
- package/dist/global-skills/expo-data-fetching/SKILL.md +27 -6
- package/dist/global-skills/expo-design-system/SKILL.md +27 -7
- package/dist/global-skills/expo-design-system/references/audit.md +7 -2
- package/dist/global-skills/expo-design-system/references/native-slop.md +74 -0
- package/dist/global-skills/expo-examples/SKILL.md +0 -1
- package/dist/global-skills/expo-examples/references/catalog.md +1 -1
- package/dist/global-skills/expo-migrate-module/SKILL.md +21 -10
- package/dist/global-skills/expo-migrate-module/references/compatibility.md +80 -23
- package/dist/global-skills/expo-migrate-module/references/migration-map.md +162 -11
- package/dist/global-skills/expo-native-ui/SKILL.md +25 -16
- package/dist/global-skills/expo-native-ui/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-native-ui/references/controls.md +5 -46
- package/dist/global-skills/expo-native-ui/references/icons.md +21 -2
- package/dist/global-skills/expo-native-ui/references/media.md +15 -20
- package/dist/global-skills/expo-native-ui/references/visual-effects.md +12 -11
- package/dist/global-skills/expo-overview/SKILL.md +17 -12
- package/dist/global-skills/expo-router/SKILL.md +5 -3
- package/dist/global-skills/expo-router/references/tabs.md +5 -5
- package/dist/global-skills/expo-skill-eval/scripts/check-static.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/clean-fixture.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/latest-sdk.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/make-fixture.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/make-workspace.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/snapshot-android.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/snapshot-ios.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/snapshot-web.sh +0 -0
- package/dist/global-skills/expo-upgrade/SKILL.md +3 -1
- package/dist/global-skills/expo-web-to-native/references/false-friends.md +2 -2
- package/dist/global-skills/expo-web-to-native/references/native-patterns.md +1 -1
- package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +13 -16
- package/dist/global-skills/firebase-ai-logic-basics/references/ios_setup.md +4 -5
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_android.md +4 -4
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_web.md +3 -3
- package/dist/global-skills/firebase-auth-basics/SKILL.md +11 -6
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_android.md +4 -5
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_web.md +3 -3
- package/dist/global-skills/firebase-auth-basics/references/flutter_setup.md +24 -25
- package/dist/global-skills/firebase-auth-basics/references/security_rules.md +4 -2
- package/dist/global-skills/firebase-crashlytics/references/android_setup.md +7 -4
- package/dist/global-skills/firebase-crashlytics/references/ios_setup.md +2 -3
- package/dist/global-skills/firebase-data-connect/SKILL.md +2 -1
- package/dist/global-skills/firebase-data-connect/examples.md +4 -4
- package/dist/global-skills/firebase-data-connect/reference/config.md +5 -4
- package/dist/global-skills/firebase-data-connect/reference/realtime.md +1 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_flutter.md +2 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_ios.md +2 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_web.md +17 -6
- package/dist/global-skills/firebase-data-connect/reference/security.md +5 -5
- package/dist/global-skills/firebase-data-connect/templates.md +2 -1
- package/dist/global-skills/firebase-firestore/SKILL.md +20 -8
- package/dist/global-skills/firebase-firestore/references/enterprise/android_sdk_usage.md +5 -4
- package/dist/global-skills/firebase-firestore/references/enterprise/data_model.md +12 -3
- package/dist/global-skills/firebase-firestore/references/enterprise/indexes.md +16 -18
- package/dist/global-skills/firebase-firestore/references/enterprise/provisioning.md +1 -1
- package/dist/global-skills/firebase-firestore/references/enterprise/python_sdk_usage.md +5 -1
- package/dist/global-skills/firebase-firestore/references/enterprise/web_sdk_usage.md +7 -7
- package/dist/global-skills/firebase-firestore/references/standard/android_sdk_usage.md +5 -5
- package/dist/global-skills/firebase-firestore/references/standard/flutter_setup.md +4 -4
- package/dist/global-skills/firebase-firestore/references/standard/indexes.md +16 -18
- package/dist/global-skills/firebase-firestore/references/standard/provisioning.md +1 -1
- package/dist/global-skills/firebase-remote-config-basics/SKILL.md +0 -5
- package/dist/global-skills/firebase-remote-config-basics/references/android_setup.md +36 -8
- package/dist/global-skills/firebase-remote-config-basics/references/ios_setup.md +1 -7
- package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +17 -6
- package/dist/global-skills/{firebase-firestore/references/standard/security_rules.md → firestore-rules-creation/SKILL.md} +24 -13
- package/dist/global-skills/grow-my-customers/SKILL.md +23 -0
- package/dist/global-skills/instrument-feature-flags/SKILL.md +25 -25
- package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +141 -285
- package/dist/global-skills/instrument-feature-flags/references/android.md +6 -15
- package/dist/global-skills/instrument-feature-flags/references/api.md +4 -11
- package/dist/global-skills/instrument-feature-flags/references/best-practices.md +1 -13
- package/dist/global-skills/instrument-feature-flags/references/django.md +14 -27
- package/dist/global-skills/instrument-feature-flags/references/dotnet.md +20 -79
- package/dist/global-skills/instrument-feature-flags/references/elixir.md +1 -9
- package/dist/global-skills/instrument-feature-flags/references/flask.md +13 -13
- package/dist/global-skills/instrument-feature-flags/references/flutter.md +3 -24
- package/dist/global-skills/instrument-feature-flags/references/go.md +3 -15
- package/dist/global-skills/instrument-feature-flags/references/ios.md +4 -17
- package/dist/global-skills/instrument-feature-flags/references/java.md +5 -13
- package/dist/global-skills/instrument-feature-flags/references/laravel.md +13 -17
- package/dist/global-skills/instrument-feature-flags/references/next-js.md +25 -32
- package/dist/global-skills/instrument-feature-flags/references/nodejs.md +8 -15
- package/dist/global-skills/instrument-feature-flags/references/php.md +1 -15
- package/dist/global-skills/instrument-feature-flags/references/python.md +2 -15
- package/dist/global-skills/instrument-feature-flags/references/react-native.md +13 -15
- package/dist/global-skills/instrument-feature-flags/references/react.md +17 -21
- package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +37 -83
- package/dist/global-skills/instrument-feature-flags/references/ruby.md +2 -15
- package/dist/global-skills/instrument-feature-flags/references/rust.md +13 -25
- package/dist/global-skills/instrument-feature-flags/references/usage.md +14 -63
- package/dist/global-skills/instrument-feature-flags/references/web.md +9 -14
- package/dist/global-skills/instrument-product-analytics/SKILL.md +29 -29
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-static.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-view-transitions.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/android.md +72 -107
- package/dist/global-skills/instrument-product-analytics/references/angular.md +26 -28
- package/dist/global-skills/instrument-product-analytics/references/astro.md +13 -24
- package/dist/global-skills/instrument-product-analytics/references/configuration.md +45 -63
- package/dist/global-skills/instrument-product-analytics/references/django.md +14 -27
- package/dist/global-skills/instrument-product-analytics/references/dotnet.md +20 -79
- package/dist/global-skills/instrument-product-analytics/references/elixir.md +47 -49
- package/dist/global-skills/instrument-product-analytics/references/flask.md +13 -13
- package/dist/global-skills/instrument-product-analytics/references/flutter.md +60 -90
- package/dist/global-skills/instrument-product-analytics/references/go.md +17 -56
- package/dist/global-skills/instrument-product-analytics/references/identify-users.md +15 -15
- package/dist/global-skills/instrument-product-analytics/references/ios.md +11 -15
- package/dist/global-skills/instrument-product-analytics/references/laravel.md +13 -17
- package/dist/global-skills/instrument-product-analytics/references/next-js.md +25 -32
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +13 -27
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +14 -28
- package/dist/global-skills/instrument-product-analytics/references/php.md +33 -84
- package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +229 -9
- package/dist/global-skills/instrument-product-analytics/references/python.md +415 -106
- package/dist/global-skills/instrument-product-analytics/references/react-native.md +161 -155
- package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +12 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +15 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +12 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +26 -41
- package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +37 -83
- package/dist/global-skills/instrument-product-analytics/references/ruby.md +48 -108
- package/dist/global-skills/instrument-product-analytics/references/svelte.md +18 -24
- package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +17 -19
- package/dist/global-skills/instrument-product-analytics/references/usage.md +14 -63
- package/dist/global-skills/instrument-product-analytics/references/vue-js.md +29 -28
- package/dist/global-skills/manifest.json +8 -2
- package/dist/global-skills/mongodb-search-and-ai/SKILL.md +28 -37
- package/dist/global-skills/mongodb-search-and-ai/references/automated-embedding.md +438 -0
- package/dist/global-skills/mongodb-search-and-ai/references/hybrid-search.md +60 -4
- package/dist/global-skills/mongodb-search-and-ai/references/vector-search.md +46 -108
- package/dist/global-skills/neon/SKILL.md +207 -213
- package/dist/global-skills/neon/references/auth.md +12 -0
- package/dist/global-skills/neon/references/claimable-neon.md +10 -14
- package/dist/global-skills/neon/references/function-triggers.md +53 -0
- package/dist/global-skills/neon/references/logs-loki.md +61 -0
- package/dist/global-skills/neon/references/parse-env.md +32 -0
- package/dist/global-skills/neon/references/sdk.md +7 -0
- package/dist/global-skills/neon-ai-gateway/SKILL.md +14 -16
- package/dist/global-skills/neon-auth/SKILL.md +155 -0
- package/dist/global-skills/neon-auth/references/managed-auth.md +173 -0
- package/dist/global-skills/neon-auth/references/self-managed.md +25 -0
- package/dist/global-skills/neon-functions/SKILL.md +159 -84
- package/dist/global-skills/neon-functions/references/ai-sdk.md +4 -6
- package/dist/global-skills/neon-functions/references/function-triggers.md +249 -0
- package/dist/global-skills/neon-functions/references/mastra-studio.md +3 -3
- package/dist/global-skills/neon-functions/references/mcp.md +1 -1
- package/dist/global-skills/neon-functions/references/production-hardening.md +340 -0
- package/dist/global-skills/neon-functions/references/sse.md +8 -5
- package/dist/global-skills/neon-object-storage/SKILL.md +10 -11
- package/dist/global-skills/neon-postgres/SKILL.md +120 -17
- package/dist/global-skills/neon-postgres/references/full-text-search.md +99 -0
- package/dist/global-skills/neon-postgres/references/hybrid-search.md +90 -0
- package/dist/global-skills/neon-postgres/references/lakebase-search-drizzle.md +172 -0
- package/dist/global-skills/neon-postgres/references/vector-search.md +137 -0
- package/dist/global-skills/neon-postgres-branches/SKILL.md +3 -3
- package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +1 -1
- package/dist/global-skills/onboarding/SKILL.md +8 -6
- package/dist/global-skills/resend/SKILL.md +4 -2
- package/dist/global-skills/resend/references/broadcasts.md +6 -1
- package/dist/global-skills/resend/references/receiving.md +29 -10
- package/dist/global-skills/resend/references/sending/email-management.md +14 -4
- package/dist/global-skills/resend/references/topics.md +9 -6
- package/dist/global-skills/resend/references/usage.md +117 -0
- package/dist/global-skills/resend/references/webhooks.md +59 -2
- package/dist/global-skills/stripe-best-practices/SKILL.md +35 -29
- package/dist/global-skills/stripe-best-practices/references/billing.md +9 -2
- package/dist/global-skills/stripe-best-practices/references/payments.md +4 -2
- package/dist/global-skills/stripe-best-practices/references/security.md +3 -1
- package/dist/global-skills/stripe-best-practices/references/tax.md +39 -20
- package/dist/global-skills/supabase/SKILL.md +6 -0
- package/dist/global-skills/use-railway/SKILL.md +42 -22
- package/dist/global-skills/use-railway/references/analyze-db.md +7 -6
- package/dist/global-skills/use-railway/references/cloud-agents.md +70 -0
- package/dist/global-skills/use-railway/references/configure.md +17 -2
- package/dist/global-skills/use-railway/references/databases.md +107 -0
- package/dist/global-skills/use-railway/references/deploy.md +5 -5
- package/dist/global-skills/use-railway/references/feature-flags.md +25 -13
- package/dist/global-skills/use-railway/references/iac.md +66 -77
- package/dist/global-skills/use-railway/references/operate.md +26 -3
- package/dist/global-skills/use-railway/references/request.md +31 -23
- package/dist/global-skills/use-railway/references/setup.md +16 -5
- package/dist/global-skills/use-railway/references/tracing.md +261 -0
- package/dist/global-skills/use-railway/references/usage.md +52 -0
- package/dist/global-skills/use-railway/scripts/analyze-mongo.py +0 -0
- package/dist/global-skills/use-railway/scripts/analyze-mysql.py +0 -0
- package/dist/global-skills/use-railway/scripts/analyze-postgres.py +0 -0
- package/dist/global-skills/use-railway/scripts/analyze-redis.py +0 -0
- package/dist/global-skills/use-railway/scripts/enable-pg-stats.py +0 -0
- package/dist/global-skills/use-railway/scripts/pg-extensions.py +0 -0
- package/dist/global-skills/use-railway/scripts/railway-api.sh +0 -0
- package/dist/global-skills/validate-my-idea/SKILL.md +54 -0
- package/dist/global-skills/{feedback → vybekiit-feedback}/SKILL.md +16 -12
- package/dist/global-skills/watch-my-app/SKILL.md +53 -0
- package/dist/global-skills/workers-best-practices/SKILL.md +36 -103
- package/dist/global-skills/workers-best-practices/references/configuration.md +139 -0
- package/dist/global-skills/workers-best-practices/references/platform-apis.md +51 -0
- package/dist/global-skills/workers-best-practices/references/{rules.md → runtime-patterns.md} +13 -137
- package/dist/global-skills/wrangler/SKILL.md +48 -901
- package/dist/global-skills/xcode-project-setup/scripts/xcode_spm_setup/Sources/main.swift +19 -15
- package/package.json +22 -22
- package/dist/global-skills/expo-native-ui/references/animations.md +0 -220
- package/dist/global-skills/firebase-firestore/references/enterprise/security_rules.md +0 -577
- 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 `
|
|
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)
|
|
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
|
-
|
|
26
|
+
timer = setInterval(
|
|
26
27
|
() => controller.enqueue(encoder.encode(": ping\n\n")),
|
|
27
28
|
25_000,
|
|
28
29
|
);
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
});
|