@lowdefy/server 5.5.1 → 6.0.0

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 (62) hide show
  1. package/{pages/_app.js → client/App.jsx} +26 -34
  2. package/client/Page.jsx +109 -0
  3. package/client/main.jsx +39 -0
  4. package/lib/build/app.js +8 -1
  5. package/lib/build/appMeta.js +8 -1
  6. package/lib/build/auth.js +8 -1
  7. package/lib/build/config.js +8 -1
  8. package/lib/build/i18n.js +8 -1
  9. package/lib/build/logger.js +8 -1
  10. package/lib/build/theme.js +8 -1
  11. package/lib/client/auth/{Auth.js → Auth.jsx} +10 -2
  12. package/lib/client/auth/{AuthConfigured.js → AuthConfigured.jsx} +22 -10
  13. package/lib/client/sentry/captureSentryError.js +1 -1
  14. package/lib/client/sentry/initSentryClient.js +1 -1
  15. package/lib/client/sentry/setSentryUser.js +1 -1
  16. package/lib/server/apiWrapper.js +2 -9
  17. package/lib/server/auth/{getAuthOptions.js → getAuthConfig.js} +5 -4
  18. package/lib/server/auth/{getServerSession.js → session.js} +9 -6
  19. package/lib/server/auth/strategies.js +47 -0
  20. package/lib/server/log/createHandleError.js +15 -2
  21. package/lib/server/log/createLogger.js +18 -1
  22. package/lib/server/log/logRequest.js +41 -45
  23. package/lib/server/sentry/captureSentryError.js +1 -1
  24. package/lib/server/sentry/initSentry.js +6 -4
  25. package/lib/server/sentry/setSentryUser.js +1 -1
  26. package/lowdefy/createCustomPluginTypesMap.mjs +1 -0
  27. package/package.json +52 -39
  28. package/package.original.json +54 -41
  29. package/src/app.js +140 -0
  30. package/src/html/getAssets.js +45 -0
  31. package/src/html/renderPage.js +80 -0
  32. package/src/html/template.js +95 -0
  33. package/src/index.js +85 -0
  34. package/src/lib/getPathSegments.js +25 -0
  35. package/src/lib/safeScriptJson.js +38 -0
  36. package/src/middleware/apiContext.js +135 -0
  37. package/src/middleware/errorHandler.js +54 -0
  38. package/src/middleware/sentry.js +33 -0
  39. package/{pages/api/agent/[...path].js → src/routes/agent.js} +20 -42
  40. package/src/routes/apiPage.js +37 -0
  41. package/src/routes/auth.js +38 -0
  42. package/{pages/api/client-error.js → src/routes/clientError.js} +14 -16
  43. package/src/routes/cron.js +44 -0
  44. package/src/routes/cronForward.js +47 -0
  45. package/src/routes/detached.js +47 -0
  46. package/src/routes/endpoints.js +71 -0
  47. package/src/routes/mcp.js +34 -0
  48. package/{pages/api/request/[...path].js → src/routes/request.js} +10 -10
  49. package/src/routes/usage.js +46 -0
  50. package/src/routes/websocket.js +46 -0
  51. package/vite.config.js +61 -0
  52. package/.eslintrc.yaml +0 -1
  53. package/lib/client/Page.js +0 -56
  54. package/lib/server/serverSidePropsWrapper.js +0 -66
  55. package/next.config.js +0 -34
  56. package/pages/404.js +0 -49
  57. package/pages/[[...pageId]].js +0 -85
  58. package/pages/_document.js +0 -106
  59. package/pages/api/auth/[...nextauth].js +0 -37
  60. package/pages/api/endpoints/[...endpointId].js +0 -32
  61. package/pages/api/usage.js +0 -67
  62. /package/{postcss.config.js → postcss.config.cjs} +0 -0
