@agent-native/core 0.176.4-nightly-20260902015502 → 0.176.4-nightly-20260902024948

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/corpus/templates/forms/actions/db-connect.ts +6 -3
  2. package/corpus/templates/forms/actions/db-status.ts +8 -8
  3. package/corpus/templates/forms/app/hooks/use-db-status.ts +6 -2
  4. package/corpus/templates/forms/server/plugins/core-routes.ts +35 -0
  5. package/corpus/templates/forms/server/routes/api/db-health.get.ts +2 -1
  6. package/dist/client/EnvironmentBadge.d.ts +1 -1
  7. package/dist/client/EnvironmentBadge.js +14 -2
  8. package/dist/client/analytics.d.ts +1 -0
  9. package/dist/client/app-providers.js +10 -1
  10. package/dist/client/sign-out.js +34 -0
  11. package/dist/collab/awareness.d.ts +2 -2
  12. package/dist/db/client.d.ts +1 -0
  13. package/dist/db/client.js +63 -11
  14. package/dist/db/index.d.ts +1 -1
  15. package/dist/db/index.js +1 -1
  16. package/dist/db/runtime-diagnostics.d.ts +5 -0
  17. package/dist/db/runtime-diagnostics.js +16 -14
  18. package/dist/deploy/build.js +38 -0
  19. package/dist/notifications/routes.d.ts +3 -3
  20. package/dist/observability/routes.d.ts +3 -3
  21. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  22. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  23. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  24. package/dist/secrets/routes.d.ts +6 -6
  25. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  26. package/dist/server/app-origin-config.d.ts +1 -0
  27. package/dist/server/app-origin-config.js +36 -0
  28. package/dist/server/auth.js +3 -5
  29. package/dist/server/beta-opt-out-html.d.ts +1 -1
  30. package/dist/server/beta-opt-out-html.js +10 -2
  31. package/dist/server/core-routes-plugin.js +4 -2
  32. package/dist/server/create-server.js +13 -7
  33. package/dist/server/onboarding-html.d.ts +1 -0
  34. package/dist/server/onboarding-html.js +1 -1
  35. package/dist/server/transcribe-voice.d.ts +1 -1
  36. package/dist/shared/environment-lanes.d.ts +3 -0
  37. package/dist/shared/environment-lanes.js +3 -0
  38. package/dist/shared/index.d.ts +1 -1
  39. package/dist/shared/index.js +1 -1
  40. package/dist/shared/ssr-beta-redirect.d.ts +8 -0
  41. package/dist/shared/ssr-beta-redirect.js +215 -0
  42. package/package.json +1 -1
