Skip to content

refactor: replace custom MSW handlers with MSWPlugin + protocol broker shim#860

Merged
hotlong merged 3 commits intomainfrom
copilot/fix-console-msw-handler
Feb 25, 2026
Merged

refactor: replace custom MSW handlers with MSWPlugin + protocol broker shim#860
hotlong merged 3 commits intomainfrom
copilot/fix-console-msw-handler

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

Console MSW mode bypassed the ObjectStack protocol stack by calling driver.find() directly in hand-written HTTP handlers, causing filter/sort/top/pagination to silently fail. Server mode worked correctly because it routes through the protocol.

Changes

  • Deleted apps/console/src/mocks/handlers.ts — 220 lines of custom createHandlers() that duplicated protocol logic incorrectly
  • createKernel.ts — Added MSWPlugin integration and a lightweight broker shim that bridges HttpDispatcher → protocol service. HttpDispatcher requires kernel.broker (normally provided by HonoServerPlugin); the shim delegates data.* and metadata.* broker calls to the protocol service instead:
(kernel as any).broker = {
  async call(action: string, params: any = {}) {
    const [service, method] = action.split('.');
    if (service === 'data') {
      switch (method) {
        case 'query':
          return protocol.findData({ object: params.object, query: params.query ?? params });
        // ... get, create, update, delete, batch
      }
    }
    // ... metadata handlers
  },
};
  • browser.ts — Replaced setupWorker + createHandlers with MSWPlugin({ enableBrowser: true }) via createKernel
  • server.ts — Replaced setupServer + createHandlers with MSWPlugin({ enableBrowser: false }) + getHandlers()
  • MSWServer.test.tsx — Added HTTP-level tests for top, sort, and filter query parameters confirming they now work through the protocol stack
  • ROADMAP.md — Added entry
Original prompt

This section details on the original issue you should resolve

<issue_title>Console MSW handler should use MSWPlugin, not custom HTTP handlers; filter/sort/top fail otherwise</issue_title>
<issue_description>## Bug Summary

ObjectUI Console (MSW 模式) 使用自定义 createHandlers 手写 HTTP handler,绕过了 ObjectStack 协议栈。结果导致 filter/sort/top 等所有查询参数失效,server 模式下正常。

复现方式

  1. 启动 dev server (pnpm dev)。
  2. 访问 /api/v1/data/contact?top=100&sort=name&filter=["priority","=","medium"]
  3. 发现过滤条件没有���效,返回所有数据。
  4. server 模式 (pnpm dev:server) 下过滤正常。

问题分析

  • apps/console/src/mocks/handlers.ts 的 Data handler 自己解析 query 参数并直接调用 driver.find(),不走 ObjectStack 协议 protocol。
  • filter/sort/top 等参数解析失效,实际行为和 dev server 不一致。
  • examples/msw-todo/QUICKSTART.md 推荐的写法是直接使用 @objectstack/plugin-msw,完全无需手写 handler。

最佳解决方案

  • 删除 createHandlers + hand-written handlers,直接在 Console 的 MSW 模式下 use(new MSWPlugin({ baseUrl: '/api/v1' }))。
  • 这样所有 HTTP 路由都和 server 模式一致,filter/sort/top/pagination/metadata 自动正确处理。
  • 维护性最好,所有协议更新自动同步。

建议修复步骤

  1. 移除 apps/console/src/mocks/handlers.ts (或者大幅简化,转为 use MSWPlugin)。
  2. 在 console mock 启动逻辑里直接 use(new MSWPlugin({ baseUrl: '/api/v1' }))。
  3. 对齐 QuickStart / msw-todo 示例。
  4. 补充测试:filter/sort/top/pagination 必须能通过。

开发任务完成之后都应该运行test,并且相关的问题,然后更新roadmap。

相关 issue: #858</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Ready Ready Preview, Comment Feb 25, 2026 8:58am
objectui-demo Ready Ready Preview, Comment Feb 25, 2026 8:58am
objectui-storybook Ready Ready Preview, Comment Feb 25, 2026 8:58am

Request Review

…r shim

