Skip to content

Commit 4b35b15

Browse files
committed
feat: implement createKernel and simulateBrowser functions for enhanced testing and kernel setup
1 parent 62c44e7 commit 4b35b15

4 files changed

Lines changed: 340 additions & 392 deletions

File tree

examples/app-react-crud/src/mocks/browser.ts

Lines changed: 7 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@
44
* This creates a complete ObjectStack environment in the browser using the In-Memory Driver
55
* and the MSW Plugin which automatically exposes the API.
66
*/
7-
8-
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
9-
import { ObjectQLPlugin, SchemaRegistry } from '@objectstack/objectql';
10-
import { InMemoryDriver } from '@objectstack/driver-memory';
11-
import { MSWPlugin } from '@objectstack/plugin-msw';
12-
// import appConfig from '../../objectstack.config';
7+
import { ObjectKernel } from '@objectstack/runtime';
138
import todoConfig from '@example/app-todo/objectstack.config';
9+
import { createKernel } from './createKernel';
1410

1511
let kernel: ObjectKernel | null = null;
1612

@@ -23,146 +19,12 @@ export async function startMockServer() {
2319
const appConfig = (todoConfig as any).default || todoConfig;
2420
console.log('[MSW] Loaded Config:', appConfig);
2521

26-
// DEBUG: Verify Data Existence
27-
const manifestData = appConfig.data || (appConfig.manifest && appConfig.manifest.data);
28-
if (manifestData && Array.isArray(manifestData)) {
29-
console.log(`[MSW] DATA DETECTED: Found ${manifestData.length} datasets in config.`);
30-
manifestData.forEach((d: any) => console.log(`[MSW] Dataset: object=${d.object}, records=${d.records?.length}`));
31-
} else {
32-
console.error('[MSW] CRITICAL: No initial data found in loaded config!', appConfig);
33-
}
34-
35-
const driver = new InMemoryDriver();
36-
37-
// Create kernel with MiniKernel architecture
38-
kernel = new ObjectKernel();
39-
40-
// Register ObjectQL engine
41-
await kernel.use(new ObjectQLPlugin());
42-
43-
// Register the driver
44-
await kernel.use(new DriverPlugin(driver, 'memory'));
45-
46-
// Load todo app config as a plugin
47-
await kernel.use(new AppPlugin(appConfig));
48-
49-
// MSW Plugin (intercepts network requests)
50-
await kernel.use(new MSWPlugin({
51-
enableBrowser: true,
52-
baseUrl: '/api/v1',
53-
logRequests: true
54-
}));
22+
// Use shared factory
23+
kernel = await createKernel({
24+
appConfig,
25+
enableBrowser: true
26+
});
5527

56-
// --- BROKER SHIM START ---
57-
// HttpDispatcher requires a broker to function. We inject a simple shim.
58-
(kernel as any).broker = {
59-
call: async (action: string, params: any, opts: any) => {
60-
const parts = action.split('.');
61-
const service = parts[0];
62-
const method = parts[1];
63-
64-
// Get Engines
65-
const ql = kernel!.context?.getService<any>('objectql');
66-
67-
if (service === 'data') {
68-
if (method === 'create') {
69-
const res = await ql.insert(params.object, params.data);
70-
return { ...params.data, ...res };
71-
}
72-
if (method === 'get') {
73-
// Manual filtering workaround for test/browser environment
74-
let all = await ql.find(params.object);
75-
if (!all) all = [];
76-
const match = all.find((i: any) => i.id === params.id || i._id === params.id);
77-
return match || null;
78-
}
79-
if (method === 'update') {
80-
// ql.update(obj, data, options) - inject ID into data
81-
return ql.update(params.object, { ...params.data, id: params.id });
82-
}
83-
if (method === 'delete') {
84-
// ql.delete(obj, options) - pass ID as filter
85-
return ql.delete(params.object, { filter: params.id });
86-
}
87-
if (method === 'find' || method === 'query') {
88-
// Manual filtering workaround
89-
let all = await ql.find(params.object);
90-
if (!all) all = [];
91-
92-
const filters = params.filters;
93-
if (filters && typeof filters === 'object' && !Array.isArray(filters)) {
94-
const keys = Object.keys(filters);
95-
if (keys.length > 0) {
96-
all = all.filter((item: any) => {
97-
return keys.every(k => item[k] === filters[k]);
98-
});
99-
}
100-
}
101-
102-
// HttpDispatcher expects { data, count } for query/list
103-
console.log(`[BrokerShim] find/query(${params.object}) -> count: ${all.length}`, all);
104-
return { data: all, count: all.length };
105-
}
106-
}
107-
108-
if (service === 'metadata') {
109-
if (method === 'objects') {
110-
// Try engine first
111-
let objs = ql && ql.getObjects ? ql.getObjects() : [];
112-
// Fallback to Registry if engine returns empty (likely delayed sync)
113-
if (objs.length === 0) {
114-
objs = SchemaRegistry.getAllObjects();
115-
}
116-
return objs;
117-
}
118-
if (method === 'getObject') {
119-
// Try Registry first for speed/correctness
120-
return SchemaRegistry.getObject(params.objectName) || (ql ? ql.getObject(params.objectName) : null);
121-
}
122-
}
123-
124-
console.warn(`[BrokerShim] Action not implemented: ${action}`);
125-
return null;
126-
}
127-
};
128-
// --- BROKER SHIM END ---
129-
130-
await kernel.bootstrap();
131-
132-
// DEBUG: Verify Seeding Result
133-
const ql = kernel.context?.getService<any>('objectql');
134-
if (ql) {
135-
setTimeout(async () => {
136-
try {
137-
const tasks = await ql.find('todo_task');
138-
console.log(`[MSW] POST-BOOTSTRAP VERIFICATION: Found ${tasks?.length} tasks in DB.`, tasks);
139-
} catch(e) {
140-
console.error('[MSW] Verification failed', e);
141-
}
142-
}, 500); // Small delay to ensure async seeding is definitely done (though bootstrap should cover it)
143-
}
144-
145-
// --- PROTOCOL SERVICE MOCK ---
146-
// Overwrite protocol service because the default one might be empty/broken in this env
147-
if (kernel.services instanceof Map) {
148-
kernel.services.set('protocol', {
149-
getUiView: async ({ object, type }: any) => {
150-
return {
151-
type: type || 'list',
152-
name: 'default',
153-
object: object,
154-
title: object,
155-
body: []
156-
};
157-
}
158-
});
159-
}
160-
161-
// Initialize default data from manifest if available
162-
// Data seeding is handled by AppPlugin automatically
163-
const manifest = appConfig.manifest;
164-
console.log('[MSW] Checking manifest for data...', manifest);
165-
16628
return kernel;
16729
}
16830

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
2+
import { ObjectQLPlugin, SchemaRegistry } from '@objectstack/objectql';
3+
import { InMemoryDriver } from '@objectstack/driver-memory';
4+
import { MSWPlugin } from '@objectstack/plugin-msw';
5+
6+
export interface KernelOptions {
7+
appConfig: any;
8+
enableBrowser?: boolean; // Default true (for browser usage), set false for tests
9+
}
10+
11+
export async function createKernel(options: KernelOptions) {
12+
const { appConfig, enableBrowser = true } = options;
13+
14+
console.log('[KernelFactory] Creating ObjectStack Kernel...');
15+
console.log('[KernelFactory] App Config:', appConfig);
16+
17+
const driver = new InMemoryDriver();
18+
const kernel = new ObjectKernel();
19+
20+
// Register ObjectQL engine
21+
await kernel.use(new ObjectQLPlugin());
22+
23+
// Register the driver
24+
await kernel.use(new DriverPlugin(driver, 'memory'));
25+
26+
// Load app config as a plugin (which handles Seeding)
27+
await kernel.use(new AppPlugin(appConfig));
28+
29+
// MSW Plugin
30+
await kernel.use(new MSWPlugin({
31+
enableBrowser: enableBrowser,
32+
baseUrl: '/api/v1',
33+
logRequests: true
34+
}));
35+
36+
// --- BROKER SHIM START ---
37+
// HttpDispatcher requires a broker to function. We inject a shim.
38+
(kernel as any).broker = {
39+
call: async (action: string, params: any, opts: any) => {
40+
const parts = action.split('.');
41+
const service = parts[0];
42+
const method = parts[1];
43+
44+
// Get Engines
45+
const ql = kernel!.context?.getService<any>('objectql');
46+
47+
if (service === 'data') {
48+
if (method === 'create') {
49+
const res = await ql.insert(params.object, params.data);
50+
return { ...params.data, ...res };
51+
}
52+
if (method === 'get') {
53+
let all = await ql.find(params.object);
54+
if (!all) all = [];
55+
const match = all.find((i: any) => i.id === params.id || i._id === params.id);
56+
return match || null;
57+
}
58+
if (method === 'update') {
59+
return ql.update(params.object, { ...params.data, id: params.id });
60+
}
61+
if (method === 'delete') {
62+
return ql.delete(params.object, { filter: params.id });
63+
}
64+
if (method === 'find' || method === 'query') {
65+
let all = await ql.find(params.object);
66+
67+
// DEBUG SHIM
68+
// console.log(`[BrokerShim debug] Incoming Params:`, JSON.stringify(params, null, 2));
69+
// console.log(`[BrokerShim debug] Raw ql.find result type: ${typeof all}, isArray: ${Array.isArray(all)}, value:`, all);
70+
71+
// Handle PaginatedResult { value: [...] } vs Array [...]
72+
if (!Array.isArray(all) && all && (all as any).value) {
73+
console.log('[BrokerShim debug] Detected PaginatedResult wrapper, unwrapping .value');
74+
all = (all as any).value;
75+
}
76+
77+
if (!all) all = [];
78+
79+
const filters = params.filters;
80+
if (filters && typeof filters === 'object' && !Array.isArray(filters)) {
81+
// Filter out reserved query parameters that are NOT field names
82+
const reserved = ['top', 'skip', 'sort', 'select', 'expand', 'count', 'search'];
83+
const keys = Object.keys(filters).filter(k => !reserved.includes(k));
84+
85+
if (keys.length > 0) {
86+
console.log('[BrokerShim debug] Applying filters:', keys);
87+
all = all.filter((item: any) => {
88+
return keys.every(k => {
89+
// Loose equality check
90+
return String(item[k]) == String(filters[k]);
91+
});
92+
});
93+
}
94+
}
95+
console.log(`[BrokerShim] find/query(${params.object}) -> count: ${all.length}`);
96+
return { data: all, count: all.length };
97+
}
98+
}
99+
100+
if (service === 'metadata') {
101+
if (method === 'objects') {
102+
let objs = ql && ql.getObjects ? ql.getObjects() : [];
103+
if (objs.length === 0) {
104+
objs = SchemaRegistry.getAllObjects();
105+
}
106+
return objs;
107+
}
108+
if (method === 'getObject') {
109+
return SchemaRegistry.getObject(params.objectName) || (ql ? ql.getObject(params.objectName) : null);
110+
}
111+
}
112+
113+
console.warn(`[BrokerShim] Action not implemented: ${action}`);
114+
return null;
115+
}
116+
};
117+
// --- BROKER SHIM END ---
118+
119+
await kernel.bootstrap();
120+
121+
// FORCE SYNC SEED: Guarantees data availability for both Browser and Tests
122+
const ql = kernel.context?.getService<any>('objectql');
123+
if (ql) {
124+
// Initial check
125+
let tasks = await ql.find('todo_task');
126+
127+
// If AppPlugin's async seeding hasn't finished or failed, do it manually now.
128+
if (!tasks || tasks.length === 0) {
129+
console.warn('[KernelFactory] Seeding check failed. Executing IMMEDIATE Manual Seeding...');
130+
131+
const manifestData = appConfig.data || (appConfig.manifest && appConfig.manifest.data);
132+
if (manifestData && Array.isArray(manifestData)) {
133+
for (const dataset of manifestData) {
134+
if (dataset.records) {
135+
console.log(`[KernelFactory] Manual Seeding ${dataset.records.length} records for ${dataset.object}`);
136+
for (const record of dataset.records) {
137+
await ql.insert(dataset.object, record);
138+
}
139+
}
140+
}
141+
}
142+
// Verify
143+
tasks = await ql.find('todo_task');
144+
console.log(`[KernelFactory] Manual Seeding Complete. Count in DB: ${tasks?.length}`);
145+
} else {
146+
console.log(`[KernelFactory] Data verified present: ${tasks?.length} records.`);
147+
}
148+
}
149+
150+
// --- PROTOCOL SERVICE MOCK ---
151+
if (kernel.services instanceof Map) {
152+
kernel.services.set('protocol', {
153+
getUiView: async ({ object, type }: any) => {
154+
return {
155+
type: type || 'list',
156+
name: 'default',
157+
object: object,
158+
title: object,
159+
body: []
160+
};
161+
}
162+
});
163+
}
164+
165+
return kernel;
166+
}

0 commit comments

Comments
 (0)