Skip to content

Commit a7832e4

Browse files
Copilothotlong
andcommitted
Add MSW browser setup and update Storybook configuration
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 0f2d3b7 commit a7832e4

File tree

6 files changed

+165
-87
lines changed

6 files changed

+165
-87
lines changed

.storybook/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const config: StorybookConfig = {
2727
'@object-ui/components': path.resolve(__dirname, '../packages/components/src/index.ts'),
2828
'@object-ui/fields': path.resolve(__dirname, '../packages/fields/src/index.tsx'),
2929
'@object-ui/layout': path.resolve(__dirname, '../packages/layout/src/index.ts'),
30+
// Alias example packages for Storybook to resolve them from workspace
31+
'@object-ui/example-crm': path.resolve(__dirname, '../examples/crm/src/index.ts'),
3032
// Alias plugin packages for Storybook to resolve them from workspace
3133
'@object-ui/plugin-aggrid': path.resolve(__dirname, '../packages/plugin-aggrid/src/index.tsx'),
3234
'@object-ui/plugin-calendar': path.resolve(__dirname, '../packages/plugin-calendar/src/index.tsx'),

.storybook/mocks.ts

Lines changed: 15 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,21 @@
11
// .storybook/mocks.ts
22
import { http, HttpResponse } from 'msw';
3-
import { ObjectStackServer } from '@objectstack/plugin-msw';
43

5-
export const protocol = {
6-
objects: [
7-
{
8-
name: "contact",
9-
fields: {
10-
name: { type: "text" },
11-
email: { type: "email" },
12-
title: { type: "text" },
13-
company: { type: "text" },
14-
status: { type: "select", options: ["Active", "Lead", "Customer"] }
15-
}
16-
},
17-
{
18-
name: "opportunity",
19-
fields: {
20-
name: { type: "text" },
21-
amount: { type: "currency" },
22-
stage: { type: "select" },
23-
closeDate: { type: "date" },
24-
accountId: { type: "lookup", reference_to: "account" }
25-
}
26-
},
27-
{
28-
name: "account",
29-
fields: {
30-
name: { type: "text" },
31-
industry: { type: "text" }
32-
}
33-
}
34-
]
35-
};
36-
37-
// Initialize the mock server
38-
// @ts-ignore
39-
ObjectStackServer.init(protocol);
40-
41-
// Seed basic data
42-
(async () => {
43-
await ObjectStackServer.createData('contact', { id: '1', name: 'John Doe', title: 'Developer', company: 'Tech', status: 'Active' });
44-
await ObjectStackServer.createData('contact', { id: '2', name: 'Jane Smith', title: 'Manager', company: 'Corp', status: 'Customer' });
45-
await ObjectStackServer.createData('account', { id: '1', name: 'Big Corp', industry: 'Finance' });
46-
await ObjectStackServer.createData('opportunity', { id: '1', name: 'Big Deal', amount: 50000, stage: 'Negotiation', accountId: '1' });
47-
})();
4+
/**
5+
* MSW Handlers for Storybook
6+
*
7+
* Note: The main MSW runtime with ObjectStack kernel is initialized in msw-browser.ts
8+
* These handlers are additional story-specific handlers that can be used
9+
* via the msw parameter in individual stories.
10+
*
11+
* The ObjectStack kernel handles standard CRUD operations automatically via MSWPlugin.
12+
*/
4813

4914
export const handlers = [
50-
// Standard CRUD handlers using ObjectStackServer
51-
http.get('/api/v1/data/:object', async ({ params }) => {
52-
const { object } = params;
53-
const result = await ObjectStackServer.findData(object as string);
54-
return HttpResponse.json({ value: result.data });
55-
}),
56-
57-
http.get('/api/v1/data/:object/:id', async ({ params }) => {
58-
const { object, id } = params;
59-
const result = await ObjectStackServer.getData(object as string, id as string);
60-
return HttpResponse.json(result.data);
61-
}),
62-
63-
http.post('/api/v1/data/:object', async ({ params, request }) => {
64-
const { object } = params;
65-
const body = await request.json();
66-
const result = await ObjectStackServer.createData(object as string, body);
67-
return HttpResponse.json(result.data);
68-
}),
69-
70-
// Custom bootstrap if needed
71-
http.get('/api/bootstrap', async () => {
72-
const contacts = (await ObjectStackServer.findData('contact')).data;
73-
return HttpResponse.json({ contacts });
74-
})
15+
// Additional custom handlers can be added here for specific story needs
16+
// The ObjectStack MSW runtime already handles:
17+
// - /api/v1/data/:object (GET, POST)
18+
// - /api/v1/data/:object/:id (GET, PUT, DELETE)
19+
// - /api/v1/metadata/*
20+
// - /api/bootstrap
7521
];

.storybook/msw-browser.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* MSW Browser Setup for Storybook
3+
*
4+
* This file integrates the ObjectStack runtime with MSW in browser mode
5+
* for use within Storybook stories. Based on the pattern from examples/crm-app.
6+
*/
7+
8+
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
9+
import { ObjectQLPlugin } from '@objectstack/objectql';
10+
import { InMemoryDriver } from '@objectstack/driver-memory';
11+
import { MSWPlugin } from '@objectstack/plugin-msw';
12+
import { config as crmConfig } from '@object-ui/example-crm';
13+
import { http, HttpResponse } from 'msw';
14+
15+
let kernel: ObjectKernel | null = null;
16+
17+
export async function startMockServer() {
18+
if (kernel) return kernel;
19+
20+
console.log('[Storybook MSW] Starting ObjectStack Runtime (Browser Mode)...');
21+
console.log('[Storybook MSW] Loaded Config:', crmConfig ? 'Found' : 'Missing', crmConfig?.apps?.length);
22+
23+
if (crmConfig && crmConfig.objects) {
24+
console.log('[Storybook MSW] Objects in Config:', crmConfig.objects.map(o => o.name));
25+
} else {
26+
console.error('[Storybook MSW] No objects found in config!');
27+
}
28+
29+
const driver = new InMemoryDriver();
30+
kernel = new ObjectKernel();
31+
32+
try {
33+
kernel
34+
.use(new ObjectQLPlugin())
35+
.use(new DriverPlugin(driver, 'memory'));
36+
37+
if (crmConfig) {
38+
kernel.use(new AppPlugin(crmConfig));
39+
} else {
40+
console.error('❌ CRM Config is missing! Skipping AppPlugin.');
41+
}
42+
43+
kernel.use(new MSWPlugin({
44+
enableBrowser: true,
45+
baseUrl: '/api/v1',
46+
logRequests: true,
47+
customHandlers: [
48+
// Explicitly handle all metadata requests to prevent pass-through
49+
http.get('/api/v1/metadata/*', async () => {
50+
return HttpResponse.json({});
51+
}),
52+
http.get('/api/bootstrap', async () => {
53+
const contacts = await driver.find('contact', { object: 'contact' });
54+
const stats = { revenue: 125000, leads: 45, deals: 12 };
55+
return HttpResponse.json({
56+
user: { name: "Demo User", role: "admin" },
57+
stats,
58+
contacts: contacts || []
59+
});
60+
})
61+
]
62+
}));
63+
64+
console.log('[Storybook MSW] Bootstrapping kernel...');
65+
await kernel.bootstrap();
66+
console.log('[Storybook MSW] Bootstrap Complete');
67+
68+
// Seed Data
69+
if (crmConfig) {
70+
await initializeMockData(driver);
71+
}
72+
} catch (err: any) {
73+
console.error('❌ Storybook Mock Server Start Failed:', err);
74+
throw err;
75+
}
76+
77+
return kernel;
78+
}
79+
80+
// Helper to seed data into the in-memory driver
81+
async function initializeMockData(driver: InMemoryDriver) {
82+
console.log('[Storybook MSW] Initializing mock data...');
83+
// @ts-ignore
84+
const manifest = crmConfig.manifest;
85+
if (manifest && manifest.data) {
86+
for (const dataSet of manifest.data) {
87+
console.log(`[Storybook MSW] Seeding ${dataSet.object}...`);
88+
if (dataSet.records) {
89+
for (const record of dataSet.records) {
90+
await driver.create(dataSet.object, record);
91+
}
92+
}
93+
}
94+
}
95+
}
96+
97+
export { kernel };

.storybook/preview.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Preview } from '@storybook/react-vite'
22
import { initialize, mswLoader } from 'msw-storybook-addon';
33
import { handlers } from './mocks';
4+
import { startMockServer } from './msw-browser';
45
import '../packages/components/src/index.css';
56
import { ComponentRegistry } from '@object-ui/core';
67
import * as components from '../packages/components/src/index';
@@ -10,6 +11,14 @@ initialize({
1011
onUnhandledRequest: 'bypass'
1112
});
1213

14+
// Start MSW runtime with ObjectStack kernel
15+
// This must be called during Storybook initialization
16+
if (typeof window !== 'undefined') {
17+
startMockServer().catch(err => {
18+
console.error('Failed to start MSW runtime:', err);
19+
});
20+
}
21+
1322
// Register all base components for Storybook
1423
Object.values(components);
1524

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@
5050
"devDependencies": {
5151
"@changesets/cli": "^2.29.8",
5252
"@eslint/js": "^9.39.1",
53+
"@objectstack/core": "^0.6.1",
54+
"@objectstack/driver-memory": "^0.6.1",
55+
"@objectstack/objectql": "^0.6.1",
5356
"@objectstack/plugin-msw": "^0.6.1",
57+
"@objectstack/runtime": "^0.6.1",
5458
"@storybook/addon-essentials": "^8.6.14",
5559
"@storybook/addon-interactions": "^8.6.14",
5660
"@storybook/addon-links": "^8.6.15",

0 commit comments

Comments
 (0)