- Remove apps/console/src/mocks/handlers.ts (custom createHandlers)
- Update createKernel.ts with MSWPlugin support and protocol broker shim
- Simplify browser.ts to use MSWPlugin (enableBrowser: true)
- Simplify server.ts to use MSWPlugin (enableBrowser: false) + getHandlers()
- Add filter/sort/top HTTP tests to MSWServer.test.tsx

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Console MSW handler to use MSWPlugin refactor: replace custom MSW handlers with MSWPlugin + protocol broker shim Feb 25, 2026
Copilot AI requested a review from hotlong February 25, 2026 08:39
@hotlong hotlong marked this pull request as ready for review February 25, 2026 08:42
Copilot AI review requested due to automatic review settings February 25, 2026 08:42
@hotlong hotlong merged commit 7dd5e9e into main Feb 25, 2026
4 of 6 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the Console MSW mock environment to use MSWPlugin instead of custom HTTP handlers, fixing a critical bug where filter/sort/top/pagination query parameters were silently ignored in dev/mock mode (while working correctly in server mode).

Changes:

  • Deleted 220 lines of custom createHandlers() that bypassed the ObjectStack protocol stack by calling driver.find() directly
  • Added broker shim in createKernel.ts that bridges HttpDispatcher → protocol service, enabling MSWPlugin to work without a full Moleculer broker
  • Integrated MSWPlugin in both browser and test environments with proper cleanup
  • Added HTTP-level integration tests validating filter/sort/top query parameters work correctly

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
apps/console/src/mocks/handlers.ts Deleted entire file (220 lines) - custom handlers that duplicated protocol logic incorrectly
apps/console/src/mocks/createKernel.ts Added installBrokerShim() function and MSWPlugin integration; broker shim delegates data/metadata broker calls to protocol service
apps/console/src/mocks/browser.ts Replaced setupWorker + custom handlers with MSWPlugin integration via createKernel
apps/console/src/mocks/server.ts Replaced custom handlers with MSWPlugin.getHandlers() for test environment
apps/console/src/tests/MSWServer.test.tsx Added 3 new tests for top/sort/filter query parameters via HTTP
ROADMAP.md Added entry documenting the fix (line 651)

Comment on lines +56 to +93
async call(action: string, params: any = {}) {
const [service, method] = action.split('.');

if (service === 'data') {
switch (method) {
case 'query':
return protocol.findData({ object: params.object, query: params.query ?? params });
case 'get':
return protocol.getData({ object: params.object, id: params.id });
case 'create':
return protocol.createData({ object: params.object, data: params.data });
case 'update':
return protocol.updateData({ object: params.object, id: params.id, data: params.data });
case 'delete':
return protocol.deleteData({ object: params.object, id: params.id });
case 'batch':
return protocol.batchData?.({ object: params.object, ...params }) ?? { results: [] };
}
}

if (service === 'metadata') {
if (method === 'types') return protocol.getMetaTypes({});
if (method === 'getObject') {
return protocol.getMetaItem({ type: 'object', name: params.objectName });
}
if (method === 'saveItem') {
return protocol.saveMetaItem?.({ type: params.type, name: params.name, item: params.item });
}
if (method.startsWith('get')) {
const type = method.replace('get', '').toLowerCase();
return protocol.getMetaItem({ type, name: params.name });
}
// list-style calls: metadata.objects, metadata.apps, etc.
return protocol.getMetaItems({ type: method, packageId: params.packageId });
}

throw new Error(`[BrokerShim] Unhandled action: ${action}`);
},
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Missing error handling: If protocol methods throw errors (e.g., findData, getData, createData), they will propagate uncaught. While this might be intentional to let MSWPlugin handle errors, the broker shim should at least ensure errors are properly wrapped or logged for debugging. Consider adding try-catch blocks around protocol method calls and wrapping errors with additional context (e.g., which action and params caused the error).

Copilot uses AI. Check for mistakes.
return protocol.getMetaItem({ type, name: params.name });
}
// list-style calls: metadata.objects, metadata.apps, etc.
return protocol.getMetaItems({ type: method, packageId: params.packageId });
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Potential bug in metadata routing: The fallback at line 89 assumes any unhandled method in the 'metadata' service should be treated as a list-style call (metadata.objects, metadata.apps, etc.). However, this could incorrectly handle typos or invalid method names, returning misleading results instead of throwing an error.

Consider adding explicit handling for known list methods (objects, apps, dashboards, pages, reports) and throwing an error for unrecognized methods, similar to the throw statement at line 92 for the data service.

Suggested change
return protocol.getMetaItems({ type: method, packageId: params.packageId });
switch (method) {
case 'objects':
case 'apps':
case 'dashboards':
case 'pages':
case 'reports':
return protocol.getMetaItems({ type: method, packageId: params.packageId });
default:
throw new Error(`[BrokerShim] Unhandled metadata method: ${method} in action: ${action}`);
}