@@ -5,15 +5,18 @@ export default defineAction({
5
5
  description:
6
6
  "Explain how to configure deployment-level database connection settings.",
7
7
  schema: z.object({
8
- url: z.string().describe("DATABASE_URL value (required)"),
9
- token: z.string().optional().describe("DATABASE_AUTH_TOKEN value"),
8
+ url: z.string().describe("Forms database URL value (required)"),
9
+ token: z
10
+ .string()
11
+ .optional()
12
+ .describe("Forms database auth token value (optional)"),
10
13
  }),
11
14
  http: false,
12
15
  run: async (args) => {
13
16
  const maskedUrl = args.url.replace(/\/\/.*@/, "//***@");
14
17
  return [
15
18
  `Database connection not written: ${maskedUrl}.`,
16
- "DATABASE_URL and DATABASE_AUTH_TOKEN are deployment-level database settings.",
19
+ "For Forms, configure FORMS_DATABASE_URL_UNPOOLED when available, otherwise FORMS_DATABASE_URL. DATABASE_URL_UNPOOLED and DATABASE_URL are supported fallbacks. Use the matching *_DATABASE_AUTH_TOKEN for libSQL.",
17
20
  "Configure them with your hosting provider and redeploy the app.",
18
21
  ].join(" ");
19
22
  },
@@ -1,5 +1,9 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
- import { createClient } from "@libsql/client";
2
+ import {
3
+ getDbExec,
4
+ getRuntimeDatabaseUrl,
5
+ isLocalDatabase,
6
+ } from "@agent-native/core/db";
3
7
  import { z } from "zod";
4
8
 
5
9
  export default defineAction({
@@ -7,15 +11,11 @@ export default defineAction({
7
11
  schema: z.object({}),
8
12
  http: false,
9
13
  run: async () => {
10
- const url = process.env.DATABASE_URL || "file:./data/app.db";
11
- const isLocal = url.startsWith("file:");
14
+ const url = getRuntimeDatabaseUrl("file:./data/app.db");
15
+ const isLocal = isLocalDatabase();
12
16
 
13
17
  try {
14
- const client = createClient({
15
- url,
16
- authToken: process.env.DATABASE_AUTH_TOKEN,
17
- });
18
- const result = await client.execute("SELECT 1 as ok");
18
+ const result = await getDbExec().execute("SELECT 1 as ok");
19
19
  return {
20
20
  url: isLocal ? url : url.replace(/\/\/.*@/, "//***@"),
21
21
  mode: isLocal ? "local (SQLite file)" : "remote (cloud)",
@@ -19,8 +19,12 @@ export function useDbStatus() {
19
19
  staleTime: 30_000,
20
20
  });
21
21
 
22
- const dbUrlEntry = data?.find((e) => e.key === "DATABASE_URL");
23
- const configured = dbUrlEntry?.configured ?? false;
22
+ const configured =
23
+ data?.some(
24
+ (entry) =>
25
+ /(?:^|_)DATABASE_URL(?:_UNPOOLED)?$/.test(entry.key) &&
26
+ entry.configured,
27
+ ) ?? false;
24
28
 
25
29
  return {
26
30
  configured,
@@ -3,12 +3,47 @@ import { createCoreRoutesPlugin } from "@agent-native/core/server";
3
3
  export default createCoreRoutesPlugin({
4
4
  googleOAuthManagedConnection: "not_applicable",
5
5
  envKeys: [
6
+ {
7
+ key: "FORMS_DATABASE_URL",
8
+ label: "Forms Database URL",
9
+ required: false,
10
+ },
11
+ {
12
+ key: "FORMS_DATABASE_URL_UNPOOLED",
13
+ label: "Forms Unpooled Database URL",
14
+ required: false,
15
+ },
16
+ {
17
+ key: "FORMS_DATABASE_AUTH_TOKEN",
18
+ label: "Forms Database Auth Token",
19
+ required: false,
20
+ },
6
21
  { key: "DATABASE_URL", label: "Database URL", required: false },
22
+ {
23
+ key: "DATABASE_URL_UNPOOLED",
24
+ label: "Unpooled Database URL",
25
+ required: false,
26
+ },
27
+ {
28
+ key: "NETLIFY_DATABASE_URL",
29
+ label: "Netlify Database URL",
30
+ required: false,
31
+ },
32
+ {
33
+ key: "NETLIFY_DATABASE_URL_UNPOOLED",
34
+ label: "Netlify Unpooled Database URL",
35
+ required: false,
36
+ },
7
37
  {
8
38
  key: "DATABASE_AUTH_TOKEN",
9
39
  label: "Database Auth Token",
10
40
  required: false,
11
41
  },
42
+ {
43
+ key: "NETLIFY_DATABASE_AUTH_TOKEN",
44
+ label: "Netlify Database Auth Token",
45
+ required: false,
46
+ },
12
47
  {
13
48
  key: "TURNSTILE_SECRET_KEY",
14
49
  label: "Turnstile Secret Key",
@@ -1,3 +1,4 @@
1
+ import { getRuntimeDatabaseUrl } from "@agent-native/core/db";
1
2
  import { sql } from "drizzle-orm";
2
3
  import { defineEventHandler } from "h3";
3
4
 
@@ -7,7 +8,7 @@ export default defineEventHandler(async () => {
7
8
  try {
8
9
  const db = getDb();
9
10
  await db.run(sql`SELECT 1`);
10
- const url = process.env.DATABASE_URL || "file:./data/app.db";
11
+ const url = getRuntimeDatabaseUrl("file:./data/app.db");
11
12
  return { ok: true, local: url.startsWith("file:") };
12
13
  } catch (e) {
13
14
  return { ok: false, error: e instanceof Error ? e.message : "Unknown" };
@@ -1,5 +1,5 @@
1
1
  import type { AgentNativeDeploymentEnvironment, AgentNativeConfig } from "../config.js";
2
- export { BETA_FORCE_QUERY_PARAM, BETA_FORCE_SESSION_STORAGE_KEY, BETA_OPT_OUT_DURATION_MS, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, type EnvironmentBadgeTargets, } from "../shared/environment-lanes.js";
2
+ export { BETA_FORCE_QUERY_PARAM, BETA_FORCE_SESSION_STORAGE_KEY, BETA_OPT_OUT_DURATION_MS, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, BETA_REDIRECT_DURATION_MS, BETA_REDIRECT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, type EnvironmentBadgeTargets, } from "../shared/environment-lanes.js";
3
3
  export declare function isBuilderIoEmployee(email: string | null | undefined): boolean;
4
4
  export declare function isAgentNativeDesktopUserAgent(userAgent: string | undefined): boolean;
5
5
  export declare function resolveEnvironmentChannel(config: AgentNativeConfig, hostname: string | undefined): Extract<AgentNativeDeploymentEnvironment, "local" | "beta" | "production"> | null;
@@ -2,12 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button } from "@agent-native/toolkit/ui/button";
3
3
  import { Popover, PopoverContent, PopoverTrigger, } from "@agent-native/toolkit/ui/popover";
4
4
  import { useEffect, useMemo, useRef, useState } from "react";
5
- import { BETA_FORCE_QUERY_PARAM, BETA_FORCE_SESSION_STORAGE_KEY, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, } from "../shared/environment-lanes.js";
5
+ import { BETA_FORCE_QUERY_PARAM, BETA_FORCE_SESSION_STORAGE_KEY, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, BETA_REDIRECT_DURATION_MS, BETA_REDIRECT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, } from "../shared/environment-lanes.js";
6
6
  import { trackEvent } from "./analytics.js";
7
7
  import { injectedAgentNativeConfig } from "./app-config.js";
8
8
  import { useSession } from "./use-session.js";
9
9
  import { cn } from "./utils.js";
10
- export { BETA_FORCE_QUERY_PARAM, BETA_FORCE_SESSION_STORAGE_KEY, BETA_OPT_OUT_DURATION_MS, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, } from "../shared/environment-lanes.js";
10
+ export { BETA_FORCE_QUERY_PARAM, BETA_FORCE_SESSION_STORAGE_KEY, BETA_OPT_OUT_DURATION_MS, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, BETA_REDIRECT_DURATION_MS, BETA_REDIRECT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, } from "../shared/environment-lanes.js";
11
11
  export function isBuilderIoEmployee(email) {
12
12
  return email?.trim().toLowerCase().endsWith("@builder.io") ?? false;
13
13
  }
@@ -50,6 +50,16 @@ function readBetaOptOutUntil(now = Date.now()) {
50
50
  }
51
51
  return null;
52
52
  }
53
+ function rememberBetaRedirectPreference() {
54
+ if (typeof window === "undefined")
55
+ return;
56
+ try {
57
+ window.localStorage.setItem(BETA_REDIRECT_STORAGE_KEY, String(Date.now() + BETA_REDIRECT_DURATION_MS));
58
+ }
59
+ catch {
60
+ // coercion-ok: the marker is only a performance hint; session auth remains authoritative.
61
+ }
62
+ }
53
63
  function rememberForcedProductionSession(sourceHref) {
54
64
  let forcedByQuery = false;
55
65
  try {
@@ -90,6 +100,7 @@ function consumeBetaOptOutQueryParam(sourceHref, now = Date.now()) {
90
100
  try {
91
101
  if (active) {
92
102
  window.localStorage.setItem(BETA_OPT_OUT_STORAGE_KEY, String(Number(rawExpiry)));
103
+ window.localStorage.removeItem(BETA_REDIRECT_STORAGE_KEY);
93
104
  }
94
105
  window.history.replaceState(null, "", target.toString());
95
106
  }
@@ -151,6 +162,7 @@ function ProductionEnvironmentBadge({ targets, }) {
151
162
  const betaHref = buildEnvironmentUrl(window.location.href, targets.betaHost);
152
163
  if (!betaHref || typeof window.location.replace !== "function")
153
164
  return;
165
+ rememberBetaRedirectPreference();
154
166
  didAutoRedirect.current = true;
155
167
  trackEvent("environment switched", {
156
168
  from_environment: "production",
@@ -23,6 +23,7 @@ declare global {
23
23
  workspaceGatewayUrl?: string;
24
24
  workspaceOAuthOrigin?: string;
25
25
  workspaceRuntime?: boolean;
26
+ workspaceAppMountPaths?: string[];
26
27
  sentryDsn?: string;
27
28
  sentryEnvironment?: string;
28
29
  deploymentEnvironment?: string;
@@ -56,6 +56,8 @@ import { ThemeProvider, useTheme } from "next-themes";
56
56
  import { useEffect, useRef } from "react";
57
57
  import { useInRouterContext } from "react-router";
58
58
  import { isHumanReadableDocumentTitle, normalizeDocumentTitle, } from "../shared/document-title.js";
59
+ import { getSsrBetaRedirectScriptBody } from "../shared/ssr-beta-redirect.js";
60
+ import { agentNativePath } from "./api-path.js";
59
61
  import { ClientOnly } from "./ClientOnly.js";
60
62
  import { DefaultSpinner } from "./DefaultSpinner.js";
61
63
  import { EnvironmentBadge } from "./EnvironmentBadge.js";
@@ -68,6 +70,11 @@ import { RuntimeConfigNotice } from "./RuntimeConfigNotice.js";
68
70
  import { EMBEDDED_THEME_CHANGE_EVENT, applyEmbeddedThemeUpdate, parseEmbeddedThemeUpdate, } from "./theme.js";
69
71
  import { createAgentNativeServerActionWebMcpRegistration } from "./webmcp.js";
70
72
  const DEFAULT_TOASTER = (_jsx(Toaster, { richColors: true, position: "bottom-left", offset: { bottom: 44, left: 32 }, mobileOffset: { bottom: 44, left: 16 } }));
73
+ function EarlyBetaRedirectScript() {
74
+ return (_jsx("script", { "data-agent-native-beta-redirect": "1", dangerouslySetInnerHTML: {
75
+ __html: getSsrBetaRedirectScriptBody(agentNativePath("/_agent-native/auth/session")),
76
+ } }));
77
+ }
71
78
  function RoutedAppEnhancements() {
72
79
  const isInRouter = useInRouterContext();
73
80
  if (!isInRouter)
@@ -165,5 +172,7 @@ export function AppProviders({ queryClient, isPublicPath = false, clientOnlyFall
165
172
  if (isPublicPath) {
166
173
  return (_jsx(ProvidersInner, { queryClient: queryClient, defaultTheme: defaultTheme, themeAttribute: themeAttribute, tooltipDelayDuration: tooltipDelayDuration, toaster: toaster, disableThemeTransitions: disableThemeTransitions, i18n: i18n, documentTitleFallback: documentTitleFallback, showProductionEnvironmentBadge: false, children: children }));
167
174
  }
168
- return (_jsx(ClientOnly, { fallback: fallback, children: _jsx(ProvidersInner, { queryClient: queryClient, defaultTheme: defaultTheme, themeAttribute: themeAttribute, tooltipDelayDuration: tooltipDelayDuration, toaster: toaster, disableThemeTransitions: disableThemeTransitions, i18n: i18n, documentTitleFallback: documentTitleFallback, showProductionEnvironmentBadge: !sessionBypass, children: _jsx(RequireSession, { bypass: sessionBypass, fallback: fallback, children: sessionBypass ? (children) : (_jsxs(FirstRunOnboardingStartupGate, { children: [_jsx(AutomaticWebMcpActionRegistration, {}), children] })) }) }) }));
175
+ // Keep the bootstrap outside ClientOnly so the HTML parser can run it before
176
+ // the authenticated client bundle starts.
177
+ return (_jsxs(_Fragment, { children: [!sessionBypass && _jsx(EarlyBetaRedirectScript, {}), _jsx(ClientOnly, { fallback: fallback, children: _jsx(ProvidersInner, { queryClient: queryClient, defaultTheme: defaultTheme, themeAttribute: themeAttribute, tooltipDelayDuration: tooltipDelayDuration, toaster: toaster, disableThemeTransitions: disableThemeTransitions, i18n: i18n, documentTitleFallback: documentTitleFallback, showProductionEnvironmentBadge: !sessionBypass, children: _jsx(RequireSession, { bypass: sessionBypass, fallback: fallback, children: sessionBypass ? (children) : (_jsxs(FirstRunOnboardingStartupGate, { children: [_jsx(AutomaticWebMcpActionRegistration, {}), children] })) }) }) })] }));
169
178
  }
@@ -1,3 +1,4 @@
1
+ import { BETA_REDIRECT_SIGN_OUT_STORAGE_KEY, BETA_REDIRECT_STORAGE_KEY, } from "../shared/environment-lanes.js";
1
2
  /**
2
3
  * The one client-side sign-out.
3
4
  *
@@ -27,6 +28,34 @@ import { beginSignOut, completeSignOut } from "./use-session.js";
27
28
  const LOGOUT_PATH = "/_agent-native/auth/logout";
28
29
  const SIGN_OUT_REQUEST_TIMEOUT_MS = 15_000;
29
30
  let signOutOperation = null;
31
+ function setBetaRedirectSignOutSignal() {
32
+ const signOutWindow = window;
33
+ signOutWindow.__agentNativeBetaRedirectSignOutStarted = true;
34
+ try {
35
+ window.sessionStorage.setItem(BETA_REDIRECT_SIGN_OUT_STORAGE_KEY, "1");
36
+ }
37
+ catch {
38
+ // coercion-ok: the in-memory signal still protects this document when storage is unavailable.
39
+ }
40
+ }
41
+ function clearBetaRedirectSignOutSignal() {
42
+ const signOutWindow = window;
43
+ signOutWindow.__agentNativeBetaRedirectSignOutStarted = false;
44
+ try {
45
+ window.sessionStorage.removeItem(BETA_REDIRECT_SIGN_OUT_STORAGE_KEY);
46
+ }
47
+ catch {
48
+ // coercion-ok: sign-out navigation must not depend on optional storage.
49
+ }
50
+ }
51
+ function clearBetaRedirectMarker() {
52
+ try {
53
+ window.localStorage.removeItem(BETA_REDIRECT_STORAGE_KEY);
54
+ }
55
+ catch {
56
+ // coercion-ok: local storage is optional; sign-out must still complete.
57
+ }
58
+ }
30
59
  /**
31
60
  * Sign the current user out and leave for the sign-in page when revocation
32
61
  * succeeds. A failed revoke reloads the current document instead.
@@ -41,6 +70,7 @@ export function signOut(options = {}) {
41
70
  return signOutOperation;
42
71
  }
43
72
  async function signOutFlow(options) {
73
+ setBetaRedirectSignOutSignal();
44
74
  beginSignOut();
45
75
  const controller = new AbortController();
46
76
  const timeout = setTimeout(() => controller.abort(), SIGN_OUT_REQUEST_TIMEOUT_MS);
@@ -69,6 +99,8 @@ async function signOutFlow(options) {
69
99
  if (!revoked) {
70
100
  // Do not send an unrevoked session through sign-in's continuation, which
71
101
  // can immediately authenticate it again.
102
+ clearBetaRedirectSignOutSignal();
103
+ clearBetaRedirectMarker();
72
104
  window.location.reload();
73
105
  return;
74
106
  }
@@ -78,5 +110,7 @@ async function signOutFlow(options) {
78
110
  completeSignOut();
79
111
  // `replace`, not `assign`: the dead authenticated URL must not stay in
80
112
  // history, or Back lands on a shell with no session.
113
+ clearBetaRedirectSignOutSignal();
114
+ clearBetaRedirectMarker();
81
115
  window.location.replace(options.redirectTo ?? buildSignInReturnHref());
82
116
  }
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -60,6 +60,7 @@ export declare function getDatabaseUrl(fallback?: string): string;
60
60
  * checks that intentionally inspect the configured deployment value.
61
61
  */
62
62
  export declare function getRuntimeDatabaseUrl(fallback?: string): string;
63
+ export declare function getRuntimeDatabaseSource(fallback?: string): string;
63
64
  /** Same per-app resolution for DATABASE_AUTH_TOKEN (used by Turso/libsql). */
64
65
  export declare function getDatabaseAuthToken(): string | undefined;
65
66
  /**
package/dist/db/client.js CHANGED
@@ -59,6 +59,65 @@ function getConfiguredAppDatabaseUrl(suffix) {
59
59
  function stripNeonPooler(url) {
60
60
  return url.replace(/-pooler(\.[a-z0-9.-]+\.neon\.tech)/, "$1");
61
61
  }
62
+ function envDatabaseValue(key) {
63
+ const value = process.env[key]?.trim();
64
+ return value || undefined;
65
+ }
66
+ function resolveRuntimeDatabase(fallback = "") {
67
+ const appName = getAppEnvPrefix();
68
+ if (appName) {
69
+ const appUnpooled = envDatabaseValue(`${appName}_DATABASE_URL_UNPOOLED`);
70
+ if (appUnpooled) {
71
+ return {
72
+ url: stripNeonPooler(appUnpooled),
73
+ source: `${appName}_DATABASE_URL_UNPOOLED`,
74
+ };
75
+ }
76
+ const appUrl = envDatabaseValue(`${appName}_DATABASE_URL`);
77
+ if (appUrl) {
78
+ return {
79
+ url: isServerlessRuntime() ? stripNeonPooler(appUrl) : appUrl,
80
+ source: `${appName}_DATABASE_URL`,
81
+ };
82
+ }
83
+ }
84
+ const configuredUnpooled = getAppConfig().runtime.databaseUrlUnpooled;
85
+ if (configuredUnpooled) {
86
+ const netlifyUnpooled = envDatabaseValue("NETLIFY_DATABASE_URL_UNPOOLED");
87
+ const databaseUnpooled = envDatabaseValue("DATABASE_URL_UNPOOLED");
88
+ return {
89
+ url: stripNeonPooler(configuredUnpooled),
90
+ source: netlifyUnpooled === configuredUnpooled
91
+ ? "NETLIFY_DATABASE_URL_UNPOOLED"
92
+ : databaseUnpooled === configuredUnpooled
93
+ ? "DATABASE_URL_UNPOOLED"
94
+ : "DATABASE_URL_UNPOOLED",
95
+ };
96
+ }
97
+ const netlifyUnpooled = envDatabaseValue("NETLIFY_DATABASE_URL_UNPOOLED");
98
+ if (netlifyUnpooled) {
99
+ return {
100
+ url: stripNeonPooler(netlifyUnpooled),
101
+ source: "NETLIFY_DATABASE_URL_UNPOOLED",
102
+ };
103
+ }
104
+ const databaseUnpooled = envDatabaseValue("DATABASE_URL_UNPOOLED");
105
+ if (databaseUnpooled) {
106
+ return {
107
+ url: stripNeonPooler(databaseUnpooled),
108
+ source: "DATABASE_URL_UNPOOLED",
109
+ };
110
+ }
111
+ const url = getDatabaseUrl(fallback);
112
+ return {
113
+ url: isServerlessRuntime() ? stripNeonPooler(url) : url,
114
+ source: envDatabaseValue("DATABASE_URL")
115
+ ? "DATABASE_URL"
116
+ : envDatabaseValue("NETLIFY_DATABASE_URL")
117
+ ? "NETLIFY_DATABASE_URL"
118
+ : "default",
119
+ };
120
+ }
62
121
  /**
63
122
  * Resolve the URL used by request-time database clients.
64
123
  *
@@ -69,17 +128,10 @@ function stripNeonPooler(url) {
69
128
  * checks that intentionally inspect the configured deployment value.
70
129
  */
71
130
  export function getRuntimeDatabaseUrl(fallback = "") {
72
- const appUnpooled = getConfiguredAppDatabaseUrl("DATABASE_URL_UNPOOLED");
73
- if (appUnpooled)
74
- return stripNeonPooler(appUnpooled);
75
- const appUrl = getConfiguredAppDatabaseUrl("DATABASE_URL");
76
- if (appUrl)
77
- return isServerlessRuntime() ? stripNeonPooler(appUrl) : appUrl;
78
- const unpooled = getAppConfig().runtime.databaseUrlUnpooled;
79
- if (unpooled)
80
- return stripNeonPooler(unpooled);
81
- const url = getDatabaseUrl(fallback);
82
- return isServerlessRuntime() ? stripNeonPooler(url) : url;
131
+ return resolveRuntimeDatabase(fallback).url;
132
+ }
133
+ export function getRuntimeDatabaseSource(fallback = "") {
134
+ return resolveRuntimeDatabase(fallback).source;
83
135
  }
84
136
  /** Same per-app resolution for DATABASE_AUTH_TOKEN (used by Turso/libsql). */
85
137
  export function getDatabaseAuthToken() {
@@ -22,6 +22,6 @@ export declare function createDb(config: DbConfig): Promise<(import("drizzle-orm
22
22
  export type DrizzleDb = Awaited<ReturnType<typeof createDb>>;
23
23
  export { createGetDb } from "./create-get-db.js";
24
24
  export { deferMigration, MIGRATION_DEFERRED, runMigrations, withMigrationRuntime, type MigrationEntry, type MigrationRunResult, type MigrationSource, type MigrationSql, } from "./migrations.js";
25
- export { getDbExec, createDbExec, getDatabaseUrl, getRuntimeDatabaseUrl, getDialect, isLocalDatabase, isPostgres, assertSchemaMutationAllowed, isSchemaMutationStatement, isProductionServerlessFunctionRuntime, intType, closeDbExec, type DbExec, type DbExecConfig, type DbExecQuery, type DbExecStatement, type Dialect, } from "./client.js";
25
+ export { getDbExec, createDbExec, getDatabaseUrl, getRuntimeDatabaseUrl, getRuntimeDatabaseSource, getDialect, isLocalDatabase, isPostgres, assertSchemaMutationAllowed, isSchemaMutationStatement, isProductionServerlessFunctionRuntime, intType, closeDbExec, type DbExec, type DbExecConfig, type DbExecQuery, type DbExecStatement, type Dialect, } from "./client.js";
26
26
  export { table, text, integer, now } from "./schema.js";
27
27
  export { ensureAdditiveColumns, type EnsureAdditiveColumnsOptions, type EnsureAdditiveColumnsResult, type EnsureAdditiveColumnsLogger, } from "./ensure-additive-columns.js";
package/dist/db/index.js CHANGED
@@ -21,6 +21,6 @@ export async function createDb(config) {
21
21
  }
22
22
  export { createGetDb } from "./create-get-db.js";
23
23
  export { deferMigration, MIGRATION_DEFERRED, runMigrations, withMigrationRuntime, } from "./migrations.js";
24
- export { getDbExec, createDbExec, getDatabaseUrl, getRuntimeDatabaseUrl, getDialect, isLocalDatabase, isPostgres, assertSchemaMutationAllowed, isSchemaMutationStatement, isProductionServerlessFunctionRuntime, intType, closeDbExec, } from "./client.js";
24
+ export { getDbExec, createDbExec, getDatabaseUrl, getRuntimeDatabaseUrl, getRuntimeDatabaseSource, getDialect, isLocalDatabase, isPostgres, assertSchemaMutationAllowed, isSchemaMutationStatement, isProductionServerlessFunctionRuntime, intType, closeDbExec, } from "./client.js";
25
25
  export { table, text, integer, now } from "./schema.js";
26
26
  export { ensureAdditiveColumns, } from "./ensure-additive-columns.js";
@@ -43,6 +43,11 @@ export interface DatabaseSchemaHealthResult {
43
43
  }
44
44
  export declare const DEFAULT_REQUIRED_SCHEMA: RequiredSchemaTable[];
45
45
  export declare function getDatabaseRuntimeFingerprint(): DatabaseRuntimeFingerprint;
46
+ /**
47
+ * Report whether a declared database URL represents the effective shared
48
+ * database, without exposing its value to the caller.
49
+ */
50
+ export declare function getEffectiveDatabaseEnvStatus(key: string): boolean | undefined;
46
51
  export declare function getRuntimeDebugFingerprint(): RuntimeDebugFingerprint;
47
52
  export declare function runDatabaseSchemaHealthCheck(options?: {
48
53
  exec?: DbExec;
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { getDialect, getDbExec, getRuntimeDatabaseUrl, } from "./client.js";
2
+ import { getDialect, getDbExec, getRuntimeDatabaseUrl, getRuntimeDatabaseSource, isLocalDatabase, } from "./client.js";
3
3
  export const DEFAULT_REQUIRED_SCHEMA = [
4
4
  {
5
5
  table: "agent_runs",
@@ -74,17 +74,6 @@ function envValue(key) {
74
74
  function appEnvPrefix() {
75
75
  return envValue("APP_NAME")?.toUpperCase().replace(/-/g, "_");
76
76
  }
77
- function databaseUrlSource() {
78
- const appName = appEnvPrefix();
79
- if (appName && envValue(`${appName}_DATABASE_URL`)) {
80
- return `${appName}_DATABASE_URL`;
81
- }
82
- if (envValue("DATABASE_URL"))
83
- return "DATABASE_URL";
84
- if (envValue("NETLIFY_DATABASE_URL"))
85
- return "NETLIFY_DATABASE_URL";
86
- return "default";
87
- }
88
77
  function databaseAuthTokenConfigured() {
89
78
  const appName = appEnvPrefix();
90
79
  return Boolean((appName && envValue(`${appName}_DATABASE_AUTH_TOKEN`)) ||
@@ -137,15 +126,28 @@ export function getDatabaseRuntimeFingerprint() {
137
126
  const parsed = parseDatabaseUrl(url);
138
127
  return {
139
128
  configured: Boolean(url),
140
- source: databaseUrlSource(),
129
+ source: getRuntimeDatabaseSource(),
141
130
  dialect: getDialect(),
142
131
  urlHash: shortHash(url),
143
132
  appName: envValue("APP_NAME"),
144
133
  authTokenConfigured: databaseAuthTokenConfigured(),
145
- netlifyDatabaseUrlConfigured: Boolean(envValue("NETLIFY_DATABASE_URL")),
134
+ netlifyDatabaseUrlConfigured: Boolean(envValue("NETLIFY_DATABASE_URL") ||
135
+ envValue("NETLIFY_DATABASE_URL_UNPOOLED")),
146
136
  ...parsed,
147
137
  };
148
138
  }
139
+ /**
140
+ * Report whether a declared database URL represents the effective shared
141
+ * database, without exposing its value to the caller.
142
+ */
143
+ export function getEffectiveDatabaseEnvStatus(key) {
144
+ if (!/(?:^|_)DATABASE_URL(?:_UNPOOLED)?$/.test(key))
145
+ return undefined;
146
+ const database = getDatabaseRuntimeFingerprint();
147
+ if (!database.configured || isLocalDatabase())
148
+ return false;
149
+ return database.source === key;
150
+ }
149
151
  export function getRuntimeDebugFingerprint() {
150
152
  return {
151
153
  app: envValue("APP_NAME") ?? "unknown",
@@ -1363,6 +1363,43 @@ function getAppOriginClientConfigScript() {
1363
1363
  env.VITE_AGENT_NATIVE_WORKSPACE_APPS_JSON,
1364
1364
  ),
1365
1365
  );
1366
+ const workspaceAppMountPaths = (() => {
1367
+ const raw = firstNonEmpty(
1368
+ env.AGENT_NATIVE_WORKSPACE_APPS_JSON,
1369
+ env.VITE_AGENT_NATIVE_WORKSPACE_APPS_JSON,
1370
+ );
1371
+ if (!raw) return;
1372
+ try {
1373
+ const parsed = JSON.parse(raw);
1374
+ const entries = Array.isArray(parsed)
1375
+ ? parsed
1376
+ : parsed && typeof parsed === "object" && "apps" in parsed
1377
+ ? parsed.apps
1378
+ : null;
1379
+ if (!Array.isArray(entries)) return;
1380
+ const paths = Array.from(
1381
+ new Set(
1382
+ entries
1383
+ .map((entry) => {
1384
+ if (!entry || typeof entry !== "object") return null;
1385
+ const rawPath =
1386
+ typeof entry.path === "string"
1387
+ ? entry.path
1388
+ : typeof entry.id === "string"
1389
+ ? "/" + entry.id
1390
+ : null;
1391
+ if (!rawPath) return null;
1392
+ const normalized = normalizeAppBasePath(rawPath);
1393
+ return normalized || null;
1394
+ })
1395
+ .filter(Boolean),
1396
+ ),
1397
+ );
1398
+ return paths.length ? paths : undefined;
1399
+ } catch {
1400
+ return;
1401
+ }
1402
+ })();
1366
1403
  const appHomePath = resolveAgentNativeAppHomePath(getAgentNativeAppConfig().app);
1367
1404
  const config = {
1368
1405
  appHomePath,
@@ -1370,6 +1407,7 @@ function getAppOriginClientConfigScript() {
1370
1407
  ...(workspaceGatewayUrl ? { workspaceGatewayUrl } : {}),
1371
1408
  ...(workspaceOAuthOrigin ? { workspaceOAuthOrigin } : {}),
1372
1409
  ...(workspaceRuntime ? { workspaceRuntime: true } : {}),
1410
+ ...(workspaceAppMountPaths ? { workspaceAppMountPaths } : {}),
1373
1411
  };
1374
1412
  if (Object.keys(config).length === 0) return null;
1375
1413
  return (
@@ -11,14 +11,14 @@
11
11
  * DELETE /_agent-native/notifications/:id — delete
12
12
  */
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
+ error?: undefined;
14
15
  count: number;
15
16
  updated?: undefined;
16
- error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
+ error?: undefined;
19
20
  count?: undefined;
20
21
  updated: number;
21
- error?: undefined;
22
22
  ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
@@ -26,8 +26,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
26
26
  error: string;
27
27
  ok?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  count?: undefined;
30
31
  updated?: undefined;
31
32
  ok: boolean;
32
- error?: undefined;
33
33
  }>>;
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
44
  summary: import("./types.js").TraceSummary;
46
45
  spans: import("./types.js").TraceSpan[];
47
46
  id?: undefined;
47
+ error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
- error?: undefined;
51
50
  summary?: undefined;
52
51
  spans?: undefined;
53
52
  id: string;
53
+ error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
60
  ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
65
+ error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;