@@ -14,13 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- // CSS layer order — MUST be the first CSS import. Next.js treats this as critical
18
- // CSS that loads before hydration, locking the cascade priority (antd > base/preflight)
19
- // before antd's StyleProvider injects @layer antd {} at runtime.
20
- import '../build/layer-order.css';
21
-
22
17
  import React, { useCallback, useRef } from 'react';
23
- import dynamic from 'next/dynamic';
24
18
 
25
19
  import { ErrorBoundary } from '@lowdefy/block-utils';
26
20
  import { useDarkMode, useLocale } from '@lowdefy/client';
@@ -28,23 +22,20 @@ import { StyleProvider } from '@ant-design/cssinjs';
28
22
  import { App as AntdApp, theme as antdTheme } from 'antd';
29
23
  import { XProvider } from '@ant-design/x';
30
24
 
25
+ import { serializer } from '@lowdefy/helpers';
26
+
31
27
  import antdLocaleLoaders from '../build/i18n/antdLocales.js';
32
28
  import antdXLocaleLoaders from '../build/i18n/antdXLocales.js';
33
29
  import dayjsLocaleMap from '../build/i18n/dayjsLocales.js';
34
- import Auth from '../lib/client/auth/Auth.js';
30
+ import rawLoggerConfig from '../build/logger.json';
31
+ import Auth from '../lib/client/auth/Auth.jsx';
35
32
  import createLogUsage from '../lib/client/createLogUsage.js';
36
33
  import initSentryClient from '../lib/client/sentry/initSentryClient.js';
37
- import loggerConfig from '../lib/build/logger.js';
38
34
  import setSentryUser from '../lib/client/sentry/setSentryUser.js';
35
+ import Page from './Page.jsx';
39
36
 
40
- // Must be in _app due to next specifications.
41
- import '../build/globals.css';
42
-
43
- // Initialize Sentry client once on module load
44
- initSentryClient({
45
- sentryDsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
46
- sentryConfig: loggerConfig.sentry,
47
- });
37
+ // Deserialize to restore arrays (e.g. sentry.userFields) from ~arr markers.
38
+ const loggerConfig = serializer.deserialize(rawLoggerConfig);
48
39
 