Copilot uses AI. Check for mistakes.
case 'delete':
return protocol.deleteData({ object: params.object, id: params.id });
case 'batch':
return protocol.batchData?.({ object: params.object, ...params }) ?? { results: [] };
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Missing default case in data service switch: The switch statement for data methods (query, get, create, update, delete, batch) doesn't have a default case to handle unrecognized methods. If an unrecognized data method is called, execution falls through to the metadata service checks, which could return incorrect results. Add a default case in the switch statement that throws an error for unhandled data methods.

Suggested change
return protocol.batchData?.({ object: params.object, ...params }) ?? { results: [] };
return protocol.batchData?.({ object: params.object, ...params }) ?? { results: [] };
default:
throw new Error(`[BrokerShim] Unhandled data method: ${method}`);

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +133
// Re-install broker shim after bootstrap to ensure the protocol service
// is fully initialised (some plugins register services during start phase).
if (mswOptions) {
await installBrokerShim(kernel);
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The broker shim is installed twice: before MSWPlugin is added (line 121) and after bootstrap (lines 131-132). While the comment explains this is to ensure the protocol service is fully initialized, this pattern is fragile and could mask initialization order issues. Consider one of these alternatives:

  1. Only install the shim after bootstrap (remove line 121), which should be sufficient since MSWPlugin's handlers are only registered during bootstrap.
  2. Add a check in installBrokerShim to avoid reinstalling if the broker already exists and is functional.
  3. Document why both installations are necessary with a more detailed explanation of what changes between the two calls.

Copilot uses AI. Check for mistakes.
}
if (!protocol) return;

(kernel as any).broker = {
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Type safety issue: Casting kernel to any to attach the broker property bypasses TypeScript's type checking. This could hide errors if the ObjectKernel interface changes or if MSWPlugin expects specific broker interface methods beyond just call().

Consider creating a proper interface for the broker shim and using type assertion to a union type (e.g., kernel as ObjectKernel & { broker: BrokerShim }) rather than casting to any.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +97
it('should support filter via HTTP', async () => {
// Filter contacts where priority = "high" using tuple format
const filter = JSON.stringify(['priority', '=', 'high']);
const res = await fetch(`http://localhost/api/v1/data/contact?filter=${encodeURIComponent(filter)}`);
expect(res.ok).toBe(true);
const body = await res.json();

const data = body.data ?? body;
const records = data.value ?? data.records ?? data;
expect(Array.isArray(records)).toBe(true);
expect(records.length).toBeGreaterThan(0);
// All returned records should have priority 'high'
for (const record of records) {
expect(record.priority).toBe('high');
}
});
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The test validates that records have priority='high' but doesn't verify that records with other priorities (e.g., 'medium', 'low') are excluded. Consider adding an assertion that verifies the total count matches expected filtered results, or seed known data with multiple priorities and verify only 'high' priority records are returned.

For example, you could check that the returned count is less than the total count of contacts, or verify specific known 'high' priority contacts are present while known 'medium' ones are absent.

Copilot uses AI. Check for mistakes.
for (const record of records) {
expect(record.priority).toBe('high');
}
});
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Test coverage is missing for the skip query parameter, which is mentioned as one of the key features in the PR description and test file comments (line 6 mentions "skip" alongside filter/sort/top). Consider adding a test case that validates pagination using the skip parameter works correctly through the protocol stack.

Suggested change
});
});
it('should support skip (offset) via HTTP', async () => {
// Use sort=name to ensure a deterministic ordering, then apply skip=2
const baseRes = await fetch('http://localhost/api/v1/data/contact?sort=name');
expect(baseRes.ok).toBe(true);
const baseBody = await baseRes.json();
const baseData = baseBody.data ?? baseBody;
const baseRecords = baseData.value ?? baseData.records ?? baseData;
expect(Array.isArray(baseRecords)).toBe(true);
expect(baseRecords.length).toBeGreaterThan(2);
const skipRes = await fetch('http://localhost/api/v1/data/contact?sort=name&skip=2');
expect(skipRes.ok).toBe(true);
const skipBody = await skipRes.json();
const skipData = skipBody.data ?? skipBody;
const skipRecords = skipData.value ?? skipData.records ?? skipData;
expect(Array.isArray(skipRecords)).toBe(true);
expect(skipRecords.length).toBeGreaterThan(0);
// After skipping 2 records, the first record in the skip result
// should match the third record from the base (index 2)
expect(skipRecords[0].id).toBe(baseRecords[2].id);
});

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console MSW handler should use MSWPlugin, not custom HTTP handlers; filter/sort/top fail otherwise

3 participants