@lowdefy/server-dev 5.2.0 → 5.4.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.
@@ -0,0 +1,19 @@
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
+ import { serializer } from '@lowdefy/helpers';
17
+ import raw from '../../build/appMeta.json';
18
+
19
+ export default serializer.deserialize(raw);
@@ -0,0 +1,19 @@
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
+ import { serializer } from '@lowdefy/helpers';
17
+ import raw from '../../build/i18n.json';
18
+
19
+ export default serializer.deserialize(raw);
@@ -20,7 +20,10 @@ import { getSecretsFromEnv } from '@lowdefy/node-utils';
20
20
  import { serializer } from '@lowdefy/helpers';
21
21
  import { v4 as uuid } from 'uuid';
22
22
 
23
+ import agents from '../../build/plugins/agents.js';
24
+ import appMeta from '../build/appMeta.js';
23
25
  import config from '../build/config.js';
26
+ import i18nConfig from '../build/i18n.js';
24
27
  import connections from '../../build/plugins/connections.js';
25
28
  import createLogger from './log/createLogger.js';
26
29
  import fileCache from './fileCache.js';
@@ -65,12 +68,15 @@ function apiWrapper(handler) {
65
68
  const context = {
66
69
  // Important to give absolute path so Next can trace build files
67
70
  rid: uuid(),
71
+ agents,
72
+ appMeta,
68
73
  buildDirectory,
69
74
  configDirectory: process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd(),
70
75
  config,
71
76
  connections,
72
77
  fileCache,
73
78
  headers: req?.headers,
79
+ i18n: i18nConfig,
74
80
  jsMap,
75
81
  handleError: async (err) => {
76
82
  console.error(err);
@@ -22,6 +22,7 @@ import { buildPageJit, createContext, makeId } from '@lowdefy/build/dev';
22
22
  import compileCss from './compileCss.js';
23
23
  import createLogger from './log/createLogger.js';
24
24
  import PageCache from './pageCache.mjs';
25
+ import readBuildApiArtifacts from './readBuildApiArtifacts.mjs';
25
26
 
26
27
  const jitLogger = createLogger({ name: 'jit-build' });
27
28
 
@@ -93,8 +94,11 @@ function getBuildContext(buildDirectory, configDirectory) {
93
94
  const connectionIds = readJsonFile(path.join(buildDirectory, 'connectionIds.json')) ?? [];
94
95
 
95
96
  const customTypesMap = readJsonFile(path.join(buildDirectory, 'customTypesMap.json')) ?? {};
97
+ const customMessagesMap =
98
+ readJsonFile(path.join(buildDirectory, 'customMessagesMap.json')) ?? {};
96
99
 
97
100
  cachedBuildContext = createContext({
101
+ customMessagesMap,
98
102
  customTypesMap,
99
103
  directories: {
100
104
  build: buildDirectory,
@@ -125,6 +129,11 @@ function getBuildContext(buildDirectory, configDirectory) {
125
129
  Object.assign(cachedBuildContext.modules, modules);
126
130
  }
127
131
 
132
+ // Restore api endpoint configs so JIT CallAPI validation (validateCallApiRefs in
133
+ // buildPageJit) can resolve endpointIds. Without this the dev context has no
134
+ // components.api and every CallAPI action is flagged as a non-existent endpoint.
135
+ cachedBuildContext.components = { api: readBuildApiArtifacts(buildDirectory) };
136
+
128
137
  // Use the frozen icon imports from the initial build for JIT detection.
129
138
  // This represents what's actually in the Next.js bundle — not what shallowBuild
130
139
  // discovers on subsequent rebuilds (those icons aren't bundled yet).
@@ -0,0 +1,53 @@
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 fs from 'fs';
18
+ import path from 'path';
19
+ import { serializer } from '@lowdefy/helpers';
20
+
21
+ // Recursively collect every endpoint artifact under the api directory. Module
22
+ // endpoints are written to build/api/<moduleId>/<endpointId>.json because their
23
+ // scoped endpointId contains a "/" (buildModules prefixes ids with the module entry
24
+ // id), so a flat read would miss them and flag every module CallAPI as non-existent.
25
+ function readApiConfigs(directory) {
26
+ const configs = [];
27
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
28
+ const entryPath = path.join(directory, entry.name);
29
+ if (entry.isDirectory()) {
30
+ configs.push(...readApiConfigs(entryPath));
31
+ } else if (entry.name.endsWith('.json')) {
32
+ configs.push(serializer.deserialize(JSON.parse(fs.readFileSync(entryPath, 'utf8'))));
33
+ }
34
+ }
35
+ return configs;
36
+ }
37
+
38
+ // Read the endpoint artifacts persisted by writeApi (build/api/<endpointId>.json).
39
+ // The full build keeps endpoints in memory on components.api, but the dev context is
40
+ // rebuilt from disk, so getBuildContext hydrates them here. validateCallApiRefs needs
41
+ // each config's endpointId and type, both of which are present in the artifact.
42
+ function readBuildApiArtifacts(buildDirectory) {
43
+ const apiDirectory = path.join(buildDirectory, 'api');
44
+ try {
45
+ return readApiConfigs(apiDirectory);
46
+ } catch (error) {
47
+ // writeApi only creates the directory when endpoints are defined — absent means none.
48
+ if (error.code === 'ENOENT') return [];
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ export default readBuildApiArtifacts;
@@ -0,0 +1,106 @@
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 fs from 'fs';
18
+ import os from 'os';
19
+ import path from 'path';
20
+ import { serializer } from '@lowdefy/helpers';
21
+
22
+ import readBuildApiArtifacts from './readBuildApiArtifacts.mjs';
23
+
24
+ function makeBuildDir() {
25
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'ldf-read-api-'));
26
+ }
27
+
28
+ // Mirror writeApi: each endpoint is serialized to build/api/<endpointId>.json.
29
+ // Scoped module endpointIds contain a "/", so the file lands in a nested directory.
30
+ function writeEndpoint(buildDir, endpoint) {
31
+ const filePath = path.join(buildDir, 'api', `${endpoint.endpointId}.json`);
32
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
33
+ fs.writeFileSync(filePath, serializer.serializeToString(endpoint));
34
+ }
35
+
36
+ test('readBuildApiArtifacts returns [] when the api directory does not exist', () => {
37
+ const buildDir = makeBuildDir();
38
+ try {
39
+ expect(readBuildApiArtifacts(buildDir)).toEqual([]);
40
+ } finally {
41
+ fs.rmSync(buildDir, { recursive: true, force: true });
42
+ }
43
+ });
44
+
45
+ test('readBuildApiArtifacts returns [] when the api directory is empty', () => {
46
+ const buildDir = makeBuildDir();
47
+ fs.mkdirSync(path.join(buildDir, 'api'));
48
+ try {
49
+ expect(readBuildApiArtifacts(buildDir)).toEqual([]);
50
+ } finally {
51
+ fs.rmSync(buildDir, { recursive: true, force: true });
52
+ }
53
+ });
54
+
55
+ test('readBuildApiArtifacts reads and deserializes endpoint artifacts with endpointId and type', () => {
56
+ const buildDir = makeBuildDir();
57
+ writeEndpoint(buildDir, { id: 'endpoint:my_api', endpointId: 'my_api', type: 'Api' });
58
+ writeEndpoint(buildDir, {
59
+ id: 'endpoint:internal_api',
60
+ endpointId: 'internal_api',
61
+ type: 'InternalApi',
62
+ });
63
+ try {
64
+ const result = readBuildApiArtifacts(buildDir);
65
+ expect(result).toHaveLength(2);
66
+ expect(result).toEqual(
67
+ expect.arrayContaining([
68
+ expect.objectContaining({ endpointId: 'my_api', type: 'Api' }),
69
+ expect.objectContaining({ endpointId: 'internal_api', type: 'InternalApi' }),
70
+ ])
71
+ );
72
+ } finally {
73
+ fs.rmSync(buildDir, { recursive: true, force: true });
74
+ }
75
+ });
76
+
77
+ test('readBuildApiArtifacts reads scoped module endpoints from nested subdirectories', () => {
78
+ const buildDir = makeBuildDir();
79
+ // Top-level endpoint at build/api/top_api.json
80
+ writeEndpoint(buildDir, { id: 'endpoint:top_api', endpointId: 'top_api', type: 'Api' });
81
+ // Module endpoint at build/api/inviter/send-invite.json (scoped id contains "/")
82
+ writeEndpoint(buildDir, {
83
+ id: 'endpoint:inviter/send-invite',
84
+ endpointId: 'inviter/send-invite',
85
+ type: 'Api',
86
+ });
87
+ try {
88
+ const result = readBuildApiArtifacts(buildDir);
89
+ expect(result.map((c) => c.endpointId).sort()).toEqual(['inviter/send-invite', 'top_api']);
90
+ } finally {
91
+ fs.rmSync(buildDir, { recursive: true, force: true });
92
+ }
93
+ });
94
+
95
+ test('readBuildApiArtifacts ignores non-json files in the api directory', () => {
96
+ const buildDir = makeBuildDir();
97
+ writeEndpoint(buildDir, { id: 'endpoint:my_api', endpointId: 'my_api', type: 'Api' });
98
+ fs.writeFileSync(path.join(buildDir, 'api', 'notes.txt'), 'ignore me');
99
+ try {
100
+ const result = readBuildApiArtifacts(buildDir);
101
+ expect(result).toHaveLength(1);
102
+ expect(result[0]).toEqual(expect.objectContaining({ endpointId: 'my_api', type: 'Api' }));
103
+ } finally {
104
+ fs.rmSync(buildDir, { recursive: true, force: true });
105
+ }
106
+ });
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import { shallowBuild } from '@lowdefy/build/dev';
18
+ import createCustomPluginMessagesMap from '../utils/createCustomPluginMessagesMap.mjs';
18
19
  import createCustomPluginTypesMap from '../utils/createCustomPluginTypesMap.mjs';
19
20
 
20
21
  function formatDuration(ms) {
@@ -27,8 +28,10 @@ function lowdefyBuild({ directories, logger, options }) {
27
28
  logger.info({ spin: 'start' }, 'Building config...');
28
29
  const startTime = Date.now();
29
30
  const customTypesMap = await createCustomPluginTypesMap({ directories, logger });
31
+ const customMessagesMap = await createCustomPluginMessagesMap({ directories, logger });
30
32
 
31
33
  const result = await shallowBuild({
34
+ customMessagesMap,
32
35
  customTypesMap,
33
36
  directories,
34
37
  logger,
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ Copyright 2020-2026 Lowdefy, Inc
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ */
17
+
18
+ import { createRequire } from 'node:module';
19
+ import path from 'path';
20
+ import { get } from '@lowdefy/helpers';
21
+ import { readFile } from '@lowdefy/node-utils';
22
+ import YAML from 'yaml';
23
+
24
+ const require = createRequire(import.meta.url);
25
+
26
+ async function getPluginDefinitions({ directories }) {
27
+ let lowdefyYaml = await readFile(path.join(directories.config, 'lowdefy.yaml'));
28
+ if (!lowdefyYaml) {
29
+ lowdefyYaml = await readFile(path.join(directories.config, 'lowdefy.yml'));
30
+ }
31
+ if (!lowdefyYaml) {
32
+ return [];
33
+ }
34
+ const lowdefy = YAML.parse(lowdefyYaml);
35
+ return get(lowdefy, 'plugins', { default: [] });
36
+ }
37
+
38
+ async function createCustomPluginMessagesMap({ directories, logger }) {
39
+ const customMessagesMap = {};
40
+ const pluginDefinitions = await getPluginDefinitions({ directories });
41
+
42
+ for (const plugin of pluginDefinitions) {
43
+ let messagesModule;
44
+ try {
45
+ messagesModule = require(`${plugin.name}/messages`);
46
+ } catch (e) {
47
+ // Plugin does not ship a `./messages` export — silently skip.
48
+ logger?.debug?.(`Plugin "${plugin.name}" has no "./messages" export; skipping.`);
49
+ continue;
50
+ }
51
+ const messages = messagesModule.default ?? messagesModule;
52
+ if (!messages || typeof messages !== 'object') continue;
53
+ customMessagesMap[plugin.name] = messages;
54
+ }
55
+
56
+ return customMessagesMap;
57
+ }
58
+
59
+ export default createCustomPluginMessagesMap;
@@ -39,6 +39,7 @@ async function getPluginDefinitions({ directories }) {
39
39
  async function createCustomPluginTypesMap({ directories, logger }) {
40
40
  const customTypesMap = {
41
41
  actions: {},
42
+ agents: {},
42
43
  auth: {
43
44
  adapters: {},
44
45
  callbacks: {},
package/next.config.js CHANGED
@@ -1,16 +1,23 @@
1
1
  const lowdefyConfig = require('./build/config.json');
2
2
  const blockPackages = require('./build/blockPackages.json');
3
+ const serverExternalPackages = require('./build/serverExternalPackages.json');
3
4
 
4
5
  // Transpile @lowdefy/client plus all block plugin packages that may
5
6
  // contain CSS imports (e.g., AG Grid themes, loaders, markdown).
6
7
  // Built dynamically so custom user plugins are included automatically.
7
- const transpilePackages = ['@lowdefy/client', ...blockPackages];
8
+ const transpilePackages = [
9
+ '@lowdefy/client',
10
+ '@ant-design/x',
11
+ '@ant-design/x-markdown',
12
+ ...blockPackages,
13
+ ];
8
14
 
9
15
  const nextConfig = {
10
16
  basePath: lowdefyConfig.basePath,
11
17
  // reactStrictMode: true,
12
18
  turbopack: {},
13
19
  transpilePackages,
20
+ serverExternalPackages,
14
21
  compress: false,
15
22
  poweredByHeader: false,
16
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/server-dev",
3
- "version": "5.2.0",
3
+ "version": "5.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -36,35 +36,37 @@
36
36
  ".npmrc"
37
37
  ],
38
38
  "dependencies": {
39
- "@lowdefy/actions-core": "5.2.0",
40
- "@lowdefy/api": "5.2.0",
41
- "@lowdefy/block-utils": "5.2.0",
42
- "@lowdefy/blocks-aggrid": "5.2.0",
43
- "@lowdefy/blocks-antd": "5.2.0",
44
- "@lowdefy/blocks-basic": "5.2.0",
45
- "@lowdefy/blocks-echarts": "5.2.0",
46
- "@lowdefy/blocks-loaders": "5.2.0",
47
- "@lowdefy/blocks-markdown": "5.2.0",
48
- "@lowdefy/blocks-tiptap": "5.2.0",
49
- "@lowdefy/build": "5.2.0",
50
- "@lowdefy/client": "5.2.0",
51
- "@lowdefy/connection-axios-http": "5.2.0",
52
- "@lowdefy/engine": "5.2.0",
53
- "@lowdefy/errors": "5.2.0",
54
- "@lowdefy/helpers": "5.2.0",
55
- "@lowdefy/layout": "5.2.0",
56
- "@lowdefy/logger": "5.2.0",
57
- "@lowdefy/node-utils": "5.2.0",
58
- "@lowdefy/operators-change-case": "5.2.0",
59
- "@lowdefy/operators-dayjs": "5.2.0",
60
- "@lowdefy/operators-diff": "5.2.0",
61
- "@lowdefy/operators-js": "5.2.0",
62
- "@lowdefy/operators-mql": "5.2.0",
63
- "@lowdefy/operators-nunjucks": "5.2.0",
64
- "@lowdefy/operators-uuid": "5.2.0",
65
- "@lowdefy/operators-yaml": "5.2.0",
66
- "@lowdefy/plugin-next-auth": "5.2.0",
39
+ "@lowdefy/actions-core": "5.4.0",
40
+ "@lowdefy/api": "5.4.0",
41
+ "@lowdefy/block-utils": "5.4.0",
42
+ "@lowdefy/blocks-aggrid": "5.4.0",
43
+ "@lowdefy/blocks-antd": "5.4.0",
44
+ "@lowdefy/blocks-antd-x": "5.4.0",
45
+ "@lowdefy/blocks-basic": "5.4.0",
46
+ "@lowdefy/blocks-echarts": "5.4.0",
47
+ "@lowdefy/blocks-loaders": "5.4.0",
48
+ "@lowdefy/blocks-markdown": "5.4.0",
49
+ "@lowdefy/blocks-tiptap": "5.4.0",
50
+ "@lowdefy/build": "5.4.0",
51
+ "@lowdefy/client": "5.4.0",
52
+ "@lowdefy/connection-axios-http": "5.4.0",
53
+ "@lowdefy/engine": "5.4.0",
54
+ "@lowdefy/errors": "5.4.0",
55
+ "@lowdefy/helpers": "5.4.0",
56
+ "@lowdefy/layout": "5.4.0",
57
+ "@lowdefy/logger": "5.4.0",
58
+ "@lowdefy/node-utils": "5.4.0",
59
+ "@lowdefy/operators-change-case": "5.4.0",
60
+ "@lowdefy/operators-dayjs": "5.4.0",
61
+ "@lowdefy/operators-diff": "5.4.0",
62
+ "@lowdefy/operators-js": "5.4.0",
63
+ "@lowdefy/operators-mql": "5.4.0",
64
+ "@lowdefy/operators-nunjucks": "5.4.0",
65
+ "@lowdefy/operators-uuid": "5.4.0",
66
+ "@lowdefy/operators-yaml": "5.4.0",
67
+ "@lowdefy/plugin-next-auth": "5.4.0",
67
68
  "@ant-design/cssinjs": "2.1.2",
69
+ "@ant-design/x": "2.7.0",
68
70
  "antd": "6.3.1",
69
71
  "dayjs": "1.11.19",
70
72
  "chokidar": "3.5.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/server-dev",
3
- "version": "5.2.0",
3
+ "version": "5.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -44,35 +44,37 @@
44
44
  "prepublishOnly": "pnpm build"
45
45
  },
46
46
  "dependencies": {
47
- "@lowdefy/actions-core": "5.2.0",
48
- "@lowdefy/api": "5.2.0",
49
- "@lowdefy/block-utils": "5.2.0",
50
- "@lowdefy/blocks-aggrid": "5.2.0",
51
- "@lowdefy/blocks-antd": "5.2.0",
52
- "@lowdefy/blocks-basic": "5.2.0",
53
- "@lowdefy/blocks-echarts": "5.2.0",
54
- "@lowdefy/blocks-loaders": "5.2.0",
55
- "@lowdefy/blocks-markdown": "5.2.0",
56
- "@lowdefy/blocks-tiptap": "5.2.0",
57
- "@lowdefy/build": "5.2.0",
58
- "@lowdefy/client": "5.2.0",
59
- "@lowdefy/connection-axios-http": "5.2.0",
60
- "@lowdefy/engine": "5.2.0",
61
- "@lowdefy/errors": "5.2.0",
62
- "@lowdefy/helpers": "5.2.0",
63
- "@lowdefy/layout": "5.2.0",
64
- "@lowdefy/logger": "5.2.0",
65
- "@lowdefy/node-utils": "5.2.0",
66
- "@lowdefy/operators-change-case": "5.2.0",
67
- "@lowdefy/operators-dayjs": "5.2.0",
68
- "@lowdefy/operators-diff": "5.2.0",
69
- "@lowdefy/operators-js": "5.2.0",
70
- "@lowdefy/operators-mql": "5.2.0",
71
- "@lowdefy/operators-nunjucks": "5.2.0",
72
- "@lowdefy/operators-uuid": "5.2.0",
73
- "@lowdefy/operators-yaml": "5.2.0",
74
- "@lowdefy/plugin-next-auth": "5.2.0",
47
+ "@lowdefy/actions-core": "5.4.0",
48
+ "@lowdefy/api": "5.4.0",
49
+ "@lowdefy/block-utils": "5.4.0",
50
+ "@lowdefy/blocks-aggrid": "5.4.0",
51
+ "@lowdefy/blocks-antd": "5.4.0",
52
+ "@lowdefy/blocks-antd-x": "5.4.0",
53
+ "@lowdefy/blocks-basic": "5.4.0",
54
+ "@lowdefy/blocks-echarts": "5.4.0",
55
+ "@lowdefy/blocks-loaders": "5.4.0",
56
+ "@lowdefy/blocks-markdown": "5.4.0",
57
+ "@lowdefy/blocks-tiptap": "5.4.0",
58
+ "@lowdefy/build": "5.4.0",
59
+ "@lowdefy/client": "5.4.0",
60
+ "@lowdefy/connection-axios-http": "5.4.0",
61
+ "@lowdefy/engine": "5.4.0",
62
+ "@lowdefy/errors": "5.4.0",
63
+ "@lowdefy/helpers": "5.4.0",
64
+ "@lowdefy/layout": "5.4.0",
65
+ "@lowdefy/logger": "5.4.0",
66
+ "@lowdefy/node-utils": "5.4.0",
67
+ "@lowdefy/operators-change-case": "5.4.0",
68
+ "@lowdefy/operators-dayjs": "5.4.0",
69
+ "@lowdefy/operators-diff": "5.4.0",
70
+ "@lowdefy/operators-js": "5.4.0",
71
+ "@lowdefy/operators-mql": "5.4.0",
72
+ "@lowdefy/operators-nunjucks": "5.4.0",
73
+ "@lowdefy/operators-uuid": "5.4.0",
74
+ "@lowdefy/operators-yaml": "5.4.0",
75
+ "@lowdefy/plugin-next-auth": "5.4.0",
75
76
  "@ant-design/cssinjs": "2.1.2",
77
+ "@ant-design/x": "2.7.0",
76
78
  "antd": "6.3.1",
77
79
  "dayjs": "1.11.19",
78
80
  "chokidar": "3.5.3",
package/pages/_app.js CHANGED
@@ -25,10 +25,14 @@ import { useRouter } from 'next/router';
25
25
  import useSWR from 'swr';
26
26
 
27
27
  import { ErrorBoundary } from '@lowdefy/block-utils';
28
- import { useDarkMode } from '@lowdefy/client';
28
+ import { useDarkMode, useLocale } from '@lowdefy/client';
29
29
  import { StyleProvider } from '@ant-design/cssinjs';
30
- import { App as AntdApp, ConfigProvider, theme as antdTheme } from 'antd';
30
+ import { App as AntdApp, theme as antdTheme } from 'antd';
31
+ import { XProvider } from '@ant-design/x';
31
32
 
33
+ import antdLocaleLoaders from '../build/i18n/antdLocales.js';
34
+ import antdXLocaleLoaders from '../build/i18n/antdXLocales.js';
35
+ import dayjsLocaleMap from '../build/i18n/dayjsLocales.js';
32
36
  import Auth from '../lib/client/auth/Auth.js';
33
37
  import ErrorBar from '../lib/client/ErrorBar.js';
34
38
  import request from '../lib/client/utils/request.js';
@@ -62,6 +66,17 @@ function App({ Component }) {
62
66
  configDarkMode: lowdefyRef.current.theme?.darkMode,
63
67
  });
64
68
 
69
+ const { active: activeLocale, antdLocale, antdXLocale } = useLocale({
70
+ i18n: rootConfig?.i18n,
71
+ antdLocaleLoaders,
72
+ antdXLocaleLoaders,
73
+ dayjsLocaleMap,
74
+ });
75
+
76
+ if (rootConfig?.i18n?.defaultLocale) {
77
+ lowdefyRef.current.i18n = { ...rootConfig.i18n, active: activeLocale };
78
+ }
79
+
65
80
  const {
66
81
  lightToken: _lightToken,
67
82
  darkToken: _darkToken,
@@ -99,9 +114,21 @@ function App({ Component }) {
99
114
  }
100
115
  }, []);
101
116
 
117
+ // XProvider extends antd's ConfigProvider; merging antd + antd-X locale packs
118
+ // gives X components their built-in strings alongside antd's.
119
+ const mergedLocale = antdLocale || antdXLocale
120
+ ? { ...(antdLocale ?? {}), ...(antdXLocale ?? {}) }
121
+ : undefined;
122
+
102
123
  return (
103
124
  <StyleProvider layer>
104
- <ConfigProvider
125
+ <XProvider
126
+ locale={mergedLocale}
127
+ form={
128
+ antdLocale?.Form?.defaultValidateMessages
129
+ ? { validateMessages: antdLocale.Form.defaultValidateMessages }
130
+ : undefined
131
+ }
105
132
  theme={{
106
133
  ...antdConfig,
107
134
  token,
@@ -134,7 +161,7 @@ function App({ Component }) {
134
161
  <ErrorBar errors={runtimeErrors} />
135
162
  </ThemeTokenResolver>
136
163
  </AntdApp>
137
- </ConfigProvider>
164
+ </XProvider>
138
165
  </StyleProvider>
139
166
  );
140
167
  }
@@ -0,0 +1,86 @@
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 { callAgent } from '@lowdefy/api';
18
+ import { translate, type } from '@lowdefy/helpers';
19
+
20
+ import apiWrapper from '../../../lib/server/apiWrapper.js';
21
+
22
+ async function handler({ context, req, res }) {
23
+ const t = (key, values) => translate({ key, values, i18n: context.i18n });
24
+ if (req.method !== 'POST') {
25
+ throw new Error(t('agent.runtime.methodNotAllowed'));
26
+ }
27
+ const segments = req.query.path;
28
+ if (!Array.isArray(segments) || segments.length < 2) {
29
+ res.status(400).json({ error: t('agent.runtime.invalidPath') });
30
+ return;
31
+ }
32
+ const agentId = segments[segments.length - 1];
33
+ const pageId = segments.slice(0, -1).join('/');
34
+ context.logger.info({ color: 'gray' }, `Agent: ${pageId} → ${agentId}`);
35
+ const { conversationId } = req.query;
36
+ const { messages, urlQuery, sharedState } = req.body;
37
+ if (!Array.isArray(messages)) {
38
+ res.status(400).json({ error: t('agent.runtime.messagesMustBeArray') });
39
+ return;
40
+ }
41
+ if (urlQuery != null && (typeof urlQuery !== 'object' || Array.isArray(urlQuery))) {
42
+ res.status(400).json({ error: t('agent.runtime.urlQueryMustBeObject') });
43
+ return;
44
+ }
45
+ if (sharedState != null && !type.isObject(sharedState)) {
46
+ res.status(400).json({ error: t('agent.runtime.sharedStateMustBeObject') });
47
+ return;
48
+ }
49
+ const { response: webResponse } = await callAgent(context, {
50
+ agentId,
51
+ pageId,
52
+ messages,
53
+ conversationId: conversationId ?? undefined,
54
+ sharedState: sharedState ?? undefined,
55
+ urlQuery: urlQuery ?? undefined,
56
+ });
57
+
58
+ // Stream the Web Response body to the Next.js response
59
+ res.setHeader('Content-Type', 'text/event-stream');
60
+ res.setHeader('Cache-Control', 'no-cache');
61
+ res.setHeader('Connection', 'keep-alive');
62
+ res.setHeader('Content-Encoding', 'none');
63
+ res.setHeader('Transfer-Encoding', 'chunked');
64
+
65
+ const reader = webResponse.body.getReader();
66
+ const decoder = new TextDecoder();
67
+ let done = false;
68
+ while (!done) {
69
+ const { value, done: readerDone } = await reader.read();
70
+ done = readerDone;
71
+ if (value) {
72
+ res.write(decoder.decode(value, { stream: true }));
73
+ }
74
+ }
75
+ res.end();
76
+ }
77
+
78
+ export const config = {
79
+ api: {
80
+ bodyParser: {
81
+ sizeLimit: '10mb',
82
+ },
83
+ },
84
+ };
85
+
86
+ export default apiWrapper(handler);