49
40
  function ThemeTokenResolver({ lowdefyRef, children }) {
50
41
  const { token } = antdTheme.useToken();
@@ -55,7 +46,16 @@ function ThemeTokenResolver({ lowdefyRef, children }) {
55
46
  return children;
56
47
  }
57
48
 
58
- function App({ Component, pageProps: { session, rootConfig, pageConfig } }) {
49
+ function App({ config }) {
50
+ const { rootConfig, session } = config;
51
+
52
+ // Sentry DSN arrives at runtime via the embedded config (the server reads
53
+ // SENTRY_DSN per process) — no build-time env inlining.
54
+ initSentryClient({
55
+ sentryDsn: config.sentryDsn,
56
+ sentryConfig: loggerConfig.sentry,
57
+ });
58
+
59
59
  const usageDataRef = useRef({});
60
60
  const lowdefyRef = useRef({ eventCallback: createLogUsage({ usageDataRef }) });
61
61
  if (rootConfig?.theme) {
@@ -67,7 +67,11 @@ function App({ Component, pageProps: { session, rootConfig, pageConfig } }) {
67
67
  configDarkMode: lowdefyRef.current.theme?.darkMode,
68
68
  });
69
69
 
70
- const { active: activeLocale, antdLocale, antdXLocale } = useLocale({
70
+ const {
71
+ active: activeLocale,
72
+ antdLocale,
73
+ antdXLocale,
74
+ } = useLocale({
71
75
  i18n: rootConfig?.i18n,
72
76
  antdLocaleLoaders,
73
77
  antdXLocaleLoaders,
@@ -98,9 +102,8 @@ function App({ Component, pageProps: { session, rootConfig, pageConfig } }) {
98
102
  // gives X components (Bubble, Sender, Conversations, ...) their built-in strings
99
103
  // alongside antd's. antd X ships only en_US + zh_CN; other locales fall back
100
104
  // to en_US for X-native strings.
101
- const mergedLocale = antdLocale || antdXLocale
102
- ? { ...(antdLocale ?? {}), ...(antdXLocale ?? {}) }
103
- : undefined;
105
+ const mergedLocale =
106
+ antdLocale || antdXLocale ? { ...(antdLocale ?? {}), ...(antdXLocale ?? {}) } : undefined;
104
107
 
105
108
  return (
106
109
  <StyleProvider layer>
@@ -131,14 +134,7 @@ function App({ Component, pageProps: { session, rootConfig, pageConfig } }) {
131
134
  user: auth.session,
132
135
  sentryConfig: loggerConfig.sentry,
133
136
  });
134
- return (
135
- <Component
136
- auth={auth}
137
- lowdefy={lowdefyRef.current}
138
- rootConfig={rootConfig}
139
- pageConfig={pageConfig}
140
- />
141
- );
137
+ return <Page auth={auth} config={config} lowdefy={lowdefyRef.current} />;
142
138
  }}
143
139
  </Auth>
144
140
  </ErrorBoundary>
@@ -149,8 +145,4 @@ function App({ Component, pageProps: { session, rootConfig, pageConfig } }) {
149
145
  );
150
146
  }
151
147
 
152
- const DynamicApp = dynamic(() => Promise.resolve(App), {
153
- ssr: false,
154
- });
155
-
156
- export default DynamicApp;
148
+ export default App;
@@ -0,0 +1,109 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import React, { useEffect, useRef, useState } from 'react';
18
+
19
+ import Client from '@lowdefy/client';
20
+ import createRouter from '@lowdefy/client/adapters/createRouter.js';
21
+ import createLinkComponent from '@lowdefy/client/adapters/Link.js';
22
+ import { createUrl } from '@lowdefy/client/adapters/url.js';
23
+ import Head from '@lowdefy/client/adapters/Head.js';
24
+
25
+ import actions from '../build/plugins/actions.js';
26
+ import blockMetas from '../build/plugins/blockMetas.json';
27
+ import blocks from '../build/plugins/blocks.js';
28
+ import icons from '../build/plugins/icons.js';
29
+ import operators from '../build/plugins/operators/client.js';
30
+ import jsMap from '../build/plugins/operators/clientJsMap.js';
31
+
32
+ // Replaces lib/client/Page.js. The first page renders from the config
33
+ // embedded in the HTML; SPA navigations fetch /api/page/* and swap pageConfig.
34
+ function Page({ auth, config, lowdefy }) {
35
+ const [pageConfig, setPageConfig] = useState(config.pageConfig);
36
+
37
+ const routerRef = useRef(null);
38
+ if (!routerRef.current) {
39
+ const router = createRouter({ basePath: config.basePath ?? '', window });
40
+ routerRef.current = {
41
+ router,
42
+ Link: createLinkComponent({ router }),
43
+ };
44
+ }
45
+ const { router, Link } = routerRef.current;
46
+
47
+ // Temporary sequence guard for the superseded-navigation race: a slow
48
+ // response for an abandoned navigation must not paint over a newer one.
49
+ // Remove when the loader-based lifecycle replaces these fetch paths.
50
+ const latestNavRef = useRef(0);
51
+
52
+ useEffect(() => {
53
+ const unsubscribe = router.subscribe(async ({ pageId }) => {
54
+ const token = ++latestNavRef.current;
55
+ const targetPageId = pageId ?? config.rootConfig.home.pageId;
56
+ try {
57
+ // Forward the current query string so server-side Dynamic block
58
+ // resolution sees the same urlQuery as an initial HTML load.
59
+ const res = await fetch(
60
+ `${router.basePath}/api/page/${targetPageId}${window.location.search}`
61
+ );
62
+ if (!res.ok) {
63
+ if (token !== latestNavRef.current) return;
64
+ if (targetPageId !== '404') {
65
+ router.replace({ pathname: '/404' });
66
+ }
67
+ return;
68
+ }
69
+ const { pageConfig: nextPageConfig } = await res.json();
70
+ if (token !== latestNavRef.current) return;
71
+ setPageConfig(nextPageConfig);
72
+ } catch (error) {
73
+ // Network failure on SPA navigation — fall back to a full page load.
74
+ if (token !== latestNavRef.current) return;
75
+ window.location.assign(
76
+ createUrl({
77
+ basePath: router.basePath,
78
+ pathname: targetPageId === config.rootConfig.home.pageId ? '/' : `/${targetPageId}`,
79
+ })
80
+ );
81
+ }
82
+ });
83
+ return unsubscribe;
84
+ }, []);
85
+
86
+ return (
87
+ <Client
88
+ auth={auth}
89
+ Components={{ Head, Link }}
90
+ config={{
91
+ pageConfig,
92
+ rootConfig: config.rootConfig,
93
+ }}
94
+ jsMap={jsMap}
95
+ lowdefy={lowdefy}
96
+ router={router}
97
+ types={{
98
+ actions,
99
+ blockMetas,
100
+ blocks,
101
+ icons,
102
+ operators,
103
+ }}
104
+ window={window}
105
+ />
106
+ );
107
+ }
108
+
109
+ export default Page;
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ // CSS layer order — MUST be the first CSS import. This locks the cascade
18
+ // priority (antd > base/preflight) before antd's StyleProvider injects
19
+ // @layer antd {} at runtime.
20
+ import '../build/layer-order.css';
21
+
22
+ import React from 'react';
23
+ import { createRoot } from 'react-dom/client';
24
+
25
+ import App from './App.jsx';
26
+
27
+ import '../build/globals.css';
28
+
29
+ const config = JSON.parse(document.getElementById('__LOWDEFY_CONFIG__').textContent);
30
+ const container = document.getElementById('root');
31
+
32
+ // Keep one React root across Vite HMR updates — re-running createRoot on a
33
+ // container that already has a root is an error.
34
+ const root = import.meta.hot?.data.root ?? createRoot(container);
35
+ if (import.meta.hot) {
36
+ import.meta.hot.data.root = root;
37
+ }
38
+
39
+ root.render(<App config={config} />);
package/lib/build/app.js CHANGED
@@ -13,7 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
+
17
+ // Build artifacts are read from disk because the Hono server runs as
18
+ // unbundled Node.js ESM — JSON imports would need import attributes, and
19
+ // client code imports the build JSON directly through Vite instead.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
16
22
  import { serializer } from '@lowdefy/helpers';
17
- import raw from '../../build/app.json';
23
+
24
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'build/app.json'), 'utf8'));
18
25
 
19
26
  export default serializer.deserialize(raw);
@@ -13,7 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
+
17
+ // Build artifacts are read from disk because the Hono server runs as
18
+ // unbundled Node.js ESM — JSON imports would need import attributes, and
19
+ // client code imports the build JSON directly through Vite instead.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
16
22
  import { serializer } from '@lowdefy/helpers';
17
- import raw from '../../build/appMeta.json';
23
+
24
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'build/appMeta.json'), 'utf8'));
18
25
 
