@chatbi-v/mocks 1.0.1

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,10 @@
1
+ import { MockSchema } from './types';
2
+ declare global {
3
+ interface Window {
4
+ _originalFetch: typeof fetch;
5
+ _originalEventSource: typeof EventSource;
6
+ }
7
+ }
8
+ export declare function installFetchMock(schema: MockSchema): void;
9
+ export declare function installSSEMock(): void;
10
+ export declare function setupMock(schema: MockSchema): void;
@@ -0,0 +1,67 @@
1
+ import { StrategyFactory } from './strategies';
2
+ import { eventsToStream, sleep } from './utils';
3
+ export function installFetchMock(schema) {
4
+ if (!window._originalFetch) {
5
+ window._originalFetch = window.fetch;
6
+ }
7
+ window.fetch = async (input, init = {}) => {
8
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
9
+ const method = (init.method || 'GET').toUpperCase();
10
+ // Find matching rule
11
+ const rules = Object.values(schema);
12
+ const rule = rules.find((v) => {
13
+ if (!v.url)
14
+ return false;
15
+ // Convert route params :id to regex \w+
16
+ // We might want to be more specific, e.g., ([^/]+)
17
+ const pattern = v.url.replace(/:[a-zA-Z0-9_]+/g, '([^/]+)');
18
+ const regex = new RegExp(`^${pattern}$`);
19
+ // Check if URL matches (ignoring query params for the match, but we might need them later)
20
+ const [path] = url.split('?');
21
+ // Match method if specified, default to GET match? or just match URL?
22
+ // Usually method matters.
23
+ const ruleMethod = (v.method || 'GET').toUpperCase();
24
+ return regex.test(path) && ruleMethod === method;
25
+ });
26
+ if (!rule) {
27
+ return window._originalFetch(input, init);
28
+ }
29
+ console.log(`[Mock] Intercepted ${method} ${url}`, rule);
30
+ await sleep(rule.delay || 0);
31
+ // Determine type (default 'json' if not present)
32
+ const type = rule.type || 'json';
33
+ const strategy = StrategyFactory.getStrategy(type);
34
+ // Parse query params for strategies that need them
35
+ const urlObj = new URL(url, window.location.origin);
36
+ const query = Object.fromEntries(urlObj.searchParams);
37
+ const result = strategy.process(rule, query);
38
+ /* 1. JSON Response */
39
+ if (type === 'json') {
40
+ return new Response(JSON.stringify(result), {
41
+ status: 200,
42
+ headers: { 'Content-Type': 'application/json' },
43
+ });
44
+ }
45
+ /* 2. SSE / SSE-Page Response */
46
+ if (type === 'sse' || type === 'sse-page') {
47
+ const events = result; // Strategy returns MockEvent[]
48
+ const stream = eventsToStream(events);
49
+ return new Response(stream, {
50
+ status: 200,
51
+ headers: { 'Content-Type': 'text/event-stream' }
52
+ });
53
+ }
54
+ return window._originalFetch(input, init);
55
+ };
56
+ }
57
+ export function installSSEMock() {
58
+ // Placeholder for EventSource mocking if needed.
59
+ // Currently, we rely on fetch interception.
60
+ // Many modern apps use fetch for SSE (POST requests, headers, etc.)
61
+ }
62
+ export function setupMock(schema) {
63
+ if (typeof window !== 'undefined') {
64
+ installFetchMock(schema);
65
+ installSSEMock();
66
+ }
67
+ }
@@ -0,0 +1,20 @@
1
+ import { MockConfig, MockEvent, MockGeneratorStrategy, // Legacy
2
+ MockStrategy } from './types';
3
+ export declare class ChatStreamStrategy implements MockGeneratorStrategy {
4
+ generate(schema: any): string[];
5
+ }
6
+ export declare class HistoryStreamStrategy implements MockGeneratorStrategy {
7
+ generate(schema: any): string[];
8
+ }
9
+ export declare class JsonStrategy implements MockStrategy {
10
+ process(config: MockConfig, _requestParams?: any): any;
11
+ }
12
+ export declare class SseStrategy implements MockStrategy {
13
+ process(config: MockConfig, requestParams?: any): MockEvent[];
14
+ }
15
+ export declare class SsePageStrategy implements MockStrategy {
16
+ process(config: MockConfig, requestParams?: any): MockEvent[];
17
+ }
18
+ export declare const StrategyFactory: {
19
+ getStrategy(type?: string): MockStrategy;
20
+ };
@@ -0,0 +1,127 @@
1
+ import Mock from 'mockjs';
2
+ import { MockResponseGenerator } from './generator';
3
+ import { flatEvents, processTemplate } from './utils';
4
+ // === Legacy Strategies (Preserved) ===
5
+ export class ChatStreamStrategy {
6
+ generate(schema) {
7
+ const config = schema;
8
+ const defaultName = config.agentName || 'BI助手';
9
+ const generator = new MockResponseGenerator(defaultName);
10
+ const mockData = config.data ? Mock.mock(config.data) : {};
11
+ // 1. Init Plan
12
+ if (config.plan) {
13
+ generator.initPlan(config.plan);
14
+ }
15
+ // 2. Execute Timeline
16
+ if (config.timeline) {
17
+ config.timeline.forEach(step => {
18
+ if (step.planIndex !== undefined) {
19
+ generator.updatePlanStatus(step.planIndex);
20
+ }
21
+ if (step.log) {
22
+ const logContent = typeof step.log === 'string' ? step.log : step.log.content;
23
+ const logLevel = typeof step.log === 'object' ? step.log.level : 'info';
24
+ const logAgent = typeof step.log === 'object' ? step.log.agent : undefined;
25
+ generator.addLog(logContent, logLevel, logAgent);
26
+ }
27
+ });
28
+ }
29
+ // 3. Content
30
+ let content;
31
+ if (typeof config.content === 'function') {
32
+ content = config.content(mockData);
33
+ }
34
+ else {
35
+ content = config.content;
36
+ }
37
+ if (typeof content === 'object' && content !== null) {
38
+ generator.emitA2UI(content);
39
+ }
40
+ else {
41
+ generator.streamContent(content);
42
+ }
43
+ // 4. Complete
44
+ generator.completePlan();
45
+ generator.finish();
46
+ return generator.toString().split('\n\n').map(chunk => chunk + '\n\n');
47
+ }
48
+ }
49
+ export class HistoryStreamStrategy {
50
+ generate(schema) {
51
+ const config = schema;
52
+ const generator = new MockResponseGenerator('BI助手');
53
+ const historyMock = Mock.mock(config.template);
54
+ const fullHistory = [...(config.prepend || []), ...(historyMock.list || historyMock)];
55
+ generator.emitHistory(fullHistory);
56
+ return generator.toString().split('\n\n').map(chunk => chunk + '\n\n');
57
+ }
58
+ }
59
+ // === New Schema Strategies ===
60
+ export class JsonStrategy {
61
+ process(config, _requestParams) {
62
+ const jsonConfig = config;
63
+ if (!jsonConfig.responseSchema)
64
+ return {};
65
+ return Mock.mock(jsonConfig.responseSchema);
66
+ }
67
+ }
68
+ export class SseStrategy {
69
+ process(config, requestParams = {}) {
70
+ const sseConfig = config;
71
+ if (!sseConfig.responseSchema)
72
+ return [];
73
+ // Use flatEvents to process the schema with MockJS and sort by delay
74
+ return flatEvents(sseConfig.responseSchema, requestParams);
75
+ }
76
+ }
77
+ export class SsePageStrategy {
78
+ process(config, requestParams = {}) {
79
+ const ssePageConfig = config;
80
+ if (!ssePageConfig.responseSchema)
81
+ return [];
82
+ // 1. Generate base events
83
+ const events = flatEvents(ssePageConfig.responseSchema, requestParams);
84
+ // 2. Generate page event if exists
85
+ if (ssePageConfig.pageEvent) {
86
+ Mock.Random.extend({ $query: () => requestParams });
87
+ // First let Mock.js process standard templates (like @integer)
88
+ let pageEvent = Mock.mock(ssePageConfig.pageEvent);
89
+ // Then process custom {{}} templates
90
+ pageEvent = processTemplate(pageEvent, { $query: requestParams });
91
+ // Calculate max delay from existing events to ensure page event is last
92
+ let maxDelay = 0;
93
+ if (events.length > 0) {
94
+ const lastEvent = events[events.length - 1];
95
+ maxDelay = typeof lastEvent.delay === 'number' ? lastEvent.delay : parseInt(String(lastEvent.delay || 0));
96
+ }
97
+ // Set page event delay to be slightly after the last event
98
+ const pageDelay = typeof pageEvent.delay === 'number' ? pageEvent.delay : parseInt(String(pageEvent.delay || 0));
99
+ if (pageDelay <= maxDelay) {
100
+ pageEvent.delay = maxDelay + 20; // Add small buffer
101
+ }
102
+ events.push(pageEvent);
103
+ }
104
+ // Re-sort in case pageEvent has specific delay
105
+ const es = events.sort((a, b) => {
106
+ const delayA = typeof a.delay === 'number' ? a.delay : parseInt(String(a.delay || 0));
107
+ const delayB = typeof b.delay === 'number' ? b.delay : parseInt(String(b.delay || 0));
108
+ return delayA - delayB;
109
+ });
110
+ console.log(es);
111
+ return es;
112
+ }
113
+ }
114
+ // Factory for new strategies
115
+ export const StrategyFactory = {
116
+ getStrategy(type = 'json') {
117
+ switch (type) {
118
+ case 'sse':
119
+ return new SseStrategy();
120
+ case 'sse-page':
121
+ return new SsePageStrategy();
122
+ case 'json':
123
+ default:
124
+ return new JsonStrategy();
125
+ }
126
+ }
127
+ };
@@ -0,0 +1,75 @@
1
+ export type MockMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
2
+ export type MockType = 'json' | 'sse' | 'sse-page';
3
+ export interface MockEvent {
4
+ event: string;
5
+ data: any;
6
+ /** Delay in ms, or a mockjs template string like '@increment(100)' */
7
+ delay?: number | string;
8
+ }
9
+ export interface MockStrategy {
10
+ /**
11
+ * Process the schema and return the result.
12
+ * For JSON: returns the mocked object.
13
+ * For SSE: returns an array of formatted event strings (chunks).
14
+ */
15
+ process(config: MockConfig, requestParams?: any): any;
16
+ }
17
+ export interface BaseMockConfig {
18
+ /** Optional: URL pattern if used in global interceptor */
19
+ url?: string;
20
+ /** Optional: Method if used in global interceptor */
21
+ method?: MockMethod;
22
+ /** Global delay for the request */
23
+ delay?: number;
24
+ /** Mock type (default: 'json') */
25
+ type?: MockType;
26
+ }
27
+ export interface JsonMockConfig extends BaseMockConfig {
28
+ type?: 'json';
29
+ /** The MockJS template for the response body */
30
+ responseSchema: any;
31
+ }
32
+ export interface SseMockConfig extends BaseMockConfig {
33
+ type: 'sse';
34
+ /**
35
+ * Dictionary of event templates.
36
+ * Key can be a mockjs template like 'data|3-5'
37
+ */
38
+ responseSchema: Record<string, MockEvent[]>;
39
+ }
40
+ export interface SsePageMockConfig extends BaseMockConfig {
41
+ type: 'sse-page';
42
+ /** Dictionary of event templates */
43
+ responseSchema: Record<string, MockEvent[]>;
44
+ /** Special event for pagination metadata */
45
+ pageEvent: MockEvent;
46
+ }
47
+ export type MockConfig = JsonMockConfig | SseMockConfig | SsePageMockConfig;
48
+ export type MockSchema = Record<string, MockConfig>;
49
+ export interface ChatStreamConfig {
50
+ _type: 'chat_stream';
51
+ agentName?: string;
52
+ plan?: string[];
53
+ data?: Record<string, any>;
54
+ timeline?: Array<{
55
+ log?: string | {
56
+ content: string;
57
+ level?: 'info' | 'warning' | 'error';
58
+ agent?: string;
59
+ };
60
+ planIndex?: number;
61
+ delay?: number;
62
+ }>;
63
+ content: string | Record<string, any> | ((data: any) => string | Record<string, any>);
64
+ }
65
+ export interface HistoryStreamConfig {
66
+ _type: 'history_stream';
67
+ template: Record<string, any>;
68
+ prepend?: any[];
69
+ }
70
+ export type AdvancedMockSchema = ChatStreamConfig | HistoryStreamConfig;
71
+ export interface MockGeneratorStrategy {
72
+ generate(schema: any): string[];
73
+ }
74
+ export declare function createChatStream(config: Omit<ChatStreamConfig, '_type'>): ChatStreamConfig;
75
+ export declare function createHistoryStream(config: Omit<HistoryStreamConfig, '_type'>): HistoryStreamConfig;
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ // === Helper Functions (Restored) ===
2
+ export function createChatStream(config) {
3
+ return { _type: 'chat_stream', ...config };
4
+ }
5
+ export function createHistoryStream(config) {
6
+ return { _type: 'history_stream', ...config };
7
+ }
@@ -0,0 +1,20 @@
1
+ import { MockEvent } from './types';
2
+ export declare const sleep: (ms: number) => Promise<unknown>;
3
+ /**
4
+ * Process string templates like "{{$query.pageNo * 10}}" in object values.
5
+ * Uses simple evaluation with limited context.
6
+ */
7
+ export declare function processTemplate(data: any, context: Record<string, any>): any;
8
+ /**
9
+ * Flatten eventsSchema into a sorted array of events.
10
+ * It also extends Mock.Random with $query to access URL parameters.
11
+ *
12
+ * Supports Mock.js array generation rules in keys, e.g.:
13
+ * { 'data|8-12': [{ event: 'data', ... }] }
14
+ * -> generates 8 to 12 data events.
15
+ */
16
+ export declare function flatEvents(eventsSchema: Record<string, MockEvent[]>, query?: Record<string, string>): MockEvent[];
17
+ /**
18
+ * Convert an array of events into a ReadableStream (for SSE).
19
+ */
20
+ export declare function eventsToStream(events: MockEvent[]): ReadableStream;
package/dist/utils.js ADDED
@@ -0,0 +1,103 @@
1
+ import dayjs from 'dayjs';
2
+ import Mock from 'mockjs';
3
+ export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
4
+ /**
5
+ * Process string templates like "{{$query.pageNo * 10}}" in object values.
6
+ * Uses simple evaluation with limited context.
7
+ */
8
+ export function processTemplate(data, context) {
9
+ if (typeof data === 'string') {
10
+ // Check for {{ ... }} pattern
11
+ if (/^\{\{.*\}\}$/.test(data)) {
12
+ const expression = data.slice(2, -2).trim();
13
+ try {
14
+ // Safe evaluation using Function with context keys
15
+ const keys = Object.keys(context);
16
+ const values = Object.values(context);
17
+ const fn = new Function(...keys, `return ${expression}`);
18
+ return fn(...values);
19
+ }
20
+ catch (e) {
21
+ console.warn(`[Mock] Failed to evaluate template: ${data}`, e);
22
+ return data;
23
+ }
24
+ }
25
+ return data;
26
+ }
27
+ if (Array.isArray(data)) {
28
+ return data.map(item => processTemplate(item, context));
29
+ }
30
+ if (typeof data === 'object' && data !== null) {
31
+ const result = {};
32
+ for (const key in data) {
33
+ result[key] = processTemplate(data[key], context);
34
+ }
35
+ return result;
36
+ }
37
+ return data;
38
+ }
39
+ /**
40
+ * Flatten eventsSchema into a sorted array of events.
41
+ * It also extends Mock.Random with $query to access URL parameters.
42
+ *
43
+ * Supports Mock.js array generation rules in keys, e.g.:
44
+ * { 'data|8-12': [{ event: 'data', ... }] }
45
+ * -> generates 8 to 12 data events.
46
+ */
47
+ export function flatEvents(eventsSchema, query = {}) {
48
+ // Extend Mock.Random to support $query
49
+ Mock.Random.extend({
50
+ $query: function () {
51
+ return query;
52
+ }
53
+ });
54
+ // Generate data using Mock.mock
55
+ // This handles keys like 'data|8-12' to generate arrays
56
+ const compiled = Mock.mock(eventsSchema);
57
+ // Flatten and sort by delay
58
+ return Object.values(compiled)
59
+ .flat()
60
+ .map(event => processTemplate(event, { $query: query })) // Apply template replacement
61
+ .sort((a, b) => {
62
+ const delayA = typeof a.delay === 'number' ? a.delay : parseInt(String(a.delay || 0));
63
+ const delayB = typeof b.delay === 'number' ? b.delay : parseInt(String(b.delay || 0));
64
+ return delayA - delayB;
65
+ });
66
+ }
67
+ /**
68
+ * Convert an array of events into a ReadableStream (for SSE).
69
+ */
70
+ export function eventsToStream(events) {
71
+ const encoder = new TextEncoder();
72
+ return new ReadableStream({
73
+ start(ctrl) {
74
+ // Check if events is empty
75
+ if (!events || events.length === 0) {
76
+ ctrl.close();
77
+ return;
78
+ }
79
+ (async () => {
80
+ try {
81
+ // We assume events are sorted by their `delay` property which represents
82
+ // "absolute time from start".
83
+ const startTime = dayjs().valueOf();
84
+ for (const eventItem of events) {
85
+ const delay = typeof eventItem.delay === 'number' ? eventItem.delay : parseInt(String(eventItem.delay || 0));
86
+ // Calculate how much time passed since start
87
+ const elapsed = dayjs().valueOf() - startTime;
88
+ const remaining = Math.max(0, delay - elapsed);
89
+ if (remaining > 0) {
90
+ await sleep(remaining);
91
+ }
92
+ const payload = `event: ${eventItem.event}\ndata: ${JSON.stringify(eventItem.data)}\n\n`;
93
+ ctrl.enqueue(encoder.encode(payload));
94
+ }
95
+ ctrl.close();
96
+ }
97
+ catch (e) {
98
+ ctrl.error(e);
99
+ }
100
+ })();
101
+ },
102
+ });
103
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@chatbi-v/mocks",
3
+ "version": "1.0.1",
4
+ "main": "dist/index.cjs",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "module": "dist/index.mjs",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.mjs",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "dependencies": {
21
+ "mockjs": "^1.1.0",
22
+ "@chatbi-v/core": "1.0.2"
23
+ },
24
+ "peerDependencies": {
25
+ "@chatbi-v/cli": "1.0.2"
26
+ },
27
+ "devDependencies": {
28
+ "@types/mockjs": "^1.0.10",
29
+ "tsup": "^8.5.1",
30
+ "@chatbi-v/tsconfig": "1.0.2"
31
+ },
32
+ "scripts": {
33
+ "build": "chatbi-cli build",
34
+ "dev": "chatbi-cli build --watch",
35
+ "test": "echo \"Error: no test specified\" && exit 1"
36
+ }
37
+ }