Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"scripts": {
"dev": "pnpm -F tiny-pro-vue start",
"dev:backend": "pnpm -F tinyui-nestjs-server start",
"dev:designer": "pnpm -F template/lowcode-designer dev",
"describe": "npm-scripts-info",
"build": "run-s build:*",
"build:main": "tsc -p tsconfig.json",
Expand Down
2 changes: 1 addition & 1 deletion template/lowcode-designer/engine.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default {
id: 'engine.config',
theme: 'light',
material: ['/mock/bundle.json'],
material: ['./mock/bundle.json'],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find bundle.json file in the template/lowcode-designer directory
echo "=== Checking for bundle.json in mock directory ==="
find template/lowcode-designer -name "bundle.json" -type f 2>/dev/null

echo ""
echo "=== Listing files in template/lowcode-designer/mock/ ==="
ls -la template/lowcode-designer/mock/ 2>/dev/null || echo "Directory does not exist or is empty"

echo ""
echo "=== Checking engine.config.js content ==="
cat -n template/lowcode-designer/engine.config.js

echo ""
echo "=== Searching for old absolute path pattern with absolute path /mock/bundle.json ==="
rg "/mock/bundle\.json" template/lowcode-designer/ --type js -n

Repository: opentiny/tiny-pro

Length of output: 639


Path resolution error: relative path points to non-existent directory.

The file bundle.json actually exists at template/lowcode-designer/public/mock/bundle.json, but the relative path ./mock/bundle.json resolves to a non-existent directory. The correct relative path should be ./public/mock/bundle.json.

🤖 Prompt for AI Agents
In @template/lowcode-designer/engine.config.js at line 4, Update the "material"
entry in the config so its relative path points to the existing bundle.json;
replace the incorrect './mock/bundle.json' value with
'./public/mock/bundle.json' for the material array entry to resolve the path
resolution error.

scripts: [],
styles: []
}
5 changes: 5 additions & 0 deletions template/lowcode-designer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
"@opentiny/vue-locale": "~3.20.0",
"@opentiny/vue-renderless": "~3.20.0",
"@opentiny/vue-theme": "~3.20.0",
"@opentiny/vue-theme-mobile": "~3.20.0",
"@vueuse/core": "^9.6.0",
"dexie": "^4.2.2",
"monaco-editor": "0.51.0",
"axios": "^1.12.0",
"axios-mock-adapter": "^2.1.0",
Comment on lines +25 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

axios-mock-adapter 2.1.0 supported axios versions compatibility

💡 Result:

axios-mock-adapter 2.1.0 is intended to work with axios v0.17.0 and above (per the package README). However, there are known TypeScript compatibility issues when used with axios ≥ 1.6.0 — users report errors and note axios 1.5.1 is the last version that did not trigger the type error. [1][2]

Sources:

  • npm package README (axios-mock-adapter: "works with axios v0.17.0 and above"). [1]
  • GitHub issue reporting TypeScript errors with axios >= 1.6.0 (recommends axios 1.5.1 to avoid the error). [2]

🌐 Web query:

monaco-editor 0.51.0 Vite worker configuration requirements

💡 Result:

Summary — what you must do to run monaco-editor 0.51.0 with Vite

  • Use the ESM integration pattern: set self.MonacoEnvironment.getWorker (Vite expects getWorker, not getWorkerUrl) and return a Worker constructed from the Vite-built worker module (type: 'module'). Example pattern and label → file mapping shown in the Monaco ESM integration docs. [1][2]

  • Worker label → module mapping (use these labels in getWorker):

    • json → vs/language/json/json.worker
    • css / scss / less → vs/language/css/css.worker
    • html / handlebars / razor → vs/language/html/html.worker
    • typescript / javascript → vs/language/typescript/ts.worker
    • default → vs/editor/editor.worker. [1][2]
  • Construct the Worker using the Vite worker entry URL (append ?worker or use Vite’s worker API) and set { type: 'module', name: label } so the worker is an ES module. Example: new Worker('/monaco-editor/esm/vs/language/typescript/ts.worker?worker', { type: 'module', name: 'typescript' }). [1][3]

  • If you prefer convenience, use a Vite plugin (vite-plugin-monaco-editor) which bundles/outputs worker scripts and exposes options (languageWorkers, customWorkers, publicPath, globalAPI). The plugin also enforces including editorWorkerService when using other language workers. [3][4]

  • Be aware of the 0.51.0-specific worker issues reported by users: some users saw importScripts / invalid-URL errors when upgrading to 0.51.0 (fallback to main-thread workers). If you hit worker-creation errors after upgrading, check your getWorker implementation / public base path and consider using the plugin or a package that bundles Monaco workers for ESM (monaco-esm / monaco-editor-workers). [5][6]

Sources

  • Monaco ESM integration + Vite example (getWorker implementation and label mapping). [1]
  • Guide showing Vite usage and worker URLs (example using ?worker and type:'module'). [2]
  • vite-plugin-monaco-editor README/options (languageWorkers, customWorkers, publicPath, globalAPI). [3]
  • vite-plugin-monaco-editor GitHub repo. [4]
  • Issue reporting worker creation errors after upgrading to 0.51.0. [5]
  • monaco-esm / monaco-editor-workers community packages (bundled workers alternative). [6]

References
[1] Integrating the ESM version of the Monaco Editor — Vite example.
[2] Integrate-ESM docs / examples (getWorker + ?worker usage).
[3] vite-plugin-monaco-editor (npm / README).
[4] vite-plugin-monaco-editor (GitHub).
[5] microsoft/monaco-editor Issue: "0.51.0 only: Could not create web worker".
[6] monaco-esm / monaco-editor-workers (bundled-workers packages).


🌐 Web query:

axios 1.12.0 axios-mock-adapter compatibility known issues

💡 Result:

Short summary — known compatibility problems between axios 1.12.0 (and recent 1.x releases) and axios-mock-adapter:

  • TypeScript type incompatibility: axios introduced ESM/typing changes (starting around 1.6.0) that cause TypeScript errors when using axios-mock-adapter’s types (errors reported when the axios import uses the newer ESM types). This is a long-running issue on the axios-mock-adapter repo (see issue #400). [1]

  • Axios 1.12.0-specific type/build problems: people reported TypeScript build errors after upgrading to axios 1.12.0 (missing global DOM types like RequestInfo/BodyInit in some Node setups), which can surface when using libraries that depend on axios types. See axios issue #7016. [2]

  • Adapter/behavior changes in axios 1.12.0: the fetch adapter behavior in recent axios releases changed and has caused compatibility problems with some mocking/interception tools (e.g., MSW) — reported in axios issue #7015. While this is not axios-mock-adapter directly, it can affect test setups that rely on different adapters. [3]

Recommendations

  • If you hit TS errors with axios-mock-adapter, either:
    • use an axios version known to avoid the ESM-typing mismatch (users reported 1.5.1 as the last version without the ESM-type issue), or
    • import axios in a way that yields compatible types (CJS import) or update/patch axios-mock-adapter types if feasible. See the axios-mock-adapter issue for discussion. [1][4]
  • If you rely on fetch-adapter behavior (or MSW), prefer forcing the XHR adapter or pinning axios until the adapter/compatibility issue is resolved. [3]
  • Watch the upstream issue trackers for fixes/PRs in both repos and test after upgrades.

Sources:
[1] axios-mock-adapter issue discussing TS errors with axios >=1.6.0: ctimmerm/axios-mock-adapter#400
[2] axios issue: TypeScript build errors reported for 1.12.0: axios/axios#7016
[3] axios issue: fetch adapter breaks compatibility with MSW in 1.12.0: axios/axios#7015
[4] (context) axios-mock-adapter repo / discussions about compatibility with newer axios typings. https://github.com/ctimmerm/axios-mock-adapter

If you want, I can search your project’s error message and suggest a concrete fix (pin version, tsconfig change, or import workaround).


🏁 Script executed:

cd template/lowcode-designer && find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.vue" \) | head -20

Repository: opentiny/tiny-pro

Length of output: 538


🏁 Script executed:

cd template/lowcode-designer && rg -i "MonacoEnvironment|getWorker" --type ts --type js --type vue

Repository: opentiny/tiny-pro

Length of output: 88


🏁 Script executed:

cd template/lowcode-designer && rg -i "axios-mock-adapter|createMockAdapter" --type ts --type js --type vue

Repository: opentiny/tiny-pro

Length of output: 88


🏁 Script executed:

cd template/lowcode-designer && cat -n vite.config.ts 2>/dev/null || cat -n vite.config.js 2>/dev/null || echo "No vite config found"

Repository: opentiny/tiny-pro

Length of output: 1643


🏁 Script executed:

cd template/lowcode-designer && rg -i "MonacoEnvironment|getWorker" --type-list | grep -E "js|ts" | head -5

Repository: opentiny/tiny-pro

Length of output: 450


🏁 Script executed:

cd template/lowcode-designer && rg "MonacoEnvironment|getWorker"

Repository: opentiny/tiny-pro

Length of output: 43


🏁 Script executed:

cd template/lowcode-designer && rg "axios-mock-adapter|MockAdapter"

Repository: opentiny/tiny-pro

Length of output: 352


🏁 Script executed:

cd template/lowcode-designer && rg "import.*axios|require.*axios" | head -10

Repository: opentiny/tiny-pro

Length of output: 210


🏁 Script executed:

cd template/lowcode-designer && cat -n src/main.js | head -50

Repository: opentiny/tiny-pro

Length of output: 1202


🏁 Script executed:

cd template/lowcode-designer && cat -n tsconfig.json 2>/dev/null || echo "No tsconfig found"

Repository: opentiny/tiny-pro

Length of output: 78


🏁 Script executed:

cd template/lowcode-designer && find . -name "tsconfig.json" -o -name "jsconfig.json" | head -5

Repository: opentiny/tiny-pro

Length of output: 43


🏁 Script executed:

cd template && cat -n tsconfig.json 2>/dev/null | head -30

Repository: opentiny/tiny-pro

Length of output: 43


🏁 Script executed:

cd template/lowcode-designer && rg "monaco-editor|monaco"

Repository: opentiny/tiny-pro

Length of output: 104


🏁 Script executed:

cd template/lowcode-designer && cat -n src/composable/http/httpServices.js | head -40

Repository: opentiny/tiny-pro

Length of output: 1163


Resolve axios/axios-mock-adapter version conflict; verify Monaco worker configuration is handled by base config.

axios-mock-adapter 2.1.0 has confirmed TypeScript compatibility issues with axios ≥ 1.6.0 (see axios-mock-adapter issue #400). axios 1.5.1 is the last version without the ESM-type mismatch, and the project actively uses axios-mock-adapter in src/composable/http/httpServices.js. Either downgrade axios to 1.5.1 or use a different testing approach.

For monaco-editor 0.51.0: the dependency requires explicit Vite worker configuration (getWorker implementation and worker URL setup). Verify that useTinyEngineBaseConfig from @opentiny/tiny-engine-vite-config already handles Monaco's ESM worker requirements; if not, you'll see runtime worker errors when the editor loads.

"vue": "^3.4.21"
},
"devDependencies": {
Expand Down
11,668 changes: 0 additions & 11,668 deletions template/lowcode-designer/pnpm-lock.yaml

This file was deleted.

243 changes: 243 additions & 0 deletions template/lowcode-designer/src/composable/http/httpServices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { defineService, META_SERVICE } from '@opentiny/tiny-engine';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';

let http = null;
let mock = null;

const createInterceptorHandler =
(http) =>
({ data, type }) => {
if (typeof data === 'function') {
http.interceptors[type].use(data);

return;
}

if (Array.isArray(data)) {
data.forEach((item) => {
if (!item) return;

if (Array.isArray(item)) {
http.interceptors[type].use(...item);

return;
}

if (typeof item === 'function') {
http.interceptors[type].use(item);
}
});
}
};

export default defineService({
id: META_SERVICE.Http,
type: 'MetaService',
options: {
axiosConfig: {
// axios 配置
baseURL: '',
withCredentials: false, // 跨域请求时是否需要使用凭证
headers: {}, // 请求头
},
interceptors: {
// 拦截器
request: [], // 支持配置多个请求拦截器,先注册后执行
response: [], // 支持配置多个响应拦截器,先注册先执行
},
mockConfig: [],
},
init: ({ options = {} }) => {
const { axiosConfig = {}, interceptors = {}, enableMock } = options;
const { request = [], response = [] } = interceptors;

http = axios.create(axiosConfig);
if (enableMock) {
mock = new AxiosMockAdapter(http);
mock.onGet(/\/mock\/bundle\.json$/).passThrough();
mock.onPost(/\/app-center\/api\/ai\/chat/).passThrough();
// 允许 tiny-pro 的接口通过,不被 mock 拦截
mock.onAny(/\/api\/auth\/api-token/).passThrough();
mock.onAny(/\/api\/i18n/).passThrough();
mock.onAny(/\/api\/menu/).passThrough();
mock.onAny(/\/api\/role/).passThrough();
mock.onAny(/\/api\/role\/detail/).passThrough();

http.interceptors.request.use((config) => {
const AI_PATH = '/app-center/api/ai/chat';

if (config.url === AI_PATH) {
config.url = `/tiny-engine${AI_PATH}`; // 修改路径
}
return config;
});
Comment on lines +67 to +74
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

AI_PATH interceptor accumulates on repeated setOptions calls.

This request interceptor is added unconditionally in both init and setOptions. If setOptions is called multiple times with enableMock, this interceptor will be registered repeatedly, potentially causing unexpected URL rewrites.

Consider tracking whether this interceptor has been added, or eject it before re-adding.

Proposed fix: track interceptor registration
 let http = null;
 let mock = null;
+let aiPathInterceptorId = null;

 // In init and setOptions, before adding the interceptor:
+if (aiPathInterceptorId !== null) {
+  http.interceptors.request.eject(aiPathInterceptorId);
+}
-http.interceptors.request.use((config) => {
+aiPathInterceptorId = http.interceptors.request.use((config) => {
   const AI_PATH = '/app-center/api/ai/chat';
   if (config.url === AI_PATH) {
     config.url = `/tiny-engine${AI_PATH}`;
   }
   return config;
 });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @template/lowcode-designer/src/composable/http/httpServices.js around lines
67 - 74, The request interceptor that rewrites AI_PATH is being added
unconditionally (in init and setOptions), causing duplicates on repeated
setOptions; update the httpServices module to store the interceptor id (the
value returned by http.interceptors.request.use) or a boolean flag (e.g.,
aiPathInterceptorId or aiPathInterceptorRegistered) and before adding the
interceptor in init/setOptions either check the flag to avoid re-adding or call
http.interceptors.request.eject(interceptorId) to remove the old one, then
register and save the new id so the AI_PATH rewrite is only applied once.


mock.onAny().reply(async (config) => {
const { mockConfig = [] } = options;
// 构建完整 URL(包含 baseURL)
const fullUrl = (config.baseURL || '') + (config.url || '');
const method = (config.method || 'GET').toUpperCase();

const mockItem = mockConfig.find((item) => {
// 方法匹配
if (item.method && method !== item.method.toUpperCase()) {
return false;
}

// URL 匹配 - 同时检查 config.url 和 fullUrl
if (typeof item.url === 'string') {
return (
item.url === config.url ||
item.url === fullUrl ||
config.url?.includes(item.url) ||
fullUrl.includes(item.url)
);
}

if (item.url instanceof RegExp) {
return item.url.test(config.url) || item.url.test(fullUrl);
}

return false;
});

if (mockItem) {
if (typeof mockItem.response === 'function') {
return mockItem.response(config);
}
return mockItem.response;
}

// 如果没有匹配到,输出调试信息
console.warn(
`[Mock] 未匹配到接口: ${method} ${fullUrl || config.url}`,
'可用路由:',
mockConfig.map((item) => `${item.method || 'ANY'} ${item.url}`)
);

return [
200,
{
code: 200,
errMsg:
'当前 demo 暂未支持该接口,请前往GitHub 或者 Gitee 克隆项目完整体验',
error:
'当前 demo 暂未支持该接口,请前往GitHub 或者 Gitee 克隆项目完整体验',
},
];
});
Comment on lines +76 to +129
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Significant code duplication between init and setOptions.

The mock handler logic (lines 76-129) is nearly identical to lines 185-239 in setOptions. This ~50-line duplication violates DRY and increases maintenance burden.

Proposed refactor: extract a shared helper
+const createMockHandler = (mockConfig) => async (config) => {
+  const fullUrl = (config.baseURL || '') + (config.url || '');
+  const method = (config.method || 'GET').toUpperCase();
+
+  const mockItem = mockConfig.find((item) => {
+    if (item.method && method !== item.method.toUpperCase()) {
+      return false;
+    }
+    if (typeof item.url === 'string') {
+      return (
+        item.url === config.url ||
+        item.url === fullUrl ||
+        config.url?.includes(item.url) ||
+        fullUrl.includes(item.url)
+      );
+    }
+    if (item.url instanceof RegExp) {
+      return item.url.test(config.url) || item.url.test(fullUrl);
+    }
+    return false;
+  });
+
+  if (mockItem) {
+    return typeof mockItem.response === 'function'
+      ? mockItem.response(config)
+      : mockItem.response;
+  }
+
+  console.warn(
+    `[Mock] 未匹配到接口: ${method} ${fullUrl || config.url}`,
+    '可用路由:',
+    mockConfig.map((item) => `${item.method || 'ANY'} ${item.url}`)
+  );
+
+  return [200, { code: 200, errMsg: '...', error: '...' }];
+};

// Then in init and setOptions:
-mock.onAny().reply(async (config) => { /* 50 lines */ });
+mock.onAny().reply(createMockHandler(options.mockConfig));

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @template/lowcode-designer/src/composable/http/httpServices.js around lines
76 - 129, The mock reply logic duplicated between init and setOptions should be
extracted into a single helper (e.g., createMockReplyHandler or
getMockResponder) that accepts the options/mockConfig and returns the function
passed to mock.onAny().reply; move the URL/method matching, RegExp handling,
response invocation (call when response is a function) and the default 200
fallback into that helper, then replace both inline handlers in init and
setOptions with mock.onAny().reply(createMockReplyHandler(options)); ensure the
helper references config.baseURL/config.url and preserves current log message
and returned payload semantics.

}

const addInterceptors = createInterceptorHandler(http);
addInterceptors({ data: request, type: 'request' });
addInterceptors({ data: response, type: 'response' });
},
apis: () => ({
getHttp: () => http,
getMock: () => mock,
get: (...args) => http?.get(...args),
post: (...args) => http?.post(...args),
request: (...args) => http?.request(...args),
put: (...args) => http?.put(...args),
delete: (...args) => http?.delete(...args),
setOptions: (options) => {
// 支持动态设置选项
if (options.axiosConfig) {
Object.assign(http.defaults, options.axiosConfig);
}
if (options.interceptors) {
const addInterceptors = createInterceptorHandler(http);
if (options.interceptors.request) {
addInterceptors({
data: options.interceptors.request,
type: 'request',
});
}
if (options.interceptors.response) {
addInterceptors({
data: options.interceptors.response,
type: 'response',
});
}
}
// 重新初始化 mock(如果启用)
if (options.enableMock && options.mockConfig) {
// 如果 mock 已存在,先恢复
if (mock) {
mock.restore();
}
mock = new AxiosMockAdapter(http);
mock.onGet(/\/mock\/bundle\.json$/).passThrough();
mock.onPost(/\/app-center\/api\/ai\/chat/).passThrough();
// 允许 /api/auth/api-token 接口通过,不被 mock 拦截
mock.onAny(/\/api\/auth\/api-token/).passThrough();
Comment on lines +171 to +174
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Passthrough routes are inconsistent with init.

When setOptions reinitializes the mock, it only restores 2 of the 6 passthrough routes defined in init. Missing routes:

  • /api/i18n
  • /api/menu
  • /api/role
  • /api/role/detail

This will cause these endpoints to be unexpectedly mocked after calling setOptions.

Proposed fix: add missing passthrough routes
 mock = new AxiosMockAdapter(http);
 mock.onGet(/\/mock\/bundle\.json$/).passThrough();
 mock.onPost(/\/app-center\/api\/ai\/chat/).passThrough();
-// 允许 /api/auth/api-token 接口通过,不被 mock 拦截
+// 允许 tiny-pro 的接口通过,不被 mock 拦截
 mock.onAny(/\/api\/auth\/api-token/).passThrough();
+mock.onAny(/\/api\/i18n/).passThrough();
+mock.onAny(/\/api\/menu/).passThrough();
+mock.onAny(/\/api\/role/).passThrough();
+mock.onAny(/\/api\/role\/detail/).passThrough();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mock.onGet(/\/mock\/bundle\.json$/).passThrough();
mock.onPost(/\/app-center\/api\/ai\/chat/).passThrough();
// 允许 /api/auth/api-token 接口通过,不被 mock 拦截
mock.onAny(/\/api\/auth\/api-token/).passThrough();
mock.onGet(/\/mock\/bundle\.json$/).passThrough();
mock.onPost(/\/app-center\/api\/ai\/chat/).passThrough();
// 允许 tiny-pro 的接口通过,不被 mock 拦截
mock.onAny(/\/api\/auth\/api-token/).passThrough();
mock.onAny(/\/api\/i18n/).passThrough();
mock.onAny(/\/api\/menu/).passThrough();
mock.onAny(/\/api\/role/).passThrough();
mock.onAny(/\/api\/role\/detail/).passThrough();
🤖 Prompt for AI Agents
In @template/lowcode-designer/src/composable/http/httpServices.js around lines
171 - 174, setOptions reinitializes the mock but only restores two passthroughs;
update the mock reinit logic (where setOptions recreates or calls init) to
register the same passthrough routes that init uses: add
mock.onGet(/\/api\/i18n/).passThrough(),
mock.onGet(/\/api\/menu/).passThrough(),
mock.onAny(/\/api\/role$/).passThrough() and
mock.onAny(/\/api\/role\/detail/).passThrough() (or the equivalent patterns used
in init) so the mock instance in setOptions matches init's passthrough list.


http.interceptors.request.use((config) => {
const AI_PATH = '/app-center/api/ai/chat';

if (config.url === AI_PATH) {
config.url = `/tiny-engine${AI_PATH}`;
}
return config;
});

mock.onAny().reply(async (config) => {
// 构建完整 URL(包含 baseURL)
const fullUrl = (config.baseURL || '') + (config.url || '');
const method = (config.method || 'GET').toUpperCase();

const mockItem = options.mockConfig.find((item) => {
// 方法匹配
if (item.method && method !== item.method.toUpperCase()) {
return false;
}

// URL 匹配 - 同时检查 config.url 和 fullUrl
if (typeof item.url === 'string') {
return (
item.url === config.url ||
item.url === fullUrl ||
config.url?.includes(item.url) ||
fullUrl.includes(item.url)
);
}

if (item.url instanceof RegExp) {
return item.url.test(config.url) || item.url.test(fullUrl);
}

return false;
});

if (mockItem) {
if (typeof mockItem.response === 'function') {
return mockItem.response(config);
}
return mockItem.response;
}

// 如果没有匹配到,输出调试信息
console.warn(
`[Mock] 未匹配到接口: ${method} ${fullUrl || config.url}`,
'可用路由:',
options.mockConfig.map(
(item) => `${item.method || 'ANY'} ${item.url}`
)
);

return [
200,
{
code: 200,
errMsg:
'当前 demo 暂未支持该接口,请前往GitHub 或者 Gitee 克隆项目完整体验',
error:
'当前 demo 暂未支持该接口,请前往GitHub 或者 Gitee 克隆项目完整体验',
},
];
});
}
},
}),
});
10 changes: 6 additions & 4 deletions template/lowcode-designer/src/composable/http/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createApp } from 'vue'
import { HttpService } from '@opentiny/tiny-engine'
import { useBroadcastChannel } from '@vueuse/core'
import { constants } from '@opentiny/tiny-engine-utils'
import HttpService from './httpServices'
import Login from './Login.vue'
import mockConfig from '../../routes'

const LOGIN_EXPIRED_CODE = 401
const { BROADCAST_CHANNEL } = constants
Expand Down Expand Up @@ -41,7 +42,7 @@ const preRequest = (config) => {

const preResponse = (res) => {
if (res.data?.error) {
showError(res.config?.url, res?.data?.error?.message)
showError(res.config?.url, res?.data?.error?.message || res?.data?.error)

return Promise.reject(res.data.error)
}
Expand Down Expand Up @@ -81,7 +82,6 @@ const openLogin = () => {
}

const errorResponse = (error) => {
// 用户信息失效时,弹窗提示登录
const { response } = error

if (response?.status === LOGIN_EXPIRED_CODE) {
Expand Down Expand Up @@ -124,7 +124,9 @@ const customizeHttpService = () => {
interceptors: {
request: [preRequest],
response: [[preResponse, errorResponse]]
}
},
mockConfig,
enableMock: true
}

HttpService.apis.setOptions(options)
Expand Down
55 changes: 55 additions & 0 deletions template/lowcode-designer/src/db/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Dexie from "dexie"
import user from '../mock/user.json'
import appDetail from '../mock/appDetail.json'
import appSchema from '../mock/appSchema.json'
import pageList from '../mock/pageList.json'
import blockList from '../mock/blocks.json'
import blockGroup from '../mock/block-groups.json'
import utilsList from '../mock/utilsList.json'
import blockHistories from '../mock/blockHistories.json'
import { schema } from './schema'

export const db = new Dexie('tiny-engine-demo-indexdb')

// 声明数据库表
export const createDB = async () => {
return db.version(1).stores({
...schema
})
}
Comment on lines +14 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd template/lowcode-designer && find . -name "index.js" -path "*/db/*" | head -20

Repository: opentiny/tiny-pro

Length of output: 78


🏁 Script executed:

cat -n template/lowcode-designer/src/db/index.js

Repository: opentiny/tiny-pro

Length of output: 1751


🌐 Web query:

Dexie 4.x idempotent initialization db.version stores repeated calls

💡 Result:

Short answer: Dexie’s version(...).stores(...) must be declared before opening the DB (and you cannot repeatedly add the same version while the DB is open). To make initialization idempotent either (A) ensure you only declare the schema once (e.g. from a single DB singleton/constructor), or (B) guard/skip calls when the DB is already opened or already at the desired version. Documentation: version/stores must be declared before open and Dexie sorts/uses the highest declared version. [1][2]

Practical options:

  • Preferred (singleton): declare db.version(...).stores(...) once in your Dexie subclass or single module that is imported by the app — don’t call it on every render or repeated init. Example pattern is shown in the docs. [1][3]

  • Guard by open/version:

    • If you want a runtime guard, skip calling stores when db.isOpen() (or when db.verno >= desiredVersion). Example:
      const db = new Dexie('MyDB');
      if (!db.isOpen() && (db.verno < 1)) {
      db.version(1).stores({ friends: '++id,name' });
      }
      await db.open();
    • Or catch the “Cannot add version when database is open” error and ignore or close/re-open only when appropriate (older advice shown in community answers). [2]

References:

  • Dexie API / declaring versions and stores. [1]
  • StackOverflow discussion: “Cannot add version when database is open” (try/catch / close workaround). [2]
  • Version.stores docs (behavior & upgrade notes). [3]

Make DB initialization idempotent—createDB() will fail on repeated initIndexDB() calls.

Calling db.version(1).stores() inside initIndexDB() (line 35) violates Dexie's requirement that schema be declared before the database is opened. On the first call, createDB() declares the schema and db.open() succeeds; on the second call, createDB() will throw "Cannot add version when database is open" because the DB is already open from the first call. The userCount guard (line 41) only prevents data re-initialization, not schema re-declaration.

Recommended fix: Move schema declaration to the Dexie instance creation (lines 12–19), so it runs once at app startup:

export const db = new Dexie('tiny-engine-demo-indexdb')
db.version(1).stores({
  ...schema
})

export const initIndexDB = async () => {
  await db.open()
  // ... rest of function
}

Also applies to: 34–46

🤖 Prompt for AI Agents
In @template/lowcode-designer/src/db/index.js around lines 14 - 19, The schema
registration must happen once at DB construction to avoid "Cannot add version
when database is open"; move the call to db.version(1).stores(...) out of
createDB() and into the Dexie instance setup so it runs at module initialization
(i.e., declare export const db = new Dexie(...); then call
db.version(1).stores({...schema})). Then change createDB() to simply return the
existing db (or remove it) and update initIndexDB() to call await db.open() (and
keep the existing userCount guard for data seeding only), ensuring no further
db.version(...) calls occur after db.open().


// 初始化注入数据
const initData = async () => {
// 添加用户
await db.user.add(user)
await db.appDetail.add(appDetail)
await db.appSchema.add(appSchema)
await db.page.bulkPut(pageList)
await db.block.bulkPut(blockList)
await db.blockGroup.bulkPut(blockGroup)
await db.blockHistories.bulkPut(blockHistories)
await db.utils.bulkPut(utilsList)
}
// 初始化 indexdb
export const initIndexDB = async () => {
await createDB()
await db.open()

const userCount = await db.user.count()

// 如果有数据了,那就不初始化数据了
if (userCount > 0) {
return
}

await initData()
}

// 重置表数据
export const resetDataBase = async () => {
await Promise.all(
db.tables.map(table => table.clear())
)

await initData()
}
Loading
Loading