19
26
  export default serializer.deserialize(raw);
package/lib/build/auth.js CHANGED
@@ -13,7 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
+
17
+ // Build artifacts are read from disk because the Hono server runs as
18
+ // unbundled Node.js ESM — JSON imports would need import attributes, and
19
+ // client code imports the build JSON directly through Vite instead.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
16
22
  import { serializer } from '@lowdefy/helpers';
17
- import raw from '../../build/auth.json';
23
+
24
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'build/auth.json'), 'utf8'));
18
25
 
19
26
  export default serializer.deserialize(raw);
@@ -13,7 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
+
17
+ // Build artifacts are read from disk because the Hono server runs as
18
+ // unbundled Node.js ESM — JSON imports would need import attributes, and
19
+ // client code imports the build JSON directly through Vite instead.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
16
22
  import { serializer } from '@lowdefy/helpers';
17
- import raw from '../../build/config.json';
23
+
24
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'build/config.json'), 'utf8'));
18
25
 
19
26
  export default serializer.deserialize(raw);
package/lib/build/i18n.js CHANGED
@@ -13,7 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
+
17
+ // Build artifacts are read from disk because the Hono server runs as
18
+ // unbundled Node.js ESM — JSON imports would need import attributes, and
19
+ // client code imports the build JSON directly through Vite instead.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
16
22
  import { serializer } from '@lowdefy/helpers';
