vybekiit 0.7.3 → 0.7.7
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 +16607 -7527
- package/dist/global-skills/aws-cdk/SKILL.md +2 -1
- package/dist/global-skills/aws-cloudformation/SKILL.md +2 -1
- package/dist/global-skills/aws-serverless/SKILL.md +45 -44
- package/dist/global-skills/aws-serverless/assets/powertools-handler.py +2 -1
- package/dist/global-skills/aws-serverless/references/api-gateway.md +50 -470
- package/dist/global-skills/aws-serverless/references/architecture.md +47 -186
- package/dist/global-skills/aws-serverless/references/concurrency.md +44 -158
- package/dist/global-skills/aws-serverless/references/deployment.md +1 -1
- package/dist/global-skills/aws-serverless/references/event-sources.md +72 -391
- package/dist/global-skills/aws-serverless/references/lambda.md +69 -428
- package/dist/global-skills/aws-serverless/references/orchestration.md +65 -384
- package/dist/global-skills/aws-serverless/references/production.md +78 -415
- package/dist/global-skills/aws-serverless/references/troubleshooting.md +92 -619
- package/dist/global-skills/claimable-postgres/SKILL.md +60 -49
- package/dist/global-skills/design-my-data/SKILL.md +2 -0
- package/dist/global-skills/eas-app-stores/SKILL.md +8 -0
- package/dist/global-skills/eas-app-stores/references/play-store.md +2 -0
- package/dist/global-skills/eas-hosting/SKILL.md +8 -0
- package/dist/global-skills/eas-observe/SKILL.md +30 -8
- package/dist/global-skills/eas-observe/agents/openai.yaml +1 -1
- package/dist/global-skills/eas-observe/references/metrics.md +54 -3
- package/dist/global-skills/eas-observe/references/queries.md +78 -17
- package/dist/global-skills/eas-observe/references/setup.md +164 -5
- package/dist/global-skills/eas-observe/references/third-party.md +136 -0
- package/dist/global-skills/eas-simulator/SKILL.md +63 -11
- package/dist/global-skills/eas-simulator/references/controllers.md +21 -2
- package/dist/global-skills/eas-simulator/references/run-your-app.md +16 -11
- package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -5
- package/dist/global-skills/eas-update-insights/SKILL.md +8 -0
- package/dist/global-skills/eas-workflows/SKILL.md +15 -10
- package/dist/global-skills/eas-workflows/scripts/package.json +2 -7
- package/dist/global-skills/email-best-practices/.github/workflows/sync-skills.yml +30 -0
- package/dist/global-skills/email-best-practices/README.md +63 -0
- package/dist/global-skills/email-best-practices/references/accessibility.md +189 -0
- package/dist/global-skills/email-best-practices/references/compliance.md +125 -0
- package/dist/global-skills/email-best-practices/references/deliverability.md +121 -0
- package/dist/global-skills/email-best-practices/references/email-capture.md +129 -0
- package/dist/global-skills/email-best-practices/references/email-types.md +173 -0
- package/dist/global-skills/email-best-practices/references/list-management.md +157 -0
- package/dist/global-skills/email-best-practices/references/marketing-emails.md +115 -0
- package/dist/global-skills/email-best-practices/references/sending-reliability.md +155 -0
- package/dist/global-skills/email-best-practices/references/transactional-email-catalog.md +418 -0
- package/dist/global-skills/email-best-practices/references/transactional-emails.md +92 -0
- package/dist/global-skills/email-best-practices/references/webhooks-events.md +167 -0
- package/dist/global-skills/email-best-practices/tests/README.md +35 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/01-spam-deliverability.md +46 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/02-multi-region-compliance.md +48 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/03-retry-idempotency.md +36 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/04-webhook-bounce-handling.md +52 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/05-new-saas-email-plan.md +51 -0
- package/dist/global-skills/expo-animation/LICENSE +21 -0
- package/dist/global-skills/expo-animation/RECIPES.md +385 -0
- package/dist/global-skills/expo-animation/SKILL.md +267 -0
- package/dist/global-skills/expo-animation/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-app-clip/SKILL.md +8 -0
- package/dist/global-skills/expo-brownfield/SKILL.md +8 -0
- package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +53 -4
- package/dist/global-skills/expo-data-fetching/SKILL.md +8 -0
- package/dist/global-skills/expo-design-system/SKILL.md +356 -0
- package/dist/global-skills/expo-design-system/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-design-system/references/audit.md +185 -0
- package/dist/global-skills/expo-dev-client/SKILL.md +8 -0
- package/dist/global-skills/expo-dom/SKILL.md +8 -0
- package/dist/global-skills/expo-examples/SKILL.md +8 -0
- package/dist/global-skills/expo-migrate-module/SKILL.md +113 -0
- package/dist/global-skills/expo-migrate-module/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-migrate-module/references/compatibility.md +73 -0
- package/dist/global-skills/expo-migrate-module/references/example.md +212 -0
- package/dist/global-skills/expo-migrate-module/references/migration-map.md +306 -0
- package/dist/global-skills/expo-module/SKILL.md +11 -1
- package/dist/global-skills/expo-native-ui/SKILL.md +8 -0
- package/dist/global-skills/expo-project-structure/SKILL.md +114 -0
- package/dist/global-skills/expo-project-structure/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-router/SKILL.md +8 -0
- package/dist/global-skills/expo-skill-feedback/SKILL.md +66 -33
- package/dist/global-skills/expo-skill-feedback/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-tailwind-setup/SKILL.md +8 -0
- package/dist/global-skills/expo-ui/SKILL.md +8 -0
- package/dist/global-skills/expo-upgrade/SKILL.md +15 -1
- package/dist/global-skills/expo-web-to-native/SKILL.md +8 -0
- package/dist/global-skills/extension-to-functions-codebase/SKILL.md +129 -0
- package/dist/global-skills/extension-to-functions-codebase/references/configuration-migration.md +161 -0
- package/dist/global-skills/extension-to-functions-codebase/references/destructuring-shim.md +122 -0
- package/dist/global-skills/extension-to-functions-codebase/references/signature-mapping.md +84 -0
- package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +61 -19
- package/dist/global-skills/firebase-ai-logic-basics/references/flutter_setup.md +6 -3
- package/dist/global-skills/firebase-app-hosting-basics/SKILL.md +5 -1
- package/dist/global-skills/firebase-auth-basics/SKILL.md +4 -2
- package/dist/global-skills/firebase-basics/SKILL.md +5 -5
- package/dist/global-skills/firebase-crashlytics/SKILL.md +2 -0
- package/dist/global-skills/firebase-data-connect/SKILL.md +2 -0
- package/dist/global-skills/firebase-firestore/SKILL.md +3 -6
- package/dist/global-skills/firebase-hosting-basics/SKILL.md +4 -1
- package/dist/global-skills/firebase-remote-config-basics/SKILL.md +5 -1
- package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +4 -1
- package/dist/global-skills/harden/SKILL.md +2 -1
- package/dist/global-skills/instrument-feature-flags/SKILL.md +1 -0
- package/dist/global-skills/instrument-feature-flags/references/COMMANDMENTS.md +5 -0
- package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/android.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/api.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/best-practices.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/django.md +38 -5
- package/dist/global-skills/instrument-feature-flags/references/dotnet.md +10 -4
- package/dist/global-skills/instrument-feature-flags/references/elixir.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/flask.md +17 -5
- package/dist/global-skills/instrument-feature-flags/references/flutter.md +7 -5
- package/dist/global-skills/instrument-feature-flags/references/go.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/ios.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/java.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/laravel.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/next-js.md +74 -8
- package/dist/global-skills/instrument-feature-flags/references/nodejs.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/php.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/python.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/react-native.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/react.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/ruby.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/rust.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/usage.md +41 -4
- package/dist/global-skills/instrument-feature-flags/references/web.md +7 -5
- package/dist/global-skills/instrument-product-analytics/SKILL.md +2 -1
- package/dist/global-skills/instrument-product-analytics/references/COMMANDMENTS.md +5 -0
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +12 -14
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +25 -21
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-django.md +75 -55
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-fastapi.md +3 -6
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-flask.md +26 -16
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-nuxt-3-6.md +8 -5
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-nuxt-4.md +4 -4
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-react-react-router-7-framework.md +3 -3
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +8 -4
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-tanstack-start.md +20 -8
- package/dist/global-skills/instrument-product-analytics/references/android.md +46 -4
- package/dist/global-skills/instrument-product-analytics/references/angular.md +41 -5
- package/dist/global-skills/instrument-product-analytics/references/astro.md +41 -5
- package/dist/global-skills/instrument-product-analytics/references/configuration.md +7 -4
- package/dist/global-skills/instrument-product-analytics/references/django.md +38 -5
- package/dist/global-skills/instrument-product-analytics/references/dotnet.md +10 -4
- package/dist/global-skills/instrument-product-analytics/references/elixir.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/flask.md +17 -5
- package/dist/global-skills/instrument-product-analytics/references/flutter.md +49 -4
- package/dist/global-skills/instrument-product-analytics/references/go.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/identify-users.md +38 -7
- package/dist/global-skills/instrument-product-analytics/references/ios.md +14 -4
- package/dist/global-skills/instrument-product-analytics/references/laravel.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/next-js.md +74 -8
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +39 -7
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +40 -4
- package/dist/global-skills/instrument-product-analytics/references/php.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +21 -17
- package/dist/global-skills/instrument-product-analytics/references/python.md +15 -5
- package/dist/global-skills/instrument-product-analytics/references/react-native.md +41 -7
- package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/ruby.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/svelte.md +42 -6
- package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +31 -7
- package/dist/global-skills/instrument-product-analytics/references/usage.md +41 -4
- package/dist/global-skills/instrument-product-analytics/references/vue-js.md +42 -6
- package/dist/global-skills/manifest.json +6 -1
- package/dist/global-skills/neon/SKILL.md +235 -121
- package/dist/global-skills/neon-ai-gateway/SKILL.md +95 -16
- package/dist/global-skills/neon-functions/SKILL.md +242 -152
- package/dist/global-skills/neon-functions/references/ai-sdk.md +9 -3
- package/dist/global-skills/neon-functions/references/mastra-studio.md +1 -1
- package/dist/global-skills/neon-functions/references/mcp.md +13 -4
- package/dist/global-skills/neon-functions/references/sentry.md +165 -52
- package/dist/global-skills/neon-functions/references/sse.md +23 -6
- package/dist/global-skills/neon-object-storage/SKILL.md +40 -16
- package/dist/global-skills/neon-postgres/SKILL.md +63 -262
- package/dist/global-skills/neon-postgres-branches/SKILL.md +61 -50
- package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +22 -3
- package/dist/global-skills/resend/SKILL.md +6 -6
- package/dist/global-skills/resend/references/automations.md +2 -2
- package/dist/global-skills/resend/references/broadcasts.md +11 -3
- package/dist/global-skills/resend/references/domains.md +25 -3
- package/dist/global-skills/resend/references/installation.md +7 -7
- package/dist/global-skills/setup-payments/SKILL.md +4 -5
- package/dist/global-skills/stripe-best-practices/SKILL.md +32 -7
- package/dist/global-skills/stripe-best-practices/references/billing.md +11 -1
- package/dist/global-skills/stripe-best-practices/references/payments.md +23 -2
- package/dist/global-skills/stripe-best-practices/references/security.md +21 -11
- package/dist/global-skills/stripe-best-practices/references/tax.md +116 -11
- package/dist/global-skills/supabase/CHANGELOG.md +25 -0
- package/dist/global-skills/supabase/SKILL.md +5 -1
- package/dist/global-skills/supabase-postgres-best-practices/CHANGELOG.md +30 -0
- package/dist/global-skills/supabase-postgres-best-practices/SKILL.md +1 -1
- package/dist/global-skills/use-kit-mcp/SKILL.md +5 -5
- package/dist/global-skills/use-railway/SKILL.md +4 -4
- package/dist/global-skills/use-railway/references/iac.md +206 -57
- package/dist/global-skills/use-railway/scripts/railway-api.sh +7 -2
- package/dist/global-skills/xcode-project-setup/SKILL.md +2 -0
- package/package.json +12 -9
- package/LICENSE.md +0 -47
- package/dist/global-skills/eas-workflows/scripts/validate.js +0 -84
- package/dist/global-skills/expo-skill-feedback/scripts/skill-feedback.cjs +0 -118
- package/dist/global-skills/neon-functions/references/hono-websockets.md +0 -145
|
@@ -32,6 +32,7 @@ The function's default export is a web-standard `{ fetch }` handler. The `@neon/
|
|
|
32
32
|
```typescript
|
|
33
33
|
// src/index.ts
|
|
34
34
|
import { neon } from "@neon/ai-sdk-provider";
|
|
35
|
+
import { attachDatabasePool } from "@neon/functions";
|
|
35
36
|
import { streamText, tool, stepCountIs, type ModelMessage } from "ai";
|
|
36
37
|
import { z } from "zod";
|
|
37
38
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
@@ -39,6 +40,7 @@ import { Pool } from "pg";
|
|
|
39
40
|
import { todos } from "./db/schema";
|
|
40
41
|
|
|
41
42
|
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
|
|
43
|
+
attachDatabasePool(pool);
|
|
42
44
|
const db = drizzle(pool);
|
|
43
45
|
|
|
44
46
|
export default {
|
|
@@ -68,7 +70,8 @@ export default {
|
|
|
68
70
|
});
|
|
69
71
|
|
|
70
72
|
return result.toUIMessageStreamResponse({
|
|
71
|
-
onError: (error) =>
|
|
73
|
+
onError: (error) =>
|
|
74
|
+
error instanceof Error ? error.message : String(error),
|
|
72
75
|
});
|
|
73
76
|
},
|
|
74
77
|
};
|
|
@@ -91,7 +94,8 @@ const files = new Files({ adapter: neonFiles({ bucket: "images" }) });
|
|
|
91
94
|
|
|
92
95
|
const result = streamText({
|
|
93
96
|
model: neon("gpt-5-mini"),
|
|
94
|
-
system:
|
|
97
|
+
system:
|
|
98
|
+
"Use image_generation when the user asks for a picture, then describe it.",
|
|
95
99
|
messages,
|
|
96
100
|
tools: {
|
|
97
101
|
image_generation: neon.tools.imageGeneration({
|
|
@@ -106,7 +110,9 @@ const result = streamText({
|
|
|
106
110
|
const base64 = imageResultBase64(tr.output);
|
|
107
111
|
if (!base64) continue;
|
|
108
112
|
const key = `generated/${randomUUID()}.jpg`;
|
|
109
|
-
await files.upload(key, Buffer.from(base64, "base64"), {
|
|
113
|
+
await files.upload(key, Buffer.from(base64, "base64"), {
|
|
114
|
+
contentType: "image/jpeg",
|
|
115
|
+
});
|
|
110
116
|
// …insert a row keyed by `key` into Postgres; serve later via files.url(key)
|
|
111
117
|
}
|
|
112
118
|
},
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A Neon Function is a long-lived Node.js 24 process, which makes it a natural host for a [Mastra](https://mastra.ai) agent: the agent keeps running for the life of the request, and you point its model at the Neon AI Gateway so there are no extra provider keys. You can keep **running the agent on Neon Functions** while shipping its traces to a **Mastra Studio (Mastra Cloud) project** for observability — the agent runs on Neon, the traces are viewable in Mastra.
|
|
4
4
|
|
|
5
|
-
The shape mirrors any other Node integration (see
|
|
5
|
+
The shape mirrors any other Node integration (see [sentry.md](sentry.md)): instantiate at module load, gate on env vars so local dev and unconfigured branches stay a no-op, and pass secrets at deploy time via `neon.ts`. `@mastra/core` and `@mastra/observability` bundle cleanly through `neon deploy`'s esbuild with no extra config.
|
|
6
6
|
|
|
7
7
|
## 1. Define the agent against the Neon AI Gateway
|
|
8
8
|
|
|
@@ -17,10 +17,11 @@ import { eq } from "drizzle-orm";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
19
|
import { StreamableHTTPTransport } from "@hono/mcp";
|
|
20
|
+
import { attachDatabasePool } from "@neon/functions";
|
|
20
21
|
import { contacts } from "./db/schema";
|
|
21
22
|
|
|
22
|
-
// One pool per isolate, reused across requests.
|
|
23
23
|
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
|
|
24
|
+
attachDatabasePool(pool);
|
|
24
25
|
const db = drizzle(pool);
|
|
25
26
|
|
|
26
27
|
const mcpServer = new McpServer({ name: "contacts", version: "1.0.0" });
|
|
@@ -52,8 +53,15 @@ mcpServer.registerTool(
|
|
|
52
53
|
inputSchema: { id: z.number().int().positive() },
|
|
53
54
|
},
|
|
54
55
|
async ({ id }) => {
|
|
55
|
-
const [row] = await db
|
|
56
|
-
|
|
56
|
+
const [row] = await db
|
|
57
|
+
.delete(contacts)
|
|
58
|
+
.where(eq(contacts.id, id))
|
|
59
|
+
.returning();
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{ type: "text", text: JSON.stringify(row ?? { error: "not found" }) },
|
|
63
|
+
],
|
|
64
|
+
};
|
|
57
65
|
},
|
|
58
66
|
);
|
|
59
67
|
|
|
@@ -124,7 +132,8 @@ Either way it's one check at the top of the `/mcp` route — reject anything tha
|
|
|
124
132
|
```typescript
|
|
125
133
|
app.all("/mcp", async (c) => {
|
|
126
134
|
const auth = c.req.header("authorization");
|
|
127
|
-
if (!(await isValidApiKey(auth)))
|
|
135
|
+
if (!(await isValidApiKey(auth)))
|
|
136
|
+
return c.json({ error: "unauthorized" }, 401);
|
|
128
137
|
if (!mcpServer.isConnected()) await mcpServer.connect(transport);
|
|
129
138
|
return transport.handleRequest(c);
|
|
130
139
|
});
|
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sentry error monitoring on Neon Functions
|
|
2
2
|
|
|
3
3
|
A Neon Function is a **long-lived Node.js 24 process running a web-standard request/response handler** — not an edge worker or a short-lived lambda. That means any integration SDK that works in an ordinary Node process works here unchanged: you initialize it once at module load, before your handler starts serving requests, and it stays instrumented for the life of the isolate.
|
|
4
4
|
|
|
5
|
-
This reference walks through wiring up Sentry for
|
|
5
|
+
This reference walks through wiring up Sentry for errors, logs, and traces — including AI-agent tracing, since agents are the workload Functions are built for. The same shape (init module imported first, gated on an env var, secret passed at deploy time) applies to other Node SDKs — see [Other Node integrations](#other-node-integrations) at the end.
|
|
6
6
|
|
|
7
|
-
## Sentry (
|
|
7
|
+
## Sentry (errors, logs, and traces)
|
|
8
8
|
|
|
9
|
-
Because the runtime is a normal Node process, use the Node SDK `@sentry/node` — not an edge/serverless wrapper.
|
|
9
|
+
Because the runtime is a normal Node process, use the Node SDK `@sentry/node` — not an edge/serverless wrapper. **Use `@sentry/node` ≥10.67.0**: older versions fail to register their tracer against the OpenTelemetry API global the runtime pre-creates, and every span is silently non-recording while errors and logs keep working. The SDK bundles cleanly through `neon deploy`'s esbuild with no extra build config.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Sentry has distinct signals, and picking the right one is the main instrumentation decision:
|
|
12
12
|
|
|
13
|
-
1.
|
|
14
|
-
2.
|
|
15
|
-
3.
|
|
13
|
+
1. **Errors** — unhandled route errors and failures your code can't recover from. Each becomes a grouped, alertable _issue_.
|
|
14
|
+
2. **Logs** — recoverable failures and narrative events (a model attempt failed and the agent moved on, a retry, a fallback). Structured, searchable, linked to the trace — and they don't pollute the issue stream.
|
|
15
|
+
3. **Traces** — the request's span tree: the incoming request, outbound fetches, and (for agents) the full model/tool call hierarchy with token usage.
|
|
16
|
+
|
|
17
|
+
All three carry the same trace ID, so from an error you can pivot to the logs and spans of the same request.
|
|
18
|
+
|
|
19
|
+
### Environment variables
|
|
20
|
+
|
|
21
|
+
| Variable | Purpose |
|
|
22
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
23
|
+
| `SENTRY_DSN` | Project DSN; keep it configurable through the deployment environment. |
|
|
24
|
+
| `SENTRY_RELEASE` | Optional release identifier such as a commit SHA — unlocks regression detection. |
|
|
25
|
+
| `SENTRY_TRACES_SAMPLE_RATE` | Trace sample rate, default `1`. Agents are low-throughput and every trace is interesting; lower it for high-volume plain HTTP. |
|
|
26
|
+
| `PRODUCTION_BRANCH` | Your default branch's name, so it reports as environment `production` (see below). |
|
|
16
27
|
|
|
17
28
|
### 1. Initialize before anything else
|
|
18
29
|
|
|
@@ -25,16 +36,25 @@ import * as Sentry from "@sentry/node";
|
|
|
25
36
|
Sentry.init({
|
|
26
37
|
dsn: process.env.SENTRY_DSN,
|
|
27
38
|
enabled: Boolean(process.env.SENTRY_DSN),
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
enableLogs: true,
|
|
40
|
+
tracesSampleRate: Number(process.env.SENTRY_TRACES_SAMPLE_RATE ?? 1),
|
|
41
|
+
traceLifecycle: "stream",
|
|
42
|
+
streamGenAiSpans: true,
|
|
43
|
+
integrations: [
|
|
44
|
+
Sentry.vercelAIIntegration({ force: true }),
|
|
45
|
+
Sentry.httpIntegration({ disableIncomingRequestSpans: true }),
|
|
46
|
+
],
|
|
47
|
+
release: process.env.SENTRY_RELEASE,
|
|
32
48
|
environment:
|
|
33
|
-
process.env.NEON_BRANCH &&
|
|
49
|
+
process.env.NEON_BRANCH &&
|
|
50
|
+
process.env.NEON_BRANCH !== process.env.PRODUCTION_BRANCH
|
|
34
51
|
? process.env.NEON_BRANCH
|
|
35
52
|
: "production",
|
|
36
53
|
});
|
|
37
54
|
|
|
55
|
+
process.on("SIGTERM", () => void Sentry.flush(2000));
|
|
56
|
+
process.on("SIGINT", () => void Sentry.flush(2000));
|
|
57
|
+
|
|
38
58
|
export { Sentry };
|
|
39
59
|
```
|
|
40
60
|
|
|
@@ -42,37 +62,66 @@ export { Sentry };
|
|
|
42
62
|
// src/index.ts
|
|
43
63
|
import "./instrument"; // MUST be the first import, before the framework/agent
|
|
44
64
|
import { Sentry } from "./instrument";
|
|
65
|
+
import { attachDatabasePool } from "@neon/functions";
|
|
45
66
|
import { Hono } from "hono";
|
|
67
|
+
import { Pool } from "pg";
|
|
68
|
+
|
|
69
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
|
|
70
|
+
attachDatabasePool(pool, {
|
|
71
|
+
onUnexpectedError: (err) => Sentry.captureException(err),
|
|
72
|
+
});
|
|
73
|
+
|
|
46
74
|
// ... rest of the function
|
|
47
75
|
```
|
|
48
76
|
|
|
49
77
|
- **Gate `enabled` on the DSN.** Local dev (`neon dev`) and any branch where you haven't configured the secret then become a no-op — no init, no noise — without changing code.
|
|
50
|
-
-
|
|
78
|
+
- **`enableLogs: true`** — the `Sentry.logger.*` API is off by default.
|
|
79
|
+
- **`traceLifecycle: "stream"`** — sends each span as it finishes instead of holding the whole tree until the request ends, so spans that complete after the response (streaming agent calls) aren't lost. `streamGenAiSpans: true` is the default on current SDKs (sends gen_ai spans as standalone items so large prompts aren't truncated); set it to `false` on self-hosted Sentry.
|
|
80
|
+
- **The two integrations:** `vercelAIIntegration({ force: true })` because `neon deploy` bundles your code, which defeats the integration's module detection; `httpIntegration({ disableIncomingRequestSpans: true })` because the request root span comes from the middleware in step 3 (the runtime's internal server would otherwise add a duplicate with an unhelpful name).
|
|
81
|
+
- **Environment:** `NEON_BRANCH` is injected on every branch — including the default — and holds the branch **name** (e.g. `main`, `preview/add-auth`). Because it's always present, don't use it as a boolean flag; compare it against your default branch's name (passed in as `PRODUCTION_BRANCH`) so the default branch reads as `production` and other branches tag by name. Pass `SENTRY_ENVIRONMENT` explicitly per deploy to override.
|
|
82
|
+
- **Flush on shutdown:** the runtime sends `SIGTERM`/`SIGINT` before evicting an idle isolate; Sentry buffers logs and batches spans, so flush or the tail gets dropped.
|
|
83
|
+
- **Idle `pg` pool errors:** call `attachDatabasePool(pool)` (or pass `onUnexpectedError: (err) => Sentry.captureException(err)` on the first call). Don't `pool.end()` on SIGINT — Neon's pooler reclaims those connections. See [Connecting to Postgres](../SKILL.md#connecting-to-postgres).
|
|
51
84
|
|
|
52
85
|
### 2. Provide the DSN as a deploy-time secret
|
|
53
86
|
|
|
54
|
-
The DSN is your own secret, so set it per-deployment (see
|
|
87
|
+
The DSN is your own secret, so set it per-deployment (see [Environment Variables](../SKILL.md#environment-variables)). Either pass it on deploy:
|
|
55
88
|
|
|
56
89
|
```bash
|
|
57
90
|
neon functions deploy <slug> --src src/index.ts \
|
|
58
|
-
--env "SENTRY_DSN=https://…@…ingest.us.sentry.io/…"
|
|
91
|
+
--env "SENTRY_DSN=https://…@…ingest.us.sentry.io/…" \
|
|
92
|
+
--env "SENTRY_RELEASE=$(git rev-parse --short HEAD)" \
|
|
93
|
+
--env "SENTRY_TRACES_SAMPLE_RATE=1"
|
|
59
94
|
```
|
|
60
95
|
|
|
61
|
-
or declare it under the function's `env` in `neon.ts` (read from `process.env` to avoid hardcoding)
|
|
96
|
+
or declare it under the function's `env` in `neon.ts` (read from `process.env` to avoid hardcoding). Deploy env vars **persist and accumulate across deployments** — omitting `--env` on a later deploy does not clear a variable set earlier.
|
|
97
|
+
|
|
98
|
+
### 3. Create the request span and catch route errors
|
|
99
|
+
|
|
100
|
+
The runtime invokes your handler through its own ingress rather than a plain `node:http` server, so give each request an isolation scope and a root span yourself — one Hono middleware covers it, and everything else (gen_ai spans, logs, outbound fetches) nests under it with clean route names. The `flush` at the end matters: an idle isolate can be suspended, so buffered telemetry has to ship while the request is alive.
|
|
62
101
|
|
|
63
102
|
```typescript
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
103
|
+
app.use("*", (c, next) =>
|
|
104
|
+
Sentry.withIsolationScope(() =>
|
|
105
|
+
Sentry.startSpan(
|
|
106
|
+
{
|
|
107
|
+
op: "http.server",
|
|
108
|
+
name: `${c.req.method} ${c.req.path}`,
|
|
109
|
+
forceTransaction: true,
|
|
110
|
+
attributes: {
|
|
111
|
+
"http.request.method": c.req.method,
|
|
112
|
+
"url.path": c.req.path,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
async (span) => {
|
|
116
|
+
await next();
|
|
117
|
+
span.setAttribute("http.response.status_code", c.res.status);
|
|
118
|
+
},
|
|
119
|
+
).finally(() => Sentry.flush(2000)),
|
|
120
|
+
),
|
|
121
|
+
);
|
|
71
122
|
```
|
|
72
123
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Wire a top-level error handler in your HTTP framework so any error thrown in a route is reported. With Hono, `onError` covers this. Watch out for one gotcha: framework middleware such as `cors()` usually does **not** decorate error responses, so re-add any headers you need on the 500 yourself.
|
|
124
|
+
Then wire a top-level error handler so any error thrown in a route is reported. With Hono, `onError` covers this. Watch out for one gotcha: framework middleware such as `cors()` usually does **not** decorate error responses, so re-add any headers you need on the 500 yourself.
|
|
76
125
|
|
|
77
126
|
```typescript
|
|
78
127
|
app.onError((err, c) => {
|
|
@@ -82,50 +131,114 @@ app.onError((err, c) => {
|
|
|
82
131
|
});
|
|
83
132
|
```
|
|
84
133
|
|
|
85
|
-
|
|
134
|
+
(There is a dedicated `@sentry/hono` package, but it is alpha and its Node entry point assumes the app is served by `@hono/node-server`, which is not how Functions run Hono — stick with `onError`.)
|
|
86
135
|
|
|
87
|
-
|
|
136
|
+
### 4. Errors are for failures; logs are for the story
|
|
88
137
|
|
|
89
|
-
|
|
138
|
+
Long-running agent workloads — the case Neon Functions are built for — typically **catch their own errors and fall back** (retry a different model, return a degraded result) rather than throwing. It's tempting to `captureException` those too, but every recovered retry then opens a warning-level issue: the issue stream fills with things nobody needs to act on, and the terminal failures drown in them.
|
|
139
|
+
|
|
140
|
+
Split by whether someone needs to act:
|
|
141
|
+
|
|
142
|
+
- **`Sentry.captureException` — terminal, needs attention.** The agent exhausted every fallback; an invariant broke. These become issues, group, and alert.
|
|
143
|
+
- **`Sentry.logger.*` — recoverable or narrative.** A model attempt failed and the agent moved on; an input couldn't be fetched; a milestone was reached. Structured log records, searchable by attribute and attached to the request's trace — the story you read _after_ an issue fires.
|
|
144
|
+
|
|
145
|
+
A representative agent that tries several models in order:
|
|
90
146
|
|
|
91
147
|
```typescript
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
148
|
+
for (const model of models) {
|
|
149
|
+
try {
|
|
150
|
+
const { text: summary } = await generateText({
|
|
151
|
+
model: neon(model),
|
|
152
|
+
prompt,
|
|
153
|
+
experimental_telemetry: { isEnabled: true },
|
|
154
|
+
});
|
|
155
|
+
Sentry.logger.info("summary produced", { component: "agent", model });
|
|
156
|
+
return c.json({ summary, model });
|
|
157
|
+
} catch (err) {
|
|
158
|
+
lastError = err;
|
|
159
|
+
Sentry.logger.warn("model attempt failed", {
|
|
160
|
+
component: "agent",
|
|
161
|
+
phase: "summarize-attempt",
|
|
162
|
+
model,
|
|
163
|
+
error: String(err),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Sentry.captureException(lastError, {
|
|
169
|
+
tags: { component: "agent", phase: "summarize-all-failed" },
|
|
170
|
+
contexts: { agent: { attempts: models.length } },
|
|
97
171
|
});
|
|
172
|
+
return c.json({ error: "all models failed" }, 502);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- Log **attributes** (the second argument — flat `string | number | boolean` values) are individually searchable and filterable in Sentry's Logs view.
|
|
176
|
+
- On the remaining `captureException` calls, use `tags` for the dimensions you'll filter and group by, and `contexts` for structured per-event detail (`contexts` replaces the legacy `extra`).
|
|
177
|
+
- Logs emitted during a request automatically link to its trace, so from the terminal error you can pull up every preceding attempt.
|
|
178
|
+
|
|
179
|
+
### 5. Trace the agent itself
|
|
98
180
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
181
|
+
If the function runs a [Vercel AI SDK](https://ai-sdk.dev) agent (see [references/ai-sdk.md](ai-sdk.md)), Sentry captures the full agent → model → tool span hierarchy with token usage per call — `gen_ai.invoke_agent`, `gen_ai.generate_content`, `gen_ai.execute_tool` spans in the request's trace, plus the **Insights → AI Agents** dashboard. On top of the init config from step 1:
|
|
182
|
+
|
|
183
|
+
**Opt each call in** — the AI SDK only emits spans when asked — and **report stream errors**, because `streamText` never throws: failures surface as error parts inside the stream and the HTTP response just ends, so without `onError` a dead agent looks like an empty reply.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const result = streamText({
|
|
187
|
+
model: neon(MODEL),
|
|
188
|
+
messages,
|
|
189
|
+
tools,
|
|
190
|
+
experimental_telemetry: { isEnabled: true },
|
|
191
|
+
onError: ({ error }) => {
|
|
192
|
+
Sentry.captureException(error, {
|
|
193
|
+
tags: { component: "agent", phase: "chat-stream" },
|
|
194
|
+
});
|
|
195
|
+
},
|
|
104
196
|
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Flush when the stream completes.** The middleware's flush runs when the `Response` object is created — before the model finishes — and the gen_ai spans only end with the stream. Ship them from the stream's own finalizer, while the request is still alive:
|
|
105
200
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
201
|
+
```typescript
|
|
202
|
+
const stream = result.textStream
|
|
203
|
+
.pipeThrough(
|
|
204
|
+
new TransformStream<string, string>({
|
|
205
|
+
async flush() {
|
|
206
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
207
|
+
await Sentry.flush(2000);
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
)
|
|
211
|
+
.pipeThrough(new TextEncoderStream());
|
|
212
|
+
return new Response(stream, {
|
|
213
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
111
214
|
});
|
|
112
215
|
```
|
|
113
216
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
217
|
+
(Once the runtime's `waitUntil` is no longer a preview stub, `waitUntil(Sentry.flush(2000))` is the cleaner way to express this.)
|
|
218
|
+
|
|
219
|
+
With telemetry enabled the AI SDK records prompts and outputs by default — set `recordInputs: false` / `recordOutputs: false` on the same `experimental_telemetry` object if conversation content must not leave the application. Optionally, group multi-turn chats into a timeline (**Explore → Conversations**) and attribute them to users — set both once per request before the model call:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
Sentry.setConversationId(chatId);
|
|
223
|
+
Sentry.setUser({ id: userId });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Direct provider SDKs (`openai`, `@anthropic-ai/sdk`, `@langchain/*`, `@google/genai`) have equivalent Sentry auto-instrumentation, but it patches those modules at import time — which bundling defeats. The Vercel AI SDK path is bundle-safe (the `ai` package emits its own OTel spans), which is why it's the recommended route here. The same caveat applies to other module-patching instrumentation (`pg` spans, for example): if a specific library's spans are missing from a deployed function, bundling is the first thing to check.
|
|
117
227
|
|
|
118
228
|
### Verifying the wiring
|
|
119
229
|
|
|
120
|
-
-
|
|
121
|
-
-
|
|
230
|
+
- **Errors:** temporarily add a route that throws (`app.get("/debug-sentry", () => { throw new Error("sentry test"); })`), hit it, confirm the 500 surfaces as an issue in Sentry, then remove the route.
|
|
231
|
+
- **Logs:** trigger a recoverable failure (e.g. pass a bogus model name to the fallback path) and confirm the `Sentry.logger` records show up in **Explore → Logs**, linked to the same trace.
|
|
232
|
+
- **Traces:** hit a real route and confirm a trace appears (**Explore → Traces**) with the spans you expect — the request root, outbound fetches, and (for agents) `gen_ai.*` spans with token counts. Two things to know before declaring it broken:
|
|
233
|
+
- **Streamed span ingestion lags several minutes behind errors and logs.** An error visible in seconds does not mean its trace is lost — check again after a few minutes.
|
|
234
|
+
- If spans **never** appear while errors and logs flow, check the SDK version — `@sentry/node` <10.67.0 cannot register its tracer against the runtime's pre-created OpenTelemetry API global. Upgrade, or on an older SDK run `delete globalThis[Symbol.for("opentelemetry.js.api.1")]` before `Sentry.init`.
|
|
122
235
|
|
|
123
236
|
## Other Node integrations
|
|
124
237
|
|
|
125
|
-
The same pattern generalizes to any Node integration (
|
|
238
|
+
The same pattern generalizes to any Node integration (structured logging, analytics):
|
|
126
239
|
|
|
127
240
|
1. Initialize once at module scope in a dedicated init module, imported before your handler.
|
|
128
241
|
2. Gate it on an env var so local dev and unconfigured branches are a no-op.
|
|
129
242
|
3. Pass secrets via `--env KEY=VALUE` on deploy or the function's `env` in `neon.ts`.
|
|
130
243
|
|
|
131
|
-
Standard Node SDKs bundle through `neon deploy`'s esbuild without changes.
|
|
244
|
+
Standard Node SDKs bundle through `neon deploy`'s esbuild without changes — but module-patching auto-instrumentation does not, and anything that registers OpenTelemetry globals contends with the runtime's own registration (see the version note at the top).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Server-sent events (SSE) on Neon Functions
|
|
2
2
|
|
|
3
|
-
SSE is the one-way (server → client) streaming counterpart to WebSockets: the browser opens a long-lived `GET` with [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) and the server pushes text frames down it. On Neon Functions
|
|
3
|
+
SSE is the one-way (server → client) streaming counterpart to WebSockets: the browser opens a long-lived `GET` with [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) and the server pushes text frames down it. On Neon Functions an SSE endpoint is just a normal `fetch` handler that returns a `Response` whose body is a `ReadableStream` with `Content-Type: text/event-stream` — there is no library to install and nothing to upgrade. The runtime holds the response open as long as bytes keep flowing (15-minute heartbeat, see [Timeouts](../SKILL.md#timeouts-and-runtime-limits)).
|
|
4
4
|
|
|
5
5
|
Reach for SSE over WebSockets when you only need server → client updates (live counters, notifications, progress, token streams) — it's simpler to run (plain HTTP, no `upgrade`), and `EventSource` **reconnects on its own**, so there's no client backoff to write.
|
|
6
6
|
|
|
@@ -64,7 +64,10 @@ app.get("/events", (c) => {
|
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
return new Response(stream, {
|
|
67
|
-
headers: {
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "text/event-stream",
|
|
69
|
+
"Cache-Control": "no-cache, no-transform",
|
|
70
|
+
},
|
|
68
71
|
});
|
|
69
72
|
});
|
|
70
73
|
|
|
@@ -76,16 +79,25 @@ export default app;
|
|
|
76
79
|
The fan-out rule is identical to WebSockets ([Keeping clients in sync across isolates](../SKILL.md#keeping-clients-in-sync-across-isolates-do-not-skip-this)): each isolate keeps its **own** set of open streams, so broadcasting in-process only reaches the clients on that isolate. Hold a `Set` of stream controllers and pick a strategy there — **poll Postgres** by default (keeps Scale to Zero), or `LISTEN`/`NOTIFY` (shown below) for lowest latency on always-on compute. Keep the source-of-truth state in Postgres — module state doesn't survive eviction.
|
|
77
80
|
|
|
78
81
|
```typescript
|
|
82
|
+
import { attachDatabasePool } from "@neon/functions";
|
|
79
83
|
import { Pool, Client } from "pg";
|
|
80
84
|
|
|
81
85
|
const encoder = new TextEncoder();
|
|
82
86
|
const clients = new Set<ReadableStreamDefaultController<Uint8Array>>();
|
|
83
87
|
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
|
|
88
|
+
attachDatabasePool(pool);
|
|
84
89
|
const CHANNEL = "events";
|
|
85
90
|
|
|
86
91
|
// One dedicated DIRECT connection per isolate to receive events (LISTEN needs a
|
|
87
92
|
// real session — use DATABASE_URL_UNPOOLED, not the pooled URL).
|
|
88
|
-
|
|
93
|
+
// 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.
|
|
95
|
+
const listener = new Client({
|
|
96
|
+
connectionString: process.env.DATABASE_URL_UNPOOLED,
|
|
97
|
+
});
|
|
98
|
+
listener.on("error", (err) => {
|
|
99
|
+
console.error(err);
|
|
100
|
+
});
|
|
89
101
|
listener.connect().then(() => listener.query(`LISTEN ${CHANNEL}`));
|
|
90
102
|
listener.on("notification", (msg) => {
|
|
91
103
|
if (!msg.payload) return;
|
|
@@ -101,7 +113,10 @@ listener.on("notification", (msg) => {
|
|
|
101
113
|
|
|
102
114
|
// Anywhere you mutate state, NOTIFY so every isolate pushes to its own streams.
|
|
103
115
|
function publish(payload: unknown) {
|
|
104
|
-
return pool.query("SELECT pg_notify($1, $2)", [
|
|
116
|
+
return pool.query("SELECT pg_notify($1, $2)", [
|
|
117
|
+
CHANNEL,
|
|
118
|
+
JSON.stringify(payload),
|
|
119
|
+
]);
|
|
105
120
|
}
|
|
106
121
|
```
|
|
107
122
|
|
|
@@ -125,8 +140,10 @@ Send `data:` with no `event:` field to deliver the default `message` event, whic
|
|
|
125
140
|
|
|
126
141
|
```typescript
|
|
127
142
|
const source = new EventSource(`${FUNCTION_URL}/events`); // GET only
|
|
128
|
-
source.onmessage = (e) => console.log("update", e.data);
|
|
129
|
-
source.onerror = () => {
|
|
143
|
+
source.onmessage = (e) => console.log("update", e.data);
|
|
144
|
+
source.onerror = () => {
|
|
145
|
+
/* EventSource auto-reconnects; nothing to do */
|
|
146
|
+
};
|
|
130
147
|
// source.close() to stop.
|
|
131
148
|
```
|
|
132
149
|
|
|
@@ -5,17 +5,31 @@ description: >-
|
|
|
5
5
|
and the database stay in sync across every branch. Use when a user wants
|
|
6
6
|
object storage, a bucket, blob/file storage, or somewhere to put uploads,
|
|
7
7
|
images, documents, avatars, or user-generated files for their app or agent —
|
|
8
|
-
especially when they already use (or are setting up)
|
|
8
|
+
especially when they already use (or are setting up) Lakebase Postgres and don't
|
|
9
9
|
want to add a separate storage provider like AWS S3, Cloudflare R2, or
|
|
10
10
|
Supabase Storage. Triggers include "object storage", "bucket", "blob
|
|
11
11
|
storage", "file storage", "store uploads/images/files", "S3-compatible
|
|
12
|
-
storage", "presigned URL", "where do I put files", "
|
|
13
|
-
"Neon Storage", and "storage that
|
|
12
|
+
storage", "presigned URL", "where do I put files", "storage logs",
|
|
13
|
+
"bucket logs", "Neon Object Storage", "Neon Storage", and "storage that
|
|
14
|
+
branches with my database".
|
|
15
|
+
metadata:
|
|
16
|
+
parent: neon
|
|
17
|
+
source: https://github.com/neondatabase/agent-skills/tree/main/skills/neon-object-storage
|
|
14
18
|
---
|
|
15
19
|
|
|
20
|
+
**FIRST**: Use the parent `neon` skill for a Neon overview, getting started with Neon, Neon development best practices, and more.
|
|
21
|
+
|
|
22
|
+
If the `neon` skill is not installed, fetch it from https://neon.com/docs/ai/skills/neon/SKILL.md or install it with:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx skills add neondatabase/agent-skills --skill neon
|
|
26
|
+
```
|
|
27
|
+
|
|
16
28
|
# Neon Object Storage
|
|
17
29
|
|
|
18
|
-
This is a
|
|
30
|
+
This is a public beta feature and only available in `us-east-2`.
|
|
31
|
+
|
|
32
|
+
Neon Object Storage is S3-compatible object storage that branches with your projects: every branch gets its own isolated storage state, so files and database rows stay in sync across dev, preview, staging, and production.
|
|
19
33
|
|
|
20
34
|
Use this skill to help the user store and serve files that branch alongside their database. Deliver a working bucket and upload/download flow, a branch-aware S3 client wired to the injected env vars, or a precise answer from the official Neon docs.
|
|
21
35
|
|
|
@@ -23,7 +37,7 @@ Use this skill to help the user store and serve files that branch alongside thei
|
|
|
23
37
|
|
|
24
38
|
Reach for Neon Object Storage when the user needs to store files (images, uploads, generated assets, documents, backups) and any of the following are true:
|
|
25
39
|
|
|
26
|
-
- **They already use
|
|
40
|
+
- **They already use Lakebase Postgres and don't want a second provider.** One backend, one bill, one CLI, one set of branches — instead of standing up and wiring a separate AWS S3 / R2 / Supabase Storage account. The same Neon credential that backs the database backs storage.
|
|
27
41
|
- **Files must stay in sync with the database across environments.** Storage branches _together with_ your Postgres data. Fork a branch and the child instantly inherits the parent's buckets and objects at that point in time — copy-on-write, so no data is duplicated. This is what makes agent, dev, preview, and test environments seamless: a preview branch gets a consistent snapshot of _both_ the rows and the files they reference, and writes on the child never touch the parent.
|
|
28
42
|
- **They want safe, throwaway environments.** Upload, overwrite, and delete files in a preview/CI branch without any risk to production data, then drop the branch.
|
|
29
43
|
- **They want standard S3 tooling.** It's built on S3 semantics and speaks the S3 API, so the AWS SDKs, `boto3`, the AWS CLI, and presigned URLs all work — reliable and familiar, with no proprietary client.
|
|
@@ -37,6 +51,10 @@ If the user has no Neon project, isn't on Postgres, and just needs a standalone
|
|
|
37
51
|
- **Two access modes** — `private` buckets require a credential for every operation; `public_read` buckets allow anonymous reads with authenticated writes.
|
|
38
52
|
- **One credential system** — The same Neon credential system used by Functions and the AI Gateway.
|
|
39
53
|
|
|
54
|
+
## Availability
|
|
55
|
+
|
|
56
|
+
Check this precondition before setting anything up: Neon Object Storage is a public beta feature available only on new projects in the `us-east-2` region. Confirm the user's Neon project is a new project in `us-east-2` before proceeding; it can't be enabled on existing projects.
|
|
57
|
+
|
|
40
58
|
## Setup
|
|
41
59
|
|
|
42
60
|
Object storage is part of the `neon.ts` infrastructure-as-code config (see the `neon` skill for the branch-first workflow, `link`/`checkout`, and `neon.ts` basics). Declare buckets under `preview.buckets`, keyed by bucket name:
|
|
@@ -73,9 +91,7 @@ neon config apply # create the declared buckets (neon deploy is an alias)
|
|
|
73
91
|
|
|
74
92
|
Buckets are **branch-scoped**: when a `neon.ts` is present, `neon checkout` applies the policy as it _creates_ a branch, so a fresh preview/CI branch comes up with its buckets already provisioned (and copy-on-write objects inherited from the parent). Checking out an _existing_ branch doesn't reconcile it — run `neon deploy` to apply changes. Provisioning (`config apply` / `deploy`), `link`, and `checkout` also pull the branch's S3 credentials into your local `.env.local`, so the same `env pull` step shown below happens for you on those commands.
|
|
75
93
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
## Environment variables
|
|
94
|
+
## Environment Variables
|
|
79
95
|
|
|
80
96
|
When `preview.buckets` is declared, Neon injects **AWS-standard** S3 env vars so the AWS SDKs work from the environment with zero extra config. Inside a deployed Neon Function these are injected automatically; locally, pull them onto disk (or inject them at runtime) via the CLI:
|
|
81
97
|
|
|
@@ -94,7 +110,9 @@ neon-env run -- <your dev command>
|
|
|
94
110
|
|
|
95
111
|
Because the names are AWS-standard, the AWS SDK picks up the credentials, endpoint, and region from the environment automatically. Credentials are branch-scoped and valid for that branch and all its descendants.
|
|
96
112
|
|
|
97
|
-
|
|
113
|
+
For typed, validated access to these credentials instead of reading `process.env` directly, pass the same `neon.ts` config object to `parseEnv` from `@neon/env` — it returns an `env.storage` namespace (`accessKeyId`, `secretAccessKey`, `endpoint`, `region`) derived from your config. See the `neon` skill.
|
|
114
|
+
|
|
115
|
+
## Working with Objects: the Files SDK (Recommended)
|
|
98
116
|
|
|
99
117
|
The simplest, most portable way to read and write objects is the [Files SDK](https://files-sdk.dev) with its `neon` adapter — a small, unified storage API (`upload`, `download`, `url`, `list`, `exists`, `copy`, `delete`, `signedUploadUrl`) over web-standard I/O. It uses the AWS S3 client under the hood, configured appropriately for Neon, and relabels errors as `Neon error` — so there's nothing to misconfigure. Reach for this first.
|
|
100
118
|
|
|
@@ -127,7 +145,7 @@ const url = await files.url("generated/cat.jpg", { expiresIn: 3600 });
|
|
|
127
145
|
|
|
128
146
|
Swap the adapter import (`files-sdk/s3`, `files-sdk/r2`, `files-sdk/gcs`, …) and the rest of your code is unchanged.
|
|
129
147
|
|
|
130
|
-
## Working with
|
|
148
|
+
## Working with Objects: the AWS S3 Client (Alternative)
|
|
131
149
|
|
|
132
150
|
Neon speaks the S3 API directly, so you can drop down to the AWS SDK whenever you prefer the native client or already depend on it. The credentials, endpoint, and region are read from the standard AWS env chain, so the only setting you pass is `forcePathStyle: true` — Neon requires path-style addressing, so the S3 client **must** set it:
|
|
133
151
|
|
|
@@ -139,8 +157,6 @@ const s3 = new S3Client({
|
|
|
139
157
|
});
|
|
140
158
|
```
|
|
141
159
|
|
|
142
|
-
If you prefer typed access instead of reading `process.env` directly, `parseEnv` (from `@neon/env`) returns a validated `env.storage` namespace (`accessKeyId`, `secretAccessKey`, `endpoint`, `region`) derived from your `neon.ts` — see the `neon` skill.
|
|
143
|
-
|
|
144
160
|
Then upload, download, and presign with the raw command objects:
|
|
145
161
|
|
|
146
162
|
```typescript
|
|
@@ -173,19 +189,27 @@ const url = await getSignedUrl(
|
|
|
173
189
|
);
|
|
174
190
|
```
|
|
175
191
|
|
|
176
|
-
|
|
192
|
+
## Pairing Storage with the Database on a Branch
|
|
193
|
+
|
|
194
|
+
The canonical pattern: an agent generates an image → `PutObject` into the `images` bucket → a row is inserted in Postgres → a presigned URL is returned on read. Store the bucket **key** (not the bytes) in a Postgres column, and presign on read. Because both the row and the object live on the same branch, they branch together and never drift.
|
|
195
|
+
|
|
196
|
+
## CLI Bucket and Object Commands
|
|
177
197
|
|
|
178
198
|
`neon` also has first-class bucket/object commands (`neon bucket create|list|delete`, `neon bucket object put|get|list|delete`) for scripting and one-off operations.
|
|
179
199
|
|
|
180
|
-
##
|
|
200
|
+
## Built-in Branch Logs
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
neon logs query --branch production --source storage --since 1h
|
|
204
|
+
```
|
|
181
205
|
|
|
182
|
-
|
|
206
|
+
Storage is one of the two sources branch logs cover today, alongside Neon Functions. Logs are scoped to a single branch, so pass `--branch` when the bucket you're debugging isn't on the branch you're checked out on. Everything else about logs — the required CLI version, filters, the SDK, and the Loki-compatible read API — is in the parent `neon` skill's **Observability** section.
|
|
183
207
|
|
|
184
208
|
## Neon Documentation
|
|
185
209
|
|
|
186
210
|
The Neon documentation is the source of truth and Object Storage is evolving rapidly, so always verify against the official docs. Any doc page can be fetched as markdown by appending `.md` to the URL or by requesting `Accept: text/markdown`. Find the right page from the docs index (https://neon.com/docs/llms.txt) and the changelog announcements.
|
|
187
211
|
|
|
188
|
-
## Further
|
|
212
|
+
## Further Reading
|
|
189
213
|
|
|
190
214
|
- https://neon.com/docs/storage/overview.md
|
|
191
215
|
- https://neon.com/docs/storage/get-started.md
|