@agent-native/core 0.145.7 → 0.145.8
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +3 -2
- package/corpus/templates/analytics/changelog/2026-08-07-settings-skeleton-placeholders.md +6 -0
- package/corpus/templates/analytics/netlify.toml +1 -1
- package/corpus/templates/analytics/package.json +1 -0
- package/corpus/templates/analytics/scripts/migrate-production.ts +24 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +9 -0
- package/corpus/templates/analytics/server/plugins/db.ts +14 -3
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/chat/AGENTS.md +3 -0
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +21 -3
- package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +23 -5
- package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +24 -5
- package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +23 -3
- package/corpus/templates/crm/changelog/2026-08-07-settings-loading-skeletons.md +6 -0
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/client/components/ApiKeySettings.js +2 -5
- package/dist/client/org/TeamPage.js +4 -2
- package/dist/client/settings/AccountSettingsCard.js +2 -1
- package/dist/client/settings/AgentsSection.js +2 -1
- package/dist/client/settings/SecretsSection.js +3 -2
- package/dist/client/settings/SettingsPanel.js +6 -12
- package/dist/client/settings/SettingsSkeleton.d.ts +12 -0
- package/dist/client/settings/SettingsSkeleton.js +12 -0
- package/dist/client/settings/VoiceTranscriptionSection.js +5 -4
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.js +1 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts +13 -7
- package/dist/db/client.js +124 -7
- package/dist/db/ddl-guard.js +3 -1
- package/dist/db/ensure-additive-columns.d.ts +2 -0
- package/dist/db/ensure-additive-columns.js +5 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +2 -2
- package/dist/db/migrations.d.ts +11 -54
- package/dist/db/migrations.js +70 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/index.d.ts +1 -0
- package/dist/jobs/index.js +1 -0
- package/dist/jobs/run-history.d.ts +4 -0
- package/dist/jobs/run-history.js +44 -0
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat-plugin.js +6 -4
- package/dist/server/auth-plugin.js +5 -0
- package/dist/server/better-auth-instance.js +0 -61
- package/dist/server/better-auth-migrations.d.ts +8 -0
- package/dist/server/better-auth-migrations.js +179 -0
- package/dist/server/edge.d.ts +2 -0
- package/dist/server/edge.js +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/release-migrations.d.ts +8 -0
- package/dist/server/release-migrations.js +24 -0
- package/dist/server/sweep-runtime.d.ts +1 -1
- package/dist/server/sweep-runtime.js +15 -0
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/chat/AGENTS.md +3 -0
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/default/AGENTS.md +3 -0
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/workspace-core/AGENTS.md +3 -0
- package/dist/templates/workspace-root/AGENTS.md +3 -0
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/chat/AGENTS.md +3 -0
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/default/AGENTS.md +3 -0
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/workspace-core/AGENTS.md +3 -0
- package/src/templates/workspace-root/AGENTS.md +3 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Skeleton } from "@agent-native/toolkit/design-system";
|
|
2
3
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@agent-native/toolkit/ui/alert-dialog";
|
|
3
4
|
import { Button as ToolkitButton } from "@agent-native/toolkit/ui/button";
|
|
4
5
|
import { Pagination, PaginationContent, PaginationItem, PaginationNext, PaginationPrevious, } from "@agent-native/toolkit/ui/pagination";
|
|
@@ -9,6 +10,7 @@ import { forwardRef, useEffect, useMemo, useRef, useState, } from "react";
|
|
|
9
10
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../components/ui/tooltip.js";
|
|
10
11
|
import { useT } from "../i18n.js";
|
|
11
12
|
import { SettingsGroup, SettingsRow } from "../settings/SettingsRow.js";
|
|
13
|
+
import { SettingsSkeleton } from "../settings/SettingsSkeleton.js";
|
|
12
14
|
import { cn } from "../utils.js";
|
|
13
15
|
import { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useSetOrgDomain, useSetOrgWorkspaceUrl, useSetOrgAuthProvider, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useJoinByDomain, useAppRoles, useSetAppMemberRole, ORG_MEMBER_PAGE_SIZE, } from "./hooks.js";
|
|
14
16
|
const Button = forwardRef(({ className, ...props }, ref) => (_jsx(ToolkitButton, { ref: ref, variant: "ghost", className: cn("h-auto p-0 hover:bg-transparent hover:text-inherit active:scale-100 [&_svg]:!size-auto", className), ...props })));
|
|
@@ -139,7 +141,7 @@ function MembersTableCard({ members, totalMembers, pendingInvites, isLoadingMemb
|
|
|
139
141
|
a.email.toLowerCase(),
|
|
140
142
|
a.role,
|
|
141
143
|
]));
|
|
142
|
-
return (_jsxs("section", { className: "overflow-hidden rounded-xl border border-border/70 bg-card text-card-foreground", children: [_jsxs("div", { className: "flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium", children: t("org.members") }), _jsx("p", { className: "text-xs text-muted-foreground", children: t("org.memberCount", { count: totalMembers }) })] }), canInvite && !showInviteForm && (_jsxs(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: () => setShowInviteForm(true), className: "inline-flex items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: [_jsx(IconUserPlus, { size: 14 }), t("org.inviteMembers")] }))] }), canInvite && showInviteForm && (_jsx("div", { className: "border-b border-border p-4", children: _jsx(BulkInviteForm, { currentUserRole: currentUserRole, onClose: () => setShowInviteForm(false) }) })), _jsx("div", { className: "divide-y divide-border/60 border-t border-border/60", children: isLoadingMembers && members.length === 0 ? ([
|
|
144
|
+
return (_jsxs("section", { className: "overflow-hidden rounded-xl border border-border/70 bg-card text-card-foreground", children: [_jsxs("div", { className: "flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium", children: t("org.members") }), _jsx("p", { className: "text-xs text-muted-foreground", children: t("org.memberCount", { count: totalMembers }) })] }), canInvite && !showInviteForm && (_jsxs(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: () => setShowInviteForm(true), className: "inline-flex items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: [_jsx(IconUserPlus, { size: 14 }), t("org.inviteMembers")] }))] }), canInvite && showInviteForm && (_jsx("div", { className: "border-b border-border p-4", children: _jsx(BulkInviteForm, { currentUserRole: currentUserRole, onClose: () => setShowInviteForm(false) }) })), _jsx("div", { className: "divide-y divide-border/60 border-t border-border/60", children: isLoadingMembers && members.length === 0 ? (_jsx("div", { role: "status", "aria-busy": "true", "aria-label": "Loading members", children: ["w-44", "w-56", "w-64"].map((nameWidth) => (_jsxs("div", { className: "flex items-center gap-3 px-5 py-4", children: [_jsx(Skeleton, { className: "size-8 rounded-full bg-muted" }), _jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: cn("h-3.5 bg-muted", nameWidth) }), _jsx(Skeleton, { className: "h-3 w-20 bg-muted" })] })] }, nameWidth))) })) : members.length === 0 && pendingInvites.length === 0 ? (_jsx("div", { className: "px-5 py-10 text-center text-sm text-muted-foreground", children: t("org.noMembers") })) : (_jsxs(_Fragment, { children: [members.map((m) => (_jsx(MemberRow, { email: m.email, role: m.role, isCurrentUser: m.email === currentUserEmail, currentUserRole: currentUserRole, appRoles: appRoles, appRole: appRoleByEmail.get(m.email.toLowerCase()) ?? null, canManageAppRoles: Boolean(appRoleData?.canManage) }, m.email))), pendingInvites.map((inv) => (_jsx(PendingInviteRow, { invite: inv }, inv.id)))] })) }), (memberOffset > 0 || hasNextPage) && (_jsx(MemberPagination, { memberOffset: memberOffset, totalMembers: totalMembers, hasNextPage: hasNextPage, nextMemberOffset: nextMemberOffset, isFetchingMembers: isFetchingMembers, onMemberPageChange: onMemberPageChange }))] }));
|
|
143
145
|
}
|
|
144
146
|
function MemberPagination({ memberOffset, totalMembers, hasNextPage, nextMemberOffset, isFetchingMembers, onMemberPageChange, }) {
|
|
145
147
|
const t = useT();
|
|
@@ -547,7 +549,7 @@ function A2ASecretSection({ isSet }) {
|
|
|
547
549
|
export function TeamPage({ layout, title, showTitle = true, createOrgDescription, className, appRoles, }) {
|
|
548
550
|
const t = useT();
|
|
549
551
|
const { data: org, isLoading } = useOrg();
|
|
550
|
-
const content = (_jsxs("div", { className: `w-full space-y-6 ${className ?? ""}`, children: [showTitle ? (_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title ?? t("org.team") })) : null, isLoading && (_jsx("section", { className: "rounded-lg border border-border bg-card p-6", children: _jsx(
|
|
552
|
+
const content = (_jsxs("div", { className: `w-full space-y-6 ${className ?? ""}`, children: [showTitle ? (_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title ?? t("org.team") })) : null, isLoading && (_jsx("section", { className: "rounded-lg border border-border bg-card p-6", children: _jsx(SettingsSkeleton, { lines: 3 }) })), !isLoading && (_jsxs(_Fragment, { children: [_jsx(PendingInvitationsCard, {}), org?.domainMatches && org.domainMatches.length > 0 && (_jsx(JoinByDomainCard, { matches: org.domainMatches })), !org?.orgId ? (_jsx(CreateOrgCard, { description: createOrgDescription })) : (_jsx(MembersCard, { appRoles: appRoles }))] }))] }));
|
|
551
553
|
const wrapped = (_jsx(TooltipProvider, { delayDuration: 200, children: content }));
|
|
552
554
|
return layout ? _jsx(_Fragment, { children: layout(wrapped) }) : wrapped;
|
|
553
555
|
}
|
|
@@ -11,6 +11,7 @@ import { useSession } from "../use-session.js";
|
|
|
11
11
|
import { cn } from "../utils.js";
|
|
12
12
|
import { SchedulingTimezoneField } from "./SchedulingTimezoneField.js";
|
|
13
13
|
import { SettingsGroup, SettingsRow } from "./SettingsRow.js";
|
|
14
|
+
import { SettingsSkeleton } from "./SettingsSkeleton.js";
|
|
14
15
|
function profileInitials(name) {
|
|
15
16
|
return (name
|
|
16
17
|
.split(/[ @._-]+/)
|
|
@@ -74,7 +75,7 @@ function PasswordSettings() {
|
|
|
74
75
|
const hasPassword = authMethods.data?.hasPassword ?? false;
|
|
75
76
|
if (!session?.email)
|
|
76
77
|
return null;
|
|
77
|
-
const passwordForm = isLoading ? (_jsx(
|
|
78
|
+
const passwordForm = isLoading ? (_jsx(SettingsSkeleton, { lines: 2 })) : authMethods.error ? (_jsx("p", { className: "text-xs text-destructive", children: t("settings.passwordSaveError") })) : (_jsxs("div", { className: "space-y-3", children: [hasPassword && (_jsx(TextField, { id: "agent-native-current-password", type: "password", label: t("settings.passwordCurrentLabel"), value: currentPassword, onChange: (value) => {
|
|
78
79
|
clearStatus();
|
|
79
80
|
setCurrentPassword(value);
|
|
80
81
|
}, placeholder: t("settings.passwordPlaceholder"), autoComplete: "current-password", disabled: isPending })), _jsx(TextField, { id: "agent-native-new-password", type: "password", label: t("settings.passwordNewLabel"), value: newPassword, onChange: (value) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Skeleton } from "@agent-native/toolkit/design-system";
|
|
2
3
|
import { ButtonBase as ToolkitButtonBase } from "@agent-native/toolkit/ui/button";
|
|
3
4
|
import { IconPlus, IconTrash, IconX, IconCheck, IconLoader2, IconAlertTriangle, IconExternalLink, IconRefresh, IconTopologyRing2, } from "@tabler/icons-react";
|
|
4
5
|
import { useState, useEffect, useRef, useCallback } from "react";
|
|
@@ -403,7 +404,7 @@ export function AgentsSection() {
|
|
|
403
404
|
}, className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: [showAdd ? _jsx(IconX, { size: 13 }) : _jsx(IconPlus, { size: 13 }), showAdd ? "Cancel" : "Connect agent"] }), showAdd && (_jsx(AgentAddPopover, { initialName: prefill?.name, initialUrl: prefill?.url, initialDescription: prefill?.description, secretSet: org?.a2aSecretSet, syncSecret: syncSecret, onAdd: handleAdd, onClose: () => {
|
|
404
405
|
setShowAdd(false);
|
|
405
406
|
setPrefill(null);
|
|
406
|
-
} }))] }) }), _jsx(A2ASecretStatusRow, { org: org, syncSecret: syncSecret }), loading ? (_jsxs("div", { className: "space-y-1.5", children: [_jsx(
|
|
407
|
+
} }))] }) }), _jsx(A2ASecretStatusRow, { org: org, syncSecret: syncSecret }), loading ? (_jsxs("div", { className: "space-y-1.5", role: "status", "aria-busy": "true", "aria-label": "Loading connected agents", children: [_jsx(Skeleton, { className: "h-6 w-full bg-muted/50" }), _jsx(Skeleton, { className: "h-6 w-3/4 bg-muted/50" })] })) : agents.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center rounded-xl border border-border/70 bg-card px-5 py-8 text-center", children: [_jsx("span", { className: "mb-2 flex size-9 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(IconTopologyRing2, { size: 17 }) }), _jsx("p", { className: "text-sm font-medium text-foreground", children: "No connected agents yet" }), _jsx("p", { className: "mt-1 max-w-sm text-xs leading-5 text-muted-foreground", children: "Connect an A2A agent to delegate work from chat." }), _jsxs("button", { onClick: () => setShowAdd(true), className: "mt-4 inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: [_jsx(IconPlus, { size: 13 }), "Connect agent"] })] })) : (_jsx("div", { className: "overflow-hidden rounded-xl border border-border/70 bg-card text-card-foreground", children: _jsx("div", { className: "divide-y divide-border/60 px-4", children: agents.map((agent) => {
|
|
407
408
|
const probe = probeById?.get(getRemoteAgentIdFromPath(agent.path).toLowerCase());
|
|
408
409
|
const dotState = !probe
|
|
409
410
|
? null
|
|
@@ -14,6 +14,7 @@ import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popov
|
|
|
14
14
|
import { Tooltip, TooltipContent, TooltipTrigger, } from "../components/ui/tooltip.js";
|
|
15
15
|
import { useT } from "../i18n.js";
|
|
16
16
|
import { cn } from "../utils.js";
|
|
17
|
+
import { SettingsSkeleton } from "./SettingsSkeleton.js";
|
|
17
18
|
const Button = React.forwardRef(({ className, ...props }, ref) => (_jsx(ToolkitButton, { ref: ref, variant: "ghost", className: cn("h-auto p-0 hover:bg-transparent hover:text-inherit active:scale-100 [&_svg]:!size-auto", className), ...props })));
|
|
18
19
|
Button.displayName = "SecretsPrimitiveButton";
|
|
19
20
|
const ENDPOINT = agentNativePath("/_agent-native/secrets");
|
|
@@ -62,7 +63,7 @@ export function SecretsSection({ focusKey }) {
|
|
|
62
63
|
return (_jsxs("p", { className: "text-[10px] text-red-500", children: ["Failed to load secrets: ", error] }));
|
|
63
64
|
}
|
|
64
65
|
if (secrets === null) {
|
|
65
|
-
return
|
|
66
|
+
return _jsx(SettingsSkeleton, { lines: 2 });
|
|
66
67
|
}
|
|
67
68
|
if (secrets.length === 0) {
|
|
68
69
|
return (_jsxs("div", { className: "space-y-3", children: [_jsx(KeysHeader, { onCustomKey: () => setCustomKeyOpen(true) }), _jsx(AdHocKeysSection, { showForm: customKeyOpen, onShowFormChange: setCustomKeyOpen, showEmptyState: true })] }));
|
|
@@ -356,7 +357,7 @@ function AdHocKeysSection({ showForm, onShowFormChange, showEmptyState, }) {
|
|
|
356
357
|
}
|
|
357
358
|
}, "aria-label": t("secrets.scopeLabel"), description: t(formScope === "user"
|
|
358
359
|
? "secrets.scopePersonalDescription"
|
|
359
|
-
: "secrets.scopeWorkspaceDescription"), className: "text-[11px]" }), _jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: resetForm, className: "rounded border border-border px-2 py-1 text-[10px] font-medium text-muted-foreground hover:text-foreground", children: "Cancel" }), _jsx(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: handleAdd, disabled: !formName.trim() || !formValue.trim() || formBusy, className: "inline-flex items-center gap-1 rounded px-2 py-1 text-[10px] font-medium disabled:opacity-40", style: { backgroundColor: "#00B5FF", color: "white" }, children: formBusy ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : ("Save") })] }), formError && _jsx("p", { className: "text-[10px] text-red-500", children: formError })] })), loading ? (
|
|
360
|
+
: "secrets.scopeWorkspaceDescription"), className: "text-[11px]" }), _jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: resetForm, className: "rounded border border-border px-2 py-1 text-[10px] font-medium text-muted-foreground hover:text-foreground", children: "Cancel" }), _jsx(Button, { type: "button", intent: "primary", emphasis: "solid", onClick: handleAdd, disabled: !formName.trim() || !formValue.trim() || formBusy, className: "inline-flex items-center gap-1 rounded px-2 py-1 text-[10px] font-medium disabled:opacity-40", style: { backgroundColor: "#00B5FF", color: "white" }, children: formBusy ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : ("Save") })] }), formError && _jsx("p", { className: "text-[10px] text-red-500", children: formError })] })), loading ? (_jsx(SettingsSkeleton, { lines: 2 })) : keys.length === 0 && !showForm && showEmptyState ? (_jsx("p", { className: "text-[10px] text-muted-foreground", children: "No keys added yet." })) : keys.length > 0 ? (_jsx("div", { className: "overflow-hidden rounded-md border border-border", children: keys.map((key) => (_jsx("div", { className: "border-b border-border px-2.5 py-2 last:border-b-0", children: _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-[11px] font-medium text-foreground font-mono truncate", children: key.name }), _jsx("span", { className: `rounded-full px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide ${key.scope === "workspace"
|
|
360
361
|
? "bg-blue-500/15 text-blue-500"
|
|
361
362
|
: "bg-accent/60 text-muted-foreground"}`, children: key.scope === "workspace" ? "workspace" : "personal" })] }), key.description && (_jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: key.description })), _jsx("div", { className: "flex items-center gap-2 text-[10px] text-muted-foreground mt-0.5", children: _jsxs("span", { children: ["Ending in", " ", _jsx("code", { className: "rounded bg-background px-1 py-0.5 text-foreground", children: key.last4 })] }) })] }), _jsx("div", { className: "shrink-0", children: confirmDeleteName === key.name ? (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { type: "button", intent: "danger", emphasis: "solid", onClick: () => handleDelete(key.name), disabled: deletingName === key.name, className: "rounded px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide bg-red-500/15 text-red-500 hover:bg-red-500/25 disabled:opacity-40", children: deletingName === key.name ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : ("Confirm") }), _jsx(Button, { type: "button", intent: "neutral", emphasis: "solid", onClick: () => setConfirmDeleteName(null), className: "rounded px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide bg-accent/60 text-muted-foreground hover:text-foreground", children: "Cancel" })] })) : (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", intent: "danger", emphasis: "ghost", onClick: () => setConfirmDeleteName(key.name), className: "text-muted-foreground hover:text-red-500", children: _jsx(IconTrash, { size: 12 }) }) }), _jsx(TooltipContent, { children: "Delete" })] })) })] }) }, `${key.scope}-${key.name}`))) })) : null, toast && (_jsx("p", { className: `text-[10px] ${toast.kind === "ok" ? "text-green-500" : "text-red-500"}`, children: toast.text }))] }));
|
|
362
363
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Checkbox, Picker, TextField, } from "@agent-native/toolkit/design-system";
|
|
2
|
+
import { Checkbox, Picker, Skeleton, TextField, } from "@agent-native/toolkit/design-system";
|
|
3
3
|
import { Button as ToolkitButton } from "@agent-native/toolkit/ui/button";
|
|
4
4
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@agent-native/toolkit/ui/command";
|
|
5
5
|
import { IconChevronDown, IconChevronRight, IconCheck, IconExternalLink, IconBrain, IconHierarchy2, IconBrowser, IconGitBranch, IconCloud, IconDatabase, IconFolder, IconShield, IconPlugConnected, IconTopologyRing2, IconLoader2, IconUpload, IconCoin, IconMail, IconKey, IconMicrophone, IconEyeOff, IconBolt, IconGauge, IconApps, IconUsersGroup, IconTool, } from "@tabler/icons-react";
|
|
@@ -26,6 +26,7 @@ import { ExtensionsSettingsContent } from "./ExtensionsSettingsContent.js";
|
|
|
26
26
|
import { SecretsSection } from "./SecretsSection.js";
|
|
27
27
|
import { SettingsGroup, SettingsRow } from "./SettingsRow.js";
|
|
28
28
|
import { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, } from "./SettingsSection.js";
|
|
29
|
+
import { SettingsLoadingRow, SettingsSkeleton } from "./SettingsSkeleton.js";
|
|
29
30
|
import { UsageSection } from "./UsageSection.js";
|
|
30
31
|
import { useBuilderConnectFlow, useBuilderStatus, } from "./useBuilderStatus.js";
|
|
31
32
|
import { settingsSectionDomId, useSettingsPanelController, } from "./useSettingsPanelController.js";
|
|
@@ -37,13 +38,6 @@ ManageButton.displayName = "SettingsManageButton";
|
|
|
37
38
|
const IntegrationsPanel = lazy(() => import("../integrations/IntegrationsPanel.js").then((m) => ({
|
|
38
39
|
default: m.IntegrationsPanel,
|
|
39
40
|
})));
|
|
40
|
-
// ─── Shared helpers ─────────────────────────────────────────────────────────
|
|
41
|
-
function SettingsSkeleton({ lines = 3 }) {
|
|
42
|
-
return (_jsx("div", { className: "space-y-3 animate-pulse", children: Array.from({ length: lines }, (_, i) => (_jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "h-3 rounded bg-muted-foreground/10", style: { width: i === 0 ? "30%" : i === 1 ? "100%" : "60%" } }), i < 2 && (_jsx("div", { className: "h-9 rounded-md border border-border bg-muted-foreground/5" }))] }, i))) }));
|
|
43
|
-
}
|
|
44
|
-
function SettingsLoadingRow({ label, description, controlCount = 1, }) {
|
|
45
|
-
return (_jsxs("div", { className: "flex items-center justify-between gap-4 px-5 py-4 sm:px-6", children: [_jsxs("div", { className: "min-w-0 space-y-2", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: label }), description ? (_jsx("p", { className: "text-sm leading-5 text-muted-foreground", children: description })) : null] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", "aria-hidden": "true", children: Array.from({ length: controlCount }, (_, index) => (_jsx("div", { className: cn("h-9 animate-pulse rounded-md border border-border bg-muted-foreground/10", index === 0 ? "w-28" : "w-20") }, index))) })] }));
|
|
46
|
-
}
|
|
47
41
|
const CONTROL_STYLE = {
|
|
48
42
|
fontSize: 12,
|
|
49
43
|
lineHeight: 1,
|
|
@@ -553,7 +547,7 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
|
|
|
553
547
|
}
|
|
554
548
|
catch { }
|
|
555
549
|
};
|
|
556
|
-
return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", subtitle: "Use Builder.io free credits or your own LLM provider.", required: true, connected: initialLoading ? undefined : anyKeyConfigured, grouped: isPage && grouped, open: open, onToggle: onToggle, children: initialLoading ? (_jsx(SettingsLoadingRow, {
|
|
550
|
+
return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", subtitle: "Use Builder.io free credits or your own LLM provider.", required: true, connected: initialLoading ? undefined : anyKeyConfigured, grouped: isPage && grouped, open: open, onToggle: onToggle, children: initialLoading ? (_jsx(SettingsLoadingRow, { controlCount: 2 })) : (_jsxs("div", { className: cn(isPage
|
|
557
551
|
? "flex items-center justify-between gap-4 px-5 py-4 sm:px-6"
|
|
558
552
|
: "space-y-2"), children: [isPage && (_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Connect an LLM" }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: "Use Builder.io free credits or your own provider." })] })), _jsxs("div", { className: cn(isPage && "flex flex-wrap items-center justify-end gap-2"), children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io", compact: isPage }), builderConnected && !isPage && (_jsxs(Button, { type: "button", intent: "neutral", emphasis: "ghost", "aria-expanded": manualSetupOpen, "aria-controls": "llm-manual-setup", onClick: () => setManualSetupOpen((open) => !open), className: cn("inline-flex items-center gap-1 px-0.5 text-muted-foreground hover:text-foreground", isPage ? "text-xs" : "text-[10px]"), children: [t("agentPanel.addOwnKeys", {
|
|
559
553
|
defaultValue: "Add your own keys",
|
|
@@ -764,7 +758,7 @@ function AppModelDefaultsSectionInner({ grouped = false, open, onToggle, }) {
|
|
|
764
758
|
setSaving(false);
|
|
765
759
|
}
|
|
766
760
|
};
|
|
767
|
-
return (_jsx(SettingsSection, { id: settingsSectionDomId("app-models"), icon: _jsx(IconApps, { size: 14 }), title: "App Default Model", subtitle: "Choose the model used by this app by default.", connected: loading ? undefined : hasAppDefault, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, {
|
|
761
|
+
return (_jsx(SettingsSection, { id: settingsSectionDomId("app-models"), icon: _jsx(IconApps, { size: 14 }), title: "App Default Model", subtitle: "Choose the model used by this app by default.", connected: loading ? undefined : hasAppDefault, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, {})) : settings ? (isPage ? (_jsxs(SettingsRow, { className: grouped ? undefined : "-mx-5 sm:-mx-6", label: "Default model", description: hasAppDefault
|
|
768
762
|
? `Used by ${friendlyAppName(settings.appId) || "this app"} · ${scopeLabel}.`
|
|
769
763
|
: "Uses the global LLM default.", status: _jsx("span", { className: "text-xs text-muted-foreground", children: settings.source }), control: _jsx(AppDefaultModelPicker, { engines: settings.engines, value: hasAppDefault
|
|
770
764
|
? `${selectedEngine}::${selectedModel}`
|
|
@@ -987,7 +981,7 @@ function AgentLimitsSectionInner({ grouped = false, open, onToggle, }) {
|
|
|
987
981
|
? undefined
|
|
988
982
|
: settings
|
|
989
983
|
? settings.maxIterations !== settings.defaultMaxIterations
|
|
990
|
-
: false, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, {
|
|
984
|
+
: false, grouped: isPage && grouped, open: open, onToggle: onToggle, children: loading ? (_jsx(SettingsLoadingRow, {})) : settings ? (isPage ? (_jsx(SettingsRow, { className: grouped ? undefined : "-mx-5 sm:-mx-6", label: "Max iterations", description: `Default ${settings.defaultMaxIterations.toLocaleString()} · applies to ${scopeLabel}.`, status: _jsx("span", { className: "text-xs text-muted-foreground", children: settings.source }), control: _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(ManageButton, {}) }), _jsx(PopoverContent, { align: "end", sideOffset: 6, className: "w-80 p-4", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Max iterations" }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Choose how long a response can work before pausing." })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("input", { type: "number", min: settings.minMaxIterations, max: settings.maxMaxIterations, value: value, disabled: !settings.canUpdate || saving, onChange: (event) => {
|
|
991
985
|
setValue(event.target.value);
|
|
992
986
|
setError(null);
|
|
993
987
|
}, onKeyDown: (event) => {
|
|
@@ -1020,7 +1014,7 @@ function CapabilityStatusStrip({ isDevMode, builderConnected, builderLoading, bu
|
|
|
1020
1014
|
: builderConnected && builderBranchesAvailable
|
|
1021
1015
|
? "Builder branches"
|
|
1022
1016
|
: "Desktop/local";
|
|
1023
|
-
return (_jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-2.5 py-2", children: [_jsx("div", { className: "mb-1.5 text-[10px] font-medium text-muted-foreground", children: "Available now" }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(CapabilityStatusRow, { label: "App", value: "Chat + actions", active: true }), _jsx(CapabilityStatusRow, { label: "Code", value: codeLabel, active: codeAvailable }), _jsx(CapabilityStatusRow, { label: "Builder", active: builderConnected, value: builderLoading ? ("
|
|
1017
|
+
return (_jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-2.5 py-2", children: [_jsx("div", { className: "mb-1.5 text-[10px] font-medium text-muted-foreground", children: "Available now" }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(CapabilityStatusRow, { label: "App", value: "Chat + actions", active: true }), _jsx(CapabilityStatusRow, { label: "Code", value: codeLabel, active: codeAvailable }), _jsx(CapabilityStatusRow, { label: "Builder", active: builderConnected, value: builderLoading ? (_jsx(Skeleton, { className: "h-3 w-16" })) : builderConnected ? ("Connected") : (_jsx(Button, { type: "button", intent: "neutral", emphasis: "outline", onClick: onOpenLlm, className: "rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground transition-colors hover:bg-accent/40 hover:text-foreground", children: "Connect" })) })] })] }));
|
|
1024
1018
|
}
|
|
1025
1019
|
function SettingsPanelContent({ isDevMode, initialSection, sectionRequestKey, sections = ALL_SETTINGS_SECTIONS, showCapabilityStrip = true, className, surface = "sidebar", builderConnectionOwnedExternally = false, agentAdditionalContent, }) {
|
|
1026
1020
|
const { status: builder, loading: builderLoading } = useBuilderStatus({
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
export interface SettingsSkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
lines?: number;
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
/** Layout-matching placeholder for settings fields while their data loads. */
|
|
7
|
+
export declare function SettingsSkeleton({ lines, label, className, ...props }: SettingsSkeletonProps): import("react").JSX.Element;
|
|
8
|
+
export interface SettingsLoadingRowProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
controlCount?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Layout-matching placeholder for a single settings row while it loads. */
|
|
12
|
+
export declare function SettingsLoadingRow({ controlCount, className, ...props }: SettingsLoadingRowProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Skeleton } from "@agent-native/toolkit/design-system";
|
|
3
|
+
import { cn } from "../utils.js";
|
|
4
|
+
const LABEL_WIDTHS = ["w-1/3", "w-full", "w-3/5"];
|
|
5
|
+
/** Layout-matching placeholder for settings fields while their data loads. */
|
|
6
|
+
export function SettingsSkeleton({ lines = 3, label = "Loading settings", className, ...props }) {
|
|
7
|
+
return (_jsx("div", { ...props, className: cn("space-y-3", className), role: "status", "aria-busy": "true", "aria-label": label, children: Array.from({ length: lines }, (_, index) => (_jsxs("div", { className: "space-y-1.5", children: [_jsx(Skeleton, { className: cn("h-3", LABEL_WIDTHS[index % 3]) }), index < 2 && (_jsx(Skeleton, { className: "h-9 w-full border border-border bg-muted-foreground/5" }))] }, index))) }));
|
|
8
|
+
}
|
|
9
|
+
/** Layout-matching placeholder for a single settings row while it loads. */
|
|
10
|
+
export function SettingsLoadingRow({ controlCount = 1, className, ...props }) {
|
|
11
|
+
return (_jsxs("div", { ...props, className: cn("flex items-center justify-between gap-4 px-5 py-4 sm:px-6", className), role: "status", "aria-busy": "true", "aria-label": "Loading setting", children: [_jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsx(Skeleton, { className: "h-3.5 w-32" }), _jsx(Skeleton, { className: "h-3 w-3/4 max-w-64" })] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", "aria-hidden": "true", children: Array.from({ length: controlCount }, (_, index) => (_jsx(Skeleton, { className: cn("h-9 border border-border bg-muted-foreground/10", index === 0 ? "w-28" : "w-20") }, index))) })] }));
|
|
12
|
+
}
|
|
@@ -10,12 +10,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
* Provider status comes from `/_agent-native/voice-providers/status`, which
|
|
11
11
|
* mirrors the server transcription route's key/env resolution.
|
|
12
12
|
*/
|
|
13
|
-
import { Picker, Switch } from "@agent-native/toolkit/design-system";
|
|
14
|
-
import { IconAlertCircle, IconCheck, IconChevronDown, IconChevronRight, IconExternalLink,
|
|
13
|
+
import { Picker, Skeleton, Switch } from "@agent-native/toolkit/design-system";
|
|
14
|
+
import { IconAlertCircle, IconCheck, IconChevronDown, IconChevronRight, IconExternalLink, IconLockOpen, } from "@tabler/icons-react";
|
|
15
15
|
import { useCallback, useEffect, useState } from "react";
|
|
16
16
|
import { buildSettingsRoute } from "../../navigation/index.js";
|
|
17
17
|
import { agentNativePath } from "../api-path.js";
|
|
18
18
|
import { SettingsRow } from "./SettingsRow.js";
|
|
19
|
+
import { SettingsSkeleton } from "./SettingsSkeleton.js";
|
|
19
20
|
import { openBuilderConnectPopup, useBuilderStatus, } from "./useBuilderStatus.js";
|
|
20
21
|
const PREFS_URL = agentNativePath("/_agent-native/application-state/voice-transcription-prefs");
|
|
21
22
|
const CLEANUP_PREFS_URL = agentNativePath("/_agent-native/application-state/voice-cleanup-prefs");
|
|
@@ -269,9 +270,9 @@ export function VoiceTranscriptionSection({ compact = false, } = {}) {
|
|
|
269
270
|
};
|
|
270
271
|
if (transcriptionMode === null) {
|
|
271
272
|
if (compact) {
|
|
272
|
-
return (_jsx(SettingsRow, { label: "Voice transcription", description: "Choose how voice input is transcribed.", control: _jsx(
|
|
273
|
+
return (_jsx(SettingsRow, { label: "Voice transcription", description: "Choose how voice input is transcribed.", control: _jsx(Skeleton, { className: "h-9 w-44 border border-border bg-muted-foreground/10", "aria-label": "Loading voice transcription" }) }));
|
|
273
274
|
}
|
|
274
|
-
return (
|
|
275
|
+
return _jsx(SettingsSkeleton, { lines: 1 });
|
|
275
276
|
}
|
|
276
277
|
if (compact) {
|
|
277
278
|
return (_jsx(SettingsRow, { label: "Voice transcription", description: "Choose how voice input is transcribed.", control: _jsx(Picker, { mode: "select", options: [
|
|
@@ -5,4 +5,5 @@ export { AccountSettingsCard, AccountSettingsForm, type AccountSettingsCardProps
|
|
|
5
5
|
export { openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, type BuilderConnectFlow, type BuilderConnectFlowOptions, type BuilderConnectStartOptions, type BuilderStatus, type OpenBuilderConnectPopupOptions, } from "./useBuilderStatus.js";
|
|
6
6
|
export { SecretsSection, type SecretsSectionProps } from "./SecretsSection.js";
|
|
7
7
|
export { SettingsGroup, SettingsRow, type SettingsGroupProps, type SettingsRowProps, } from "./SettingsRow.js";
|
|
8
|
+
export { SettingsLoadingRow, SettingsSkeleton, type SettingsLoadingRowProps, type SettingsSkeletonProps, } from "./SettingsSkeleton.js";
|
|
8
9
|
export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, type SettingsPanelController, type SettingsPanelControllerOptions, } from "./useSettingsPanelController.js";
|
|
@@ -5,4 +5,5 @@ export { AccountSettingsCard, AccountSettingsForm, } from "./AccountSettingsCard
|
|
|
5
5
|
export { openBuilderConnectPopup, useBuilderConnectFlow, useBuilderStatus, withBuilderConnectTrackingParams, } from "./useBuilderStatus.js";
|
|
6
6
|
export { SecretsSection } from "./SecretsSection.js";
|
|
7
7
|
export { SettingsGroup, SettingsRow, } from "./SettingsRow.js";
|
|
8
|
+
export { SettingsLoadingRow, SettingsSkeleton, } from "./SettingsSkeleton.js";
|
|
8
9
|
export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, } from "./useSettingsPanelController.js";
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
package/dist/db/client.d.ts
CHANGED
|
@@ -174,14 +174,20 @@ export declare function withDbTimeout<T>(op: string, run: () => Promise<T>, ms?:
|
|
|
174
174
|
*/
|
|
175
175
|
export declare function isServerlessRuntime(): boolean;
|
|
176
176
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
* True for production serverless execution contexts. Netlify also exposes
|
|
178
|
+
* `NETLIFY=true` in its build environment, so build-time code that owns
|
|
179
|
+
* migrations must use `withMigrationRuntime()` explicitly.
|
|
180
|
+
*/
|
|
181
|
+
export declare function isProductionServerlessFunctionRuntime(env?: NodeJS.ProcessEnv): boolean;
|
|
182
|
+
/** True for statements that change database schema or database privileges. */
|
|
183
|
+
export declare function isSchemaMutationStatement(statement: DbExecStatement): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* Request functions must never be able to prepare or mutate schema. Throwing
|
|
186
|
+
* here makes an unconverted ensureTable path fail loudly instead of silently
|
|
187
|
+
* reporting success against a missing table. The release migration wrapper
|
|
188
|
+
* is the only supported production opt-in.
|
|
184
189
|
*/
|
|
190
|
+
export declare function assertSchemaMutationAllowed(statement: DbExecStatement): void;
|
|
185
191
|
export declare function pgPoolOptions(url: string): Record<string, unknown>;
|
|
186
192
|
/**
|
|
187
193
|
* Shared options for every Neon serverless pool. The startup parameter is
|
package/dist/db/client.js
CHANGED
|
@@ -765,6 +765,47 @@ export function isServerlessRuntime() {
|
|
|
765
765
|
!!process.env.LAMBDA_TASK_ROOT ||
|
|
766
766
|
!!process.env.CF_PAGES);
|
|
767
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* True for production serverless execution contexts. Netlify also exposes
|
|
770
|
+
* `NETLIFY=true` in its build environment, so build-time code that owns
|
|
771
|
+
* migrations must use `withMigrationRuntime()` explicitly.
|
|
772
|
+
*/
|
|
773
|
+
export function isProductionServerlessFunctionRuntime(env = process.env) {
|
|
774
|
+
if (env.NODE_ENV !== "production" || env.NETLIFY_LOCAL === "true") {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
return Boolean(env.NETLIFY === "true" ||
|
|
778
|
+
env.NETLIFY_FUNCTION_NAME ||
|
|
779
|
+
env.AWS_LAMBDA_FUNCTION_NAME ||
|
|
780
|
+
env.AWS_LAMBDA_FUNCTION_VERSION ||
|
|
781
|
+
env.LAMBDA_TASK_ROOT ||
|
|
782
|
+
env.AWS_EXECUTION_ENV?.startsWith("AWS_Lambda") === true ||
|
|
783
|
+
env.VERCEL_FUNCTION_ID ||
|
|
784
|
+
env.VERCEL_REGION ||
|
|
785
|
+
env.VERCEL === "1");
|
|
786
|
+
}
|
|
787
|
+
const SCHEMA_MUTATION_STATEMENT = /^\s*(?:CREATE|ALTER|DROP|TRUNCATE|COMMENT|REINDEX|GRANT|REVOKE)\b/i;
|
|
788
|
+
function rawSql(statement) {
|
|
789
|
+
return typeof statement === "string" ? statement : statement.sql;
|
|
790
|
+
}
|
|
791
|
+
/** True for statements that change database schema or database privileges. */
|
|
792
|
+
export function isSchemaMutationStatement(statement) {
|
|
793
|
+
return SCHEMA_MUTATION_STATEMENT.test(rawSql(statement));
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Request functions must never be able to prepare or mutate schema. Throwing
|
|
797
|
+
* here makes an unconverted ensureTable path fail loudly instead of silently
|
|
798
|
+
* reporting success against a missing table. The release migration wrapper
|
|
799
|
+
* is the only supported production opt-in.
|
|
800
|
+
*/
|
|
801
|
+
export function assertSchemaMutationAllowed(statement) {
|
|
802
|
+
const migrationRuntime = globalThis.__AGENT_NATIVE_MIGRATION_RUNTIME__ === true;
|
|
803
|
+
if (isProductionServerlessFunctionRuntime() &&
|
|
804
|
+
!migrationRuntime &&
|
|
805
|
+
isSchemaMutationStatement(statement)) {
|
|
806
|
+
throw new Error("Schema mutation attempted in a production serverless request. Run migrations in the release job instead.");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
768
809
|
/**
|
|
769
810
|
* postgres.js pool options tuned per runtime. idle_timeout is shortened on
|
|
770
811
|
* serverless so a thawed-but-idle instance releases its connections quickly.
|
|
@@ -774,10 +815,27 @@ export function isServerlessRuntime() {
|
|
|
774
815
|
* process, not one per consumer. See {@link neonPoolMax} for why the cap must
|
|
775
816
|
* leave room for concurrency.
|
|
776
817
|
*/
|
|
818
|
+
/**
|
|
819
|
+
* Identifies the connection in `pg_stat_activity.application_name`.
|
|
820
|
+
*
|
|
821
|
+
* Without it every backend reports `pgbouncer` and a runaway query is
|
|
822
|
+
* anonymous. A 58 MB `SELECT id, config FROM dashboards` ran 20-wide against
|
|
823
|
+
* production and could not be traced to a caller: it appears nowhere in the
|
|
824
|
+
* repo or any built bundle, and `pg_stat_statements` is not installed. This is
|
|
825
|
+
* the cheapest thing that would have named it.
|
|
826
|
+
*/
|
|
827
|
+
function poolApplicationName() {
|
|
828
|
+
const site = process.env.SITE_NAME ??
|
|
829
|
+
process.env.NETLIFY_SITE_NAME ??
|
|
830
|
+
process.env.AGENT_NATIVE_APP_NAME ??
|
|
831
|
+
"app";
|
|
832
|
+
return `agent-native:${site}`.slice(0, 63);
|
|
833
|
+
}
|
|
777
834
|
export function pgPoolOptions(url) {
|
|
778
835
|
const serverless = isServerlessRuntime();
|
|
779
836
|
return {
|
|
780
837
|
onnotice: () => { },
|
|
838
|
+
connection: { application_name: poolApplicationName() },
|
|
781
839
|
max: serverless ? serverlessPoolMax() : 20,
|
|
782
840
|
idle_timeout: serverless ? 20 : 240,
|
|
783
841
|
max_lifetime: 60 * 30,
|
|
@@ -785,6 +843,7 @@ export function pgPoolOptions(url) {
|
|
|
785
843
|
...(serverless
|
|
786
844
|
? {
|
|
787
845
|
connection: {
|
|
846
|
+
application_name: poolApplicationName(),
|
|
788
847
|
idle_in_transaction_session_timeout: 30_000,
|
|
789
848
|
},
|
|
790
849
|
}
|
|
@@ -1504,7 +1563,36 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
|
|
|
1504
1563
|
};
|
|
1505
1564
|
}
|
|
1506
1565
|
export async function createDbExec(config = {}) {
|
|
1507
|
-
|
|
1566
|
+
const exec = await createDbExecInternal(config, false);
|
|
1567
|
+
return guardSchemaMutations(exec);
|
|
1568
|
+
}
|
|
1569
|
+
function guardSchemaMutations(exec) {
|
|
1570
|
+
const guarded = {
|
|
1571
|
+
async execute(statement) {
|
|
1572
|
+
assertSchemaMutationAllowed(statement);
|
|
1573
|
+
return exec.execute(statement);
|
|
1574
|
+
},
|
|
1575
|
+
};
|
|
1576
|
+
if (exec.atomicBatch) {
|
|
1577
|
+
guarded.atomicBatch = async (statements) => {
|
|
1578
|
+
for (const statement of statements) {
|
|
1579
|
+
assertSchemaMutationAllowed(statement);
|
|
1580
|
+
}
|
|
1581
|
+
return exec.atomicBatch(statements);
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
if (exec.transaction) {
|
|
1585
|
+
guarded.transaction = (fn) => exec.transaction((tx) => fn({
|
|
1586
|
+
...tx,
|
|
1587
|
+
execute: async (statement) => {
|
|
1588
|
+
assertSchemaMutationAllowed(statement);
|
|
1589
|
+
return tx.execute(statement);
|
|
1590
|
+
},
|
|
1591
|
+
}));
|
|
1592
|
+
}
|
|
1593
|
+
if (exec.close)
|
|
1594
|
+
guarded.close = () => exec.close();
|
|
1595
|
+
return guarded;
|
|
1508
1596
|
}
|
|
1509
1597
|
async function initClient() {
|
|
1510
1598
|
if (_exec)
|
|
@@ -1561,6 +1649,7 @@ export function getDbExec() {
|
|
|
1561
1649
|
return sql;
|
|
1562
1650
|
}
|
|
1563
1651
|
async function execAnnotated(s) {
|
|
1652
|
+
assertSchemaMutationAllowed(s);
|
|
1564
1653
|
try {
|
|
1565
1654
|
return await _exec.execute(sanitize(s));
|
|
1566
1655
|
}
|
|
@@ -1571,6 +1660,7 @@ export function getDbExec() {
|
|
|
1571
1660
|
// Return a proxy that lazy-inits on first call
|
|
1572
1661
|
const proxy = {
|
|
1573
1662
|
async execute(sql) {
|
|
1663
|
+
assertSchemaMutationAllowed(sql);
|
|
1574
1664
|
if (!_initPromise)
|
|
1575
1665
|
_initPromise = initClient();
|
|
1576
1666
|
try {
|
|
@@ -1588,11 +1678,19 @@ export function getDbExec() {
|
|
|
1588
1678
|
const wrapper = {
|
|
1589
1679
|
execute: (s) => execAnnotated(s),
|
|
1590
1680
|
atomicBatch: _exec.atomicBatch
|
|
1591
|
-
?
|
|
1681
|
+
? async (statements) => {
|
|
1682
|
+
for (const statement of statements) {
|
|
1683
|
+
assertSchemaMutationAllowed(statement);
|
|
1684
|
+
}
|
|
1685
|
+
return _exec.atomicBatch(statements.map((s) => sanitize(s)));
|
|
1686
|
+
}
|
|
1592
1687
|
: undefined,
|
|
1593
1688
|
transaction: _exec.transaction
|
|
1594
1689
|
? (fn) => _exec.transaction((tx) => fn({
|
|
1595
|
-
execute: (s) =>
|
|
1690
|
+
execute: (s) => {
|
|
1691
|
+
assertSchemaMutationAllowed(s);
|
|
1692
|
+
return tx.execute(sanitize(s));
|
|
1693
|
+
},
|
|
1596
1694
|
transaction: tx.transaction,
|
|
1597
1695
|
}))
|
|
1598
1696
|
: undefined,
|
|
@@ -1614,11 +1712,19 @@ export function getDbExec() {
|
|
|
1614
1712
|
const wrapper = {
|
|
1615
1713
|
execute: (s) => execAnnotated(s),
|
|
1616
1714
|
atomicBatch: _exec.atomicBatch
|
|
1617
|
-
?
|
|
1715
|
+
? async (statements) => {
|
|
1716
|
+
for (const statement of statements) {
|
|
1717
|
+
assertSchemaMutationAllowed(statement);
|
|
1718
|
+
}
|
|
1719
|
+
return _exec.atomicBatch(statements.map((s) => sanitize(s)));
|
|
1720
|
+
}
|
|
1618
1721
|
: undefined,
|
|
1619
1722
|
transaction: _exec.transaction
|
|
1620
1723
|
? (innerFn) => _exec.transaction((tx) => innerFn({
|
|
1621
|
-
execute: (s) =>
|
|
1724
|
+
execute: (s) => {
|
|
1725
|
+
assertSchemaMutationAllowed(s);
|
|
1726
|
+
return tx.execute(sanitize(s));
|
|
1727
|
+
},
|
|
1622
1728
|
transaction: tx.transaction,
|
|
1623
1729
|
}))
|
|
1624
1730
|
: undefined,
|
|
@@ -1626,7 +1732,10 @@ export function getDbExec() {
|
|
|
1626
1732
|
Object.assign(proxy, wrapper);
|
|
1627
1733
|
if (_exec.transaction) {
|
|
1628
1734
|
return _exec.transaction((tx) => fn({
|
|
1629
|
-
execute: (s) =>
|
|
1735
|
+
execute: (s) => {
|
|
1736
|
+
assertSchemaMutationAllowed(s);
|
|
1737
|
+
return tx.execute(sanitize(s));
|
|
1738
|
+
},
|
|
1630
1739
|
transaction: tx.transaction,
|
|
1631
1740
|
}));
|
|
1632
1741
|
}
|
|
@@ -1636,6 +1745,9 @@ export function getDbExec() {
|
|
|
1636
1745
|
return explicitTransaction(wrapper.execute)(fn);
|
|
1637
1746
|
},
|
|
1638
1747
|
async atomicBatch(statements) {
|
|
1748
|
+
for (const statement of statements) {
|
|
1749
|
+
assertSchemaMutationAllowed(statement);
|
|
1750
|
+
}
|
|
1639
1751
|
if (!_initPromise)
|
|
1640
1752
|
_initPromise = initClient();
|
|
1641
1753
|
try {
|
|
@@ -1649,7 +1761,12 @@ export function getDbExec() {
|
|
|
1649
1761
|
if (!_exec.atomicBatch) {
|
|
1650
1762
|
throw new Error("This database does not support atomic batches.");
|
|
1651
1763
|
}
|
|
1652
|
-
const batch =
|
|
1764
|
+
const batch = async (items) => {
|
|
1765
|
+
for (const item of items) {
|
|
1766
|
+
assertSchemaMutationAllowed(item);
|
|
1767
|
+
}
|
|
1768
|
+
return _exec.atomicBatch(items.map((item) => sanitize(item)));
|
|
1769
|
+
};
|
|
1653
1770
|
Object.assign(proxy, { atomicBatch: batch });
|
|
1654
1771
|
return batch(statements);
|
|
1655
1772
|
},
|
package/dist/db/ddl-guard.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* (local dev) there is no such lock problem, so callers keep their existing
|
|
29
29
|
* behaviour there.
|
|
30
30
|
*/
|
|
31
|
-
import { isPostgres,
|
|
31
|
+
import { getDbExec, isPostgres, isProductionServerlessFunctionRuntime, } from "./client.js";
|
|
32
32
|
const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
33
33
|
/**
|
|
34
34
|
* Skip on-demand schema DDL entirely. For deployments that run real migrations,
|
|
@@ -45,6 +45,8 @@ const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
45
45
|
* down the DDL path this flag exists to avoid.
|
|
46
46
|
*/
|
|
47
47
|
function schemaEnsureDisabled() {
|
|
48
|
+
if (isProductionServerlessFunctionRuntime())
|
|
49
|
+
return true;
|
|
48
50
|
const raw = process.env.AGENT_NATIVE_SKIP_ENSURE_TABLES?.trim();
|
|
49
51
|
return !!raw && ["1", "true", "yes", "on"].includes(raw.toLowerCase());
|
|
50
52
|
}
|
|
@@ -67,6 +67,8 @@ export interface EnsureAdditiveColumnsOptions {
|
|
|
67
67
|
logger?: EnsureAdditiveColumnsLogger;
|
|
68
68
|
}
|
|
69
69
|
export interface EnsureAdditiveColumnsResult {
|
|
70
|
+
/** Whether this invocation actually inspected the live schema. */
|
|
71
|
+
mode: "checked" | "skipped-serverless";
|
|
70
72
|
/** `"table.column"` entries that were successfully added. */
|
|
71
73
|
applied: string[];
|
|
72
74
|
/** Declared columns that were intentionally left unpatched, with why. */
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
* unreadable) must never crash boot — the caller decides whether/how to
|
|
55
55
|
* surface `errors` in the returned summary.
|
|
56
56
|
*/
|
|
57
|
-
import { isPostgres } from "./client.js";
|
|
57
|
+
import { isPostgres, isProductionServerlessFunctionRuntime, } from "./client.js";
|
|
58
58
|
const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
59
59
|
const defaultLogger = {
|
|
60
60
|
info: (message) => console.log(message),
|
|
@@ -62,7 +62,7 @@ const defaultLogger = {
|
|
|
62
62
|
error: (message) => console.error(message),
|
|
63
63
|
};
|
|
64
64
|
function emptyResult() {
|
|
65
|
-
return { applied: [], skipped: [], errors: [] };
|
|
65
|
+
return { mode: "checked", applied: [], skipped: [], errors: [] };
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
68
|
* Resolve `getTableConfig` for the dialect currently in effect. Both
|
|
@@ -246,6 +246,9 @@ function describeSchemaDriftError(err) {
|
|
|
246
246
|
*/
|
|
247
247
|
export async function ensureAdditiveColumns(options) {
|
|
248
248
|
const { db, tables, logger = defaultLogger } = options;
|
|
249
|
+
if (isProductionServerlessFunctionRuntime()) {
|
|
250
|
+
return { ...emptyResult(), mode: "skipped-serverless" };
|
|
251
|
+
}
|
|
249
252
|
const result = emptyResult();
|
|
250
253
|
let getTableConfig;
|
|
251
254
|
try {
|
package/dist/db/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function createDb(config: DbConfig): Promise<(import("drizzle-orm
|
|
|
21
21
|
})>;
|
|
22
22
|
export type DrizzleDb = Awaited<ReturnType<typeof createDb>>;
|
|
23
23
|
export { createGetDb } from "./create-get-db.js";
|
|
24
|
-
export { deferMigration, MIGRATION_DEFERRED, runMigrations, type MigrationEntry, type MigrationRunResult, type MigrationSql, } from "./migrations.js";
|
|
25
|
-
export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, intType, closeDbExec, type DbExec, type DbExecConfig, type DbExecQuery, type DbExecStatement, type Dialect, } from "./client.js";
|
|
24
|
+
export { deferMigration, MIGRATION_DEFERRED, runMigrations, withMigrationRuntime, type MigrationEntry, type MigrationRunResult, type MigrationSql, } from "./migrations.js";
|
|
25
|
+
export { getDbExec, createDbExec, getDatabaseUrl, 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
|
@@ -20,7 +20,7 @@ export async function createDb(config) {
|
|
|
20
20
|
throw new Error(`Unsupported driver: ${config.driver}`);
|
|
21
21
|
}
|
|
22
22
|
export { createGetDb } from "./create-get-db.js";
|
|
23
|
-
export { deferMigration, MIGRATION_DEFERRED, runMigrations, } from "./migrations.js";
|
|
24
|
-
export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, intType, closeDbExec, } from "./client.js";
|
|
23
|
+
export { deferMigration, MIGRATION_DEFERRED, runMigrations, withMigrationRuntime, } from "./migrations.js";
|
|
24
|
+
export { getDbExec, createDbExec, getDatabaseUrl, 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";
|