17
- import raw from '../../build/i18n.json';
23
+
24
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'build/i18n.json'), 'utf8'));
18
25
 
19
26
  export default serializer.deserialize(raw);
@@ -13,7 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
+
17
+ // Build artifacts are read from disk because the Hono server runs as
18
+ // unbundled Node.js ESM — JSON imports would need import attributes, and
19
+ // client code imports the build JSON directly through Vite instead.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
16
22
  import { serializer } from '@lowdefy/helpers';
17
- import raw from '../../build/logger.json';
23
+
24
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'build/logger.json'), 'utf8'));
18
25
 
19
26
  export default serializer.deserialize(raw);
@@ -13,7 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
+
17
+ // Build artifacts are read from disk because the Hono server runs as
18
+ // unbundled Node.js ESM — JSON imports would need import attributes, and
19
+ // client code imports the build JSON directly through Vite instead.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
16
22
  import { serializer } from '@lowdefy/helpers';
17
- import raw from '../../build/theme.json';
23
+
24
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'build/theme.json'), 'utf8'));
18
25
 
19
26
  export default serializer.deserialize(raw);
@@ -17,10 +17,18 @@
17
17
  /* eslint-disable react/jsx-props-no-spreading */
18
18
 
19
19
  import React from 'react';
20
- import AuthConfigured from './AuthConfigured.js';
20
+ import AuthConfigured from './AuthConfigured.jsx';
21
21
  import AuthNotConfigured from './AuthNotConfigured.js';
22
22
 
23
- import authConfig from '../../build/auth.js';
23
+ import { serializer } from '@lowdefy/helpers';
24
+
25
+ // Client code imports the build JSON directly — Vite handles JSON imports;
26
+ // the lib/build/*.js wrappers are server-only (they read from disk).
27
+ // Deserialize to restore arrays from their ~arr build markers (providers
28
+ // must be a real array for single-provider inference in createAuthMethods).
29
+ import rawAuthConfig from '../../../build/auth.json';
30
+
31
+ const authConfig = serializer.deserialize(rawAuthConfig);
24
32
 
25
33
  function Auth({ children, session }) {
26
34
  if (authConfig.configured === true) {
@@ -17,9 +17,26 @@
17
17
  /* eslint-disable react/jsx-props-no-spreading */
18
18
 
19
19
  import React, { useEffect, useRef } from 'react';
20
- import { getSession, SessionProvider, signIn, signOut, useSession } from 'next-auth/react';
20
+ import {
21
+ authConfigManager,
22
+ getSession,
23
+ SessionProvider,
24
+ signIn,
25
+ signOut,
26
+ useSession,
27
+ } from '@hono/auth-js/react';
21
28
 
22
- import lowdefyConfig from '../../build/config.js';
29
+ import { serializer } from '@lowdefy/helpers';
30
+
31
+ import rawLowdefyConfig from '../../../build/config.json';
32
+
33
+ const lowdefyConfig = serializer.deserialize(rawLowdefyConfig);
34
+
35
+ // @hono/auth-js/react configures its fetch paths through a module-level
36
+ // manager instead of SessionProvider props.
37
+ if (lowdefyConfig.basePath) {
38
+ authConfigManager.setConfig({ basePath: `${lowdefyConfig.basePath}/api/auth` });
39
+ }
23
40
 
24
41
  function Session({ children }) {
25
42
  const wasAuthenticated = useRef(false);
@@ -32,9 +49,8 @@ function Session({ children }) {
32
49
  }
33
50
  }, [status]);
34
51
 
35
- // If session is passed to SessionProvider from getServerSideProps
36
- // we won't have a loading state here.
37
- // But 404 uses getStaticProps so we have this for 404.
52
+ // If session is passed to SessionProvider from the server-rendered config
53
+ // we won't have a loading state here, but unauthenticated first loads do.
38
54
  if (status === 'loading') {
39
55
  return '';
40
56
  }
@@ -43,12 +59,8 @@ function Session({ children }) {
43
59
 
44
60
  function AuthConfigured({ authConfig, children, serverSession }) {
45
61
  const auth = { authConfig, getSession, signIn, signOut };
46
- let basePath = lowdefyConfig.basePath;
47
- if (basePath) {
48
- basePath = `${basePath}/api/auth`;
49
- }
50
62
  return (
51
- <SessionProvider session={serverSession} basePath={basePath}>
63
+ <SessionProvider session={serverSession}>
52
64
  <Session>
53
65
  {(session) => {
54
66
  auth.session = session;
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import * as Sentry from '@sentry/nextjs';
17
+ import * as Sentry from '@sentry/browser';
18
18
 
19
19
  function captureSentryError({ error, pageId, blockId, configLocation }) {
20
20
  const tags = {};
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import * as Sentry from '@sentry/nextjs';
17
+ import * as Sentry from '@sentry/browser';
18
18
 
19
19
  let initialized = false;
20
20
 
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import * as Sentry from '@sentry/nextjs';
17
+ import * as Sentry from '@sentry/browser';
18
18
 
19
19
  function setSentryUser({ user, sentryConfig }) {
20
20
  // No-op if no user
@@ -15,9 +15,8 @@
15
15
  */
16
16
 
17
17
  import path from 'path';
18
- import { createApiContext } from '@lowdefy/api';
18
+ import { createApiContext, redactErrorResponse } from '@lowdefy/api';
19
19
  import { getSecretsFromEnv } from '@lowdefy/node-utils';
20
- import { serializer } from '@lowdefy/helpers';
21
20
  import { v4 as uuid } from 'uuid';
22
21
 
23
22
  import agents from '../../build/plugins/agents.js';
@@ -80,13 +79,7 @@ function apiWrapper(handler) {
80
79
  return response;
81
80
  } catch (error) {
82
81
  await context.handleError(error);
83
- const serialized = serializer.serialize(error);
84
- if (serialized?.['~e']) {
85
- delete serialized['~e'].received;
86
- delete serialized['~e'].stack;
87
- delete serialized['~e'].configKey;
88
- }
89
- res.status(500).json(serialized);
82
+ res.status(500).json(redactErrorResponse(context, error));
90
83
  }
91
84
  };
92
85
  }
@@ -14,17 +14,18 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { getNextAuthConfig } from '@lowdefy/api';
17
+ import { getAuthConfig as getApiAuthConfig } from '@lowdefy/api';
18
18
  import { getSecretsFromEnv } from '@lowdefy/node-utils';
19
19
 
20
20
  import adapters from '../../../build/plugins/auth/adapters.js';
21
+ import appMeta from '../../build/appMeta.js';
21
22
  import authJson from '../../build/auth.js';
22
23
  import callbacks from '../../../build/plugins/auth/callbacks.js';
23
24
  import events from '../../../build/plugins/auth/events.js';
24
25
  import providers from '../../../build/plugins/auth/providers.js';
25
26
 
26
- function getAuthOptions({ appMeta, logger }) {
27
- return getNextAuthConfig({
27
+ function getAuthConfig({ logger }) {
28
+ return getApiAuthConfig({
28
29
  appMeta,
29
30
  authJson,
30
31
  logger,
@@ -33,4 +34,4 @@ function getAuthOptions({ appMeta, logger }) {
33
34
  });
34
35
  }
35
36
 
36
- export default getAuthOptions;
37
+ export default getAuthConfig;
@@ -14,15 +14,18 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { getServerSession as getNextAuthServerSession } from 'next-auth/next';
17
+ import { getAuthUser } from '@hono/auth-js';
18
18
 
19
19
  import authJson from '../../build/auth.js';
20
20
 
21
- function getServerSession({ authOptions, req, res }) {
22
- if (authJson.configured === true) {
23
- return getNextAuthServerSession(req, res, authOptions);
21
+ // Replaces getServerSession.js — reads the session from the Hono context
22
+ // populated by the initAuthConfig middleware.
23
+ async function getSession(c) {
24
+ if (authJson.configured !== true) {
25
+ return undefined;
24
26
  }
25
- return undefined;
27
+ const authUser = await getAuthUser(c);
28
+ return authUser?.session ?? undefined;
26
29
  }
27
30
 
28
- export default getServerSession;
31
+ export default getSession;
@@ -0,0 +1,47 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { getAuthStrategies, resolveStrategyCaller } from '@lowdefy/api';
18
+ import { getSecretsFromEnv } from '@lowdefy/node-utils';
19
+
20
+ import appMeta from '../../build/appMeta.js';
21
+ import authJson from '../../build/auth.js';
22
+ import strategies from '../../../build/plugins/auth/strategies.js';
23
+
24
+ // Secrets are process-static env, so read once at module load rather than
25
+ // on every sessionless request.
26
+ const secrets = getSecretsFromEnv();
27
+
28
+ // API auth strategies (apiKey/jwt headers) let MCP and service clients that
29
+ // cannot hold a session cookie authenticate. Tried only when no session
30
+ // resolved; a match is synthesized as session.user so createAuthorize and
31
+ // _user work unchanged.
32
+ async function getStrategyCaller(c, logger) {
33
+ const authStrategies = getAuthStrategies({
34
+ appMeta,
35
+ authJson,
36
+ logger,
37
+ plugins: { strategies },
38
+ secrets,
39
+ });
40
+ return resolveStrategyCaller({
41
+ headers: c.req.raw.headers,
42
+ logger,
43
+ strategies: authStrategies,
44
+ });
45
+ }
46
+
47
+ export default getStrategyCaller;
@@ -59,7 +59,20 @@ function getEventType(error) {
59
59
  function createHandleError({ context }) {
60
60
  return async function handleError(error) {
61
61
  try {
62
- const { headers = {}, user = {} } = context;
62
+ // Set first, not after the log call: this function is the server's error
63
+ // sink, so reaching it is what makes the error already-logged, and the catch
64
+ // below guarantees something is written even when the steps here fail. The
65
+ // client reads `handled` to decide whether to POST the error to
66
+ // /api/client-error, so a later throw skipping this assignment would have it
67
+ // logged a second time. Not keyed on `source`, which a LowdefyInternalError
68
+ // never gets - location resolution is skipped for it below.
69
+ error.handled = true;
70
+
71
+ // `?? {}` rather than a destructuring default: context.user is null (not
72
+ // undefined) for an anonymous caller and for every system context, so a
73
+ // default would not fire and reading user.id below would throw.
74
+ const headers = context.headers ?? {};
75
+ const user = context.user ?? {};
63
76
  const eventType = getEventType(error);
64
77
  const isServiceError = error instanceof ServiceError;
65
78
  const isLowdefyInternalError = error instanceof LowdefyInternalError;
@@ -114,7 +127,7 @@ function createHandleError({ context }) {
114
127
  host: headers.host,
115
128
  referer: headers.referer,
116
129
  // Non localhost headers
117
- 'x-forward-for': headers['x-forward-for'],
130
+ 'x-forwarded-for': headers['x-forwarded-for'],
118
131
  // Vercel headers
119
132
  'x-vercel-id': headers['x-vercel-id'],
120
133
  'x-real-ip': headers['x-real-ip'],