lambder 1.0.131 → 1.0.133
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.
- package/Readme.md +24 -37
- package/dist/LambderMSW.d.ts +48 -101
- package/dist/LambderMSW.js +105 -172
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -2
- package/docs/LAMBDER_MSW.md +430 -0
- package/docs/TYPE_SAFE_QUICK_START.md +33 -0
- package/examples/msw-testing-example.ts +277 -0
- package/package.json +1 -1
- package/src/LambderMSW.ts +172 -281
- package/src/index.ts +1 -10
- package/docs/MSW_IMPLEMENTATION_COMPLETE.md +0 -191
- package/docs/MSW_MOCKING.md +0 -606
- package/examples/msw-correct-usage.ts +0 -136
- package/examples/msw-mocking-example.ts +0 -241
- package/tests/msw-integration.test.ts +0 -45
package/src/LambderMSW.ts
CHANGED
|
@@ -1,295 +1,186 @@
|
|
|
1
|
-
import type { ApiContractShape } from './LambderApiContract
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
apiPath
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
apiVersion
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return http.post(apiPath, async ({ request }: any) => {
|
|
47
|
-
const body = (await request.json()) as {
|
|
48
|
-
apiName: string;
|
|
49
|
-
payload?: TContract[TApiName]['input'];
|
|
50
|
-
version?: string;
|
|
51
|
-
token?: string;
|
|
52
|
-
siteHost?: string;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Only handle this specific API
|
|
56
|
-
if (body.apiName !== apiName) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
console.log(`[MSW Lambder] Mocking ${apiName}`, body.payload);
|
|
61
|
-
|
|
62
|
-
// Apply delay if specified
|
|
63
|
-
if (delay > 0) {
|
|
64
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1
|
+
import type { ApiContractShape } from './LambderApiContract';
|
|
2
|
+
import type { LambderApiResponse } from './LambderResponseBuilder';
|
|
3
|
+
|
|
4
|
+
// MSW types - these will be resolved at runtime when msw is installed
|
|
5
|
+
type RequestHandler = any;
|
|
6
|
+
|
|
7
|
+
type MockApiOptions = {
|
|
8
|
+
versionExpired?: boolean;
|
|
9
|
+
sessionExpired?: boolean;
|
|
10
|
+
notAuthorized?: boolean;
|
|
11
|
+
message?: any;
|
|
12
|
+
errorMessage?: any;
|
|
13
|
+
logList?: any[];
|
|
14
|
+
delay?: number; // Add artificial delay to simulate network latency
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Extended response type that includes apiVersion
|
|
18
|
+
type MockApiResponse<T> = {
|
|
19
|
+
apiVersion?: string;
|
|
20
|
+
} & LambderApiResponse<T>;
|
|
21
|
+
|
|
22
|
+
export default class LambderMSW<TContract extends ApiContractShape = any> {
|
|
23
|
+
private apiPath: string;
|
|
24
|
+
private apiVersion?: string;
|
|
25
|
+
private http: any;
|
|
26
|
+
private HttpResponse: any;
|
|
27
|
+
|
|
28
|
+
constructor({
|
|
29
|
+
apiPath,
|
|
30
|
+
apiVersion,
|
|
31
|
+
}: {
|
|
32
|
+
apiPath: string;
|
|
33
|
+
apiVersion?: string;
|
|
34
|
+
}) {
|
|
35
|
+
this.apiPath = apiPath;
|
|
36
|
+
this.apiVersion = apiVersion;
|
|
37
|
+
|
|
38
|
+
// Dynamically import MSW - it needs to be installed by the user
|
|
39
|
+
try {
|
|
40
|
+
const msw = require('msw');
|
|
41
|
+
this.http = msw.http;
|
|
42
|
+
this.HttpResponse = msw.HttpResponse;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw new Error('MSW (Mock Service Worker) is required. Install it with: npm install msw --save-dev');
|
|
65
45
|
}
|
|
66
|
-
|
|
67
|
-
// Generate the response
|
|
68
|
-
const payload = await responseFactory(body.payload as TContract[TApiName]['input']);
|
|
69
|
-
|
|
70
|
-
// Return in Lambder API response format
|
|
71
|
-
return HttpResponse.json({
|
|
72
|
-
...(apiVersion !== null ? { apiVersion } : {}),
|
|
73
|
-
payload,
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Creates a mock handler that returns an error for a Lambder API endpoint
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```typescript
|
|
83
|
-
* mockLambderApiError('user.deleteAccount', 'Account deletion failed')
|
|
84
|
-
* ```
|
|
85
|
-
*/
|
|
86
|
-
export function mockLambderApiError<
|
|
87
|
-
TContract extends ApiContractShape,
|
|
88
|
-
TApiName extends keyof TContract & string
|
|
89
|
-
>(
|
|
90
|
-
apiName: TApiName,
|
|
91
|
-
errorMessage: string,
|
|
92
|
-
options?: {
|
|
93
|
-
/** Custom API path, defaults to '/api' */
|
|
94
|
-
apiPath?: string;
|
|
95
|
-
/** Delay in ms before responding */
|
|
96
|
-
delay?: number;
|
|
97
|
-
/** Custom apiVersion to include in response */
|
|
98
|
-
apiVersion?: string | null;
|
|
99
46
|
}
|
|
100
|
-
) {
|
|
101
|
-
const apiPath = options?.apiPath ?? '/api';
|
|
102
|
-
const delay = options?.delay ?? 0;
|
|
103
|
-
const apiVersion = options?.apiVersion ?? null;
|
|
104
|
-
|
|
105
|
-
// @ts-ignore - MSW is an optional peer dependency
|
|
106
|
-
const { http, HttpResponse } = require('msw');
|
|
107
|
-
|
|
108
|
-
return http.post(apiPath, async ({ request }: any) => {
|
|
109
|
-
const body = (await request.json()) as {
|
|
110
|
-
apiName: string;
|
|
111
|
-
payload?: any;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
if (body.apiName !== apiName) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
console.log(`[MSW Lambder] Mocking error for ${apiName}:`, errorMessage);
|
|
119
47
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Mock an API endpoint with MSW
|
|
50
|
+
* @param apiName - The name of the API to mock
|
|
51
|
+
* @param handler - Function that returns the mock payload
|
|
52
|
+
* @param options - Additional response options (session expired, version expired, etc.)
|
|
53
|
+
*/
|
|
54
|
+
mockApi<TApiName extends keyof TContract & string>(
|
|
55
|
+
apiName: TApiName,
|
|
56
|
+
handler: (payload?: TContract[TApiName]['input']) => Promise<TContract[TApiName]['output']> | TContract[TApiName]['output'],
|
|
57
|
+
options?: MockApiOptions
|
|
58
|
+
): RequestHandler {
|
|
59
|
+
return this.http.post(this.apiPath, async ({ request }: any) => {
|
|
60
|
+
let body: any;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
body = await request.json();
|
|
64
|
+
} catch (parseErr) {
|
|
65
|
+
// If JSON parsing fails, return undefined to let other handlers try
|
|
66
|
+
console.warn("LambderMSW: Failed to parse request body as JSON");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check if body is valid and has apiName
|
|
71
|
+
if (!body || typeof body.apiName !== 'string') {
|
|
72
|
+
// Invalid request format, let other handlers try
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log("LambderMSW called for:", body.apiName, "matching against:", apiName);
|
|
77
|
+
|
|
78
|
+
// Check if this is the API we're mocking
|
|
79
|
+
if (body.apiName !== apiName) {
|
|
80
|
+
// If this handler doesn't match, return undefined to let MSW try other handlers
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Add artificial delay if specified
|
|
86
|
+
if (options?.delay) {
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, options.delay));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Call the handler with the payload from the request
|
|
91
|
+
const payload = await handler(body.payload as TContract[TApiName]['input']);
|
|
92
|
+
|
|
93
|
+
console.log("Matched! Returning payload for:", apiName);
|
|
94
|
+
|
|
95
|
+
const response: MockApiResponse<TContract[TApiName]['output']> = {
|
|
96
|
+
apiVersion: this.apiVersion,
|
|
97
|
+
payload,
|
|
98
|
+
...(options?.versionExpired ? { versionExpired: options.versionExpired } : {}),
|
|
99
|
+
...(options?.sessionExpired ? { sessionExpired: options.sessionExpired } : {}),
|
|
100
|
+
...(options?.notAuthorized ? { notAuthorized: options.notAuthorized } : {}),
|
|
101
|
+
...(options?.message ? { message: options.message } : {}),
|
|
102
|
+
...(options?.errorMessage ? { errorMessage: options.errorMessage } : {}),
|
|
103
|
+
...(options?.logList?.length ? { logList: options.logList } : {}),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return this.HttpResponse.json(response);
|
|
107
|
+
} catch (err: any) {
|
|
108
|
+
// Only handle errors that occur during handler execution for matched APIs
|
|
109
|
+
console.error("Error in LambderMSW handler for", apiName, ":", err);
|
|
110
|
+
|
|
111
|
+
const errorResponse: MockApiResponse<null> = {
|
|
112
|
+
apiVersion: this.apiVersion,
|
|
113
|
+
payload: null,
|
|
114
|
+
errorMessage: err.message || "Unknown error",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return this.HttpResponse.json(errorResponse, { status: 500 });
|
|
118
|
+
}
|
|
128
119
|
});
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Creates a mock handler that simulates a session expired error
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```typescript
|
|
137
|
-
* mockLambderSessionExpired('user.updateProfile')
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
export function mockLambderSessionExpired<
|
|
141
|
-
TContract extends ApiContractShape,
|
|
142
|
-
TApiName extends keyof TContract & string
|
|
143
|
-
>(
|
|
144
|
-
apiName: TApiName,
|
|
145
|
-
options?: {
|
|
146
|
-
/** Custom API path, defaults to '/api' */
|
|
147
|
-
apiPath?: string;
|
|
148
|
-
/** Custom apiVersion to include in response */
|
|
149
|
-
apiVersion?: string | null;
|
|
150
120
|
}
|
|
151
|
-
) {
|
|
152
|
-
const apiPath = options?.apiPath ?? '/api';
|
|
153
|
-
const apiVersion = options?.apiVersion ?? null;
|
|
154
|
-
|
|
155
|
-
// @ts-ignore - MSW is an optional peer dependency
|
|
156
|
-
const { http, HttpResponse } = require('msw');
|
|
157
121
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return HttpResponse.json({
|
|
171
|
-
...(apiVersion !== null ? { apiVersion } : {}),
|
|
172
|
-
payload: null,
|
|
173
|
-
sessionExpired: true,
|
|
174
|
-
errorMessage: 'Session expired',
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Creates a mock handler that simulates a "not authorized" error
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* ```typescript
|
|
184
|
-
* mockLambderNotAuthorized('admin.deleteUser')
|
|
185
|
-
* ```
|
|
186
|
-
*/
|
|
187
|
-
export function mockLambderNotAuthorized<
|
|
188
|
-
TContract extends ApiContractShape,
|
|
189
|
-
TApiName extends keyof TContract & string
|
|
190
|
-
>(
|
|
191
|
-
apiName: TApiName,
|
|
192
|
-
options?: {
|
|
193
|
-
/** Custom API path, defaults to '/api' */
|
|
194
|
-
apiPath?: string;
|
|
195
|
-
/** Custom apiVersion to include in response */
|
|
196
|
-
apiVersion?: string | null;
|
|
122
|
+
/**
|
|
123
|
+
* Mock an API endpoint that returns a session expired error
|
|
124
|
+
*/
|
|
125
|
+
mockSessionExpired<TApiName extends keyof TContract & string>(
|
|
126
|
+
apiName: TApiName
|
|
127
|
+
): RequestHandler {
|
|
128
|
+
return this.mockApi(
|
|
129
|
+
apiName,
|
|
130
|
+
async () => null as any,
|
|
131
|
+
{ sessionExpired: true }
|
|
132
|
+
);
|
|
197
133
|
}
|
|
198
|
-
) {
|
|
199
|
-
const apiPath = options?.apiPath ?? '/api';
|
|
200
|
-
const apiVersion = options?.apiVersion ?? null;
|
|
201
|
-
|
|
202
|
-
// @ts-ignore - MSW is an optional peer dependency
|
|
203
|
-
const { http, HttpResponse } = require('msw');
|
|
204
|
-
|
|
205
|
-
return http.post(apiPath, async ({ request }: any) => {
|
|
206
|
-
const body = (await request.json()) as {
|
|
207
|
-
apiName: string;
|
|
208
|
-
payload?: any;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
if (body.apiName !== apiName) {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
console.log(`[MSW Lambder] Mocking not authorized for ${apiName}`);
|
|
216
134
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
*
|
|
229
|
-
* @example
|
|
230
|
-
* ```typescript
|
|
231
|
-
* mockLambderVersionExpired('user.updateProfile')
|
|
232
|
-
* ```
|
|
233
|
-
*/
|
|
234
|
-
export function mockLambderVersionExpired<
|
|
235
|
-
TContract extends ApiContractShape,
|
|
236
|
-
TApiName extends keyof TContract & string
|
|
237
|
-
>(
|
|
238
|
-
apiName: TApiName,
|
|
239
|
-
options?: {
|
|
240
|
-
/** Custom API path, defaults to '/api' */
|
|
241
|
-
apiPath?: string;
|
|
242
|
-
/** Custom apiVersion to include in response */
|
|
243
|
-
apiVersion?: string | null;
|
|
135
|
+
/**
|
|
136
|
+
* Mock an API endpoint that returns a version expired error
|
|
137
|
+
*/
|
|
138
|
+
mockVersionExpired<TApiName extends keyof TContract & string>(
|
|
139
|
+
apiName: TApiName
|
|
140
|
+
): RequestHandler {
|
|
141
|
+
return this.mockApi(
|
|
142
|
+
apiName,
|
|
143
|
+
async () => null as any,
|
|
144
|
+
{ versionExpired: true }
|
|
145
|
+
);
|
|
244
146
|
}
|
|
245
|
-
) {
|
|
246
|
-
const apiPath = options?.apiPath ?? '/api';
|
|
247
|
-
const apiVersion = options?.apiVersion ?? null;
|
|
248
147
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Mock an API endpoint that returns a not authorized error
|
|
150
|
+
*/
|
|
151
|
+
mockNotAuthorized<TApiName extends keyof TContract & string>(
|
|
152
|
+
apiName: TApiName
|
|
153
|
+
): RequestHandler {
|
|
154
|
+
return this.mockApi(
|
|
155
|
+
apiName,
|
|
156
|
+
async () => null as any,
|
|
157
|
+
{ notAuthorized: true }
|
|
158
|
+
);
|
|
159
|
+
}
|
|
261
160
|
|
|
262
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Mock an API endpoint that returns an error message
|
|
163
|
+
*/
|
|
164
|
+
mockError<TApiName extends keyof TContract & string>(
|
|
165
|
+
apiName: TApiName,
|
|
166
|
+
errorMessage: string
|
|
167
|
+
): RequestHandler {
|
|
168
|
+
return this.mockApi(
|
|
169
|
+
apiName,
|
|
170
|
+
async () => null as any,
|
|
171
|
+
{ errorMessage }
|
|
172
|
+
);
|
|
173
|
+
}
|
|
263
174
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Mock an API endpoint with a custom message
|
|
177
|
+
*/
|
|
178
|
+
mockWithMessage<TApiName extends keyof TContract & string>(
|
|
179
|
+
apiName: TApiName,
|
|
180
|
+
handler: (payload?: TContract[TApiName]['input']) => Promise<TContract[TApiName]['output']> | TContract[TApiName]['output'],
|
|
181
|
+
message: any
|
|
182
|
+
): RequestHandler {
|
|
183
|
+
return this.mockApi(apiName, handler, { message });
|
|
184
|
+
}
|
|
271
185
|
}
|
|
272
186
|
|
|
273
|
-
/**
|
|
274
|
-
* Helper to combine multiple Lambder mock handlers
|
|
275
|
-
* Since each handler checks for its specific apiName, you can just spread them into setupWorker/setupServer
|
|
276
|
-
*
|
|
277
|
-
* @deprecated This helper is not needed - just spread handlers directly into setupWorker/setupServer
|
|
278
|
-
* @example
|
|
279
|
-
* ```typescript
|
|
280
|
-
* import { setupServer } from 'msw/node';
|
|
281
|
-
*
|
|
282
|
-
* const server = setupServer(
|
|
283
|
-
* mockLambderApi('user.getProfile', () => ({ name: 'Test User' })),
|
|
284
|
-
* mockLambderApi('user.updateProfile', (input) => ({ success: true }))
|
|
285
|
-
* );
|
|
286
|
-
* ```
|
|
287
|
-
*/
|
|
288
|
-
export function createLambderApiHandler(
|
|
289
|
-
apiPath: string,
|
|
290
|
-
handlers: any[]
|
|
291
|
-
) {
|
|
292
|
-
// This is deprecated - users should just spread handlers directly
|
|
293
|
-
// Kept for backward compatibility
|
|
294
|
-
return handlers;
|
|
295
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { default as LambderCaller } from "./LambderCaller.js";
|
|
|
5
5
|
export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
|
|
6
6
|
export { default as LambderResolver } from "./LambderResolver.js";
|
|
7
7
|
export { default as LambderSessionManager } from "./LambderSessionManager.js";
|
|
8
|
+
export { default as LambderMSW } from "./LambderMSW.js";
|
|
8
9
|
|
|
9
10
|
// Type-safe API contract utilities
|
|
10
11
|
export {
|
|
@@ -13,13 +14,3 @@ export {
|
|
|
13
14
|
type ApiInput,
|
|
14
15
|
type ApiOutput,
|
|
15
16
|
} from "./LambderApiContract.js";
|
|
16
|
-
|
|
17
|
-
// MSW mocking utilities (optional - requires msw to be installed)
|
|
18
|
-
export {
|
|
19
|
-
mockLambderApi,
|
|
20
|
-
mockLambderApiError,
|
|
21
|
-
mockLambderSessionExpired,
|
|
22
|
-
mockLambderNotAuthorized,
|
|
23
|
-
mockLambderVersionExpired,
|
|
24
|
-
createLambderApiHandler,
|
|
25
|
-
} from "./LambderMSW.js";
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# MSW Integration - Complete Implementation Summary
|
|
2
|
-
|
|
3
|
-
## What Was Done
|
|
4
|
-
|
|
5
|
-
Added Mock Service Worker (MSW) integration to Lambder as an optional peer dependency, allowing developers to easily mock Lambder API endpoints for testing and development.
|
|
6
|
-
|
|
7
|
-
## Files Created/Modified
|
|
8
|
-
|
|
9
|
-
### 1. Core Implementation
|
|
10
|
-
- **`src/LambderMSW.ts`** - New file with MSW mocking utilities
|
|
11
|
-
- `mockLambderApi()` - Mock successful API responses
|
|
12
|
-
- `mockLambderApiError()` - Mock error responses
|
|
13
|
-
- `mockLambderSessionExpired()` - Mock session expiry
|
|
14
|
-
- `mockLambderNotAuthorized()` - Mock authorization errors
|
|
15
|
-
- `mockLambderVersionExpired()` - Mock version expiry
|
|
16
|
-
- All functions use `require('msw')` to dynamically load MSW
|
|
17
|
-
- Each function calls `http.post()` and returns a proper MSW `RequestHandler`
|
|
18
|
-
|
|
19
|
-
### 2. Package Configuration
|
|
20
|
-
- **`package.json`** - Added MSW as optional peer dependency
|
|
21
|
-
```json
|
|
22
|
-
"peerDependencies": {
|
|
23
|
-
"msw": "^2.0.0"
|
|
24
|
-
},
|
|
25
|
-
"peerDependenciesMeta": {
|
|
26
|
-
"msw": {
|
|
27
|
-
"optional": true
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### 3. Exports
|
|
33
|
-
- **`src/index.ts`** - Exported all MSW utilities from main entry point
|
|
34
|
-
|
|
35
|
-
### 4. Documentation
|
|
36
|
-
- **`docs/MSW_MOCKING.md`** - Comprehensive guide (591 lines)
|
|
37
|
-
- Installation instructions
|
|
38
|
-
- Quick start with examples
|
|
39
|
-
- API reference for all functions
|
|
40
|
-
- Testing examples (Vitest, Jest, React Testing Library)
|
|
41
|
-
- Storybook integration
|
|
42
|
-
- Advanced usage patterns
|
|
43
|
-
- Best practices and troubleshooting
|
|
44
|
-
|
|
45
|
-
- **`docs/MSW_QUICK_FIX.md`** - Quick reference for the fix
|
|
46
|
-
- Explains the solution to type compatibility
|
|
47
|
-
- Shows correct usage patterns
|
|
48
|
-
- Provides working examples
|
|
49
|
-
|
|
50
|
-
- **`docs/MSW_INTEGRATION_SUMMARY.md`** - High-level overview
|
|
51
|
-
- Lists all changes made
|
|
52
|
-
- Explains benefits
|
|
53
|
-
- Shows usage examples
|
|
54
|
-
|
|
55
|
-
### 5. Examples
|
|
56
|
-
- **`examples/msw-mocking-example.ts`** - Detailed example with comments
|
|
57
|
-
- API contract definition
|
|
58
|
-
- Various mock handlers
|
|
59
|
-
- Setup for Node.js tests
|
|
60
|
-
- Setup for browser development
|
|
61
|
-
|
|
62
|
-
- **`examples/msw-correct-usage.ts`** - Shows the correct way to use handlers
|
|
63
|
-
- Demonstrates proper MSW integration
|
|
64
|
-
- TypeScript type verification
|
|
65
|
-
|
|
66
|
-
### 6. Tests
|
|
67
|
-
- **`tests/msw-integration.test.ts`** - Basic test to verify exports
|
|
68
|
-
- Tests run without MSW installed
|
|
69
|
-
- Includes commented examples for when MSW is installed
|
|
70
|
-
|
|
71
|
-
### 7. README Update
|
|
72
|
-
- **`Readme.md`** - Added MSW to features and created new section
|
|
73
|
-
- Added MSW to features list
|
|
74
|
-
- New "Testing & Mocking with MSW" section
|
|
75
|
-
- Links to detailed documentation
|
|
76
|
-
|
|
77
|
-
## How It Works
|
|
78
|
-
|
|
79
|
-
### The Fix for Type Compatibility
|
|
80
|
-
|
|
81
|
-
**Problem**: Custom `HttpHandler` type didn't match MSW's `RequestHandler`
|
|
82
|
-
|
|
83
|
-
**Solution**: Each Lambder MSW function now:
|
|
84
|
-
|
|
85
|
-
1. Dynamically requires MSW: `const { http, HttpResponse } = require('msw')`
|
|
86
|
-
2. Calls MSW's `http.post()` with the handler logic
|
|
87
|
-
3. Returns the `RequestHandler` that `http.post()` creates
|
|
88
|
-
4. Uses `@ts-ignore` for the require statement since MSW is optional
|
|
89
|
-
|
|
90
|
-
This means the return type is automatically compatible with MSW's `setupWorker` and `setupServer`.
|
|
91
|
-
|
|
92
|
-
### Example Flow
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
// User code
|
|
96
|
-
mockLambderApi('user.getProfile', () => ({ name: 'Test' }))
|
|
97
|
-
|
|
98
|
-
// Internally:
|
|
99
|
-
// 1. require('msw') → gets http and HttpResponse
|
|
100
|
-
// 2. http.post('/api', async ({ request }) => { ... })
|
|
101
|
-
// 3. Returns: RequestHandler from MSW
|
|
102
|
-
|
|
103
|
-
// Result: Perfect type compatibility!
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Usage Example
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
// 1. Define API contract
|
|
110
|
-
const ApiContract = {
|
|
111
|
-
'user.getProfile': {
|
|
112
|
-
input: { userId: string },
|
|
113
|
-
output: { name: string, email: string }
|
|
114
|
-
}
|
|
115
|
-
} satisfies ApiContractShape
|
|
116
|
-
|
|
117
|
-
type MyApiContract = typeof ApiContract
|
|
118
|
-
|
|
119
|
-
// 2. Create handlers
|
|
120
|
-
import { mockLambderApi } from 'lambder'
|
|
121
|
-
|
|
122
|
-
const handlers = [
|
|
123
|
-
mockLambderApi<MyApiContract, 'user.getProfile'>(
|
|
124
|
-
'user.getProfile',
|
|
125
|
-
() => ({
|
|
126
|
-
name: 'Test User',
|
|
127
|
-
email: 'test@example.com'
|
|
128
|
-
})
|
|
129
|
-
)
|
|
130
|
-
]
|
|
131
|
-
|
|
132
|
-
// 3. Setup MSW
|
|
133
|
-
import { setupWorker } from 'msw/browser'
|
|
134
|
-
export const worker = setupWorker(...handlers) // ✅ Works!
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## Key Features
|
|
138
|
-
|
|
139
|
-
✅ **Type-Safe**: Full TypeScript support with API contracts
|
|
140
|
-
✅ **Optional**: No hard dependency - only needed for testing
|
|
141
|
-
✅ **Easy**: Simple functions matching Lambder's API pattern
|
|
142
|
-
✅ **Comprehensive**: Covers all error scenarios
|
|
143
|
-
✅ **Compatible**: Returns proper MSW `RequestHandler` types
|
|
144
|
-
|
|
145
|
-
## Benefits
|
|
146
|
-
|
|
147
|
-
1. **No Breaking Changes**: Existing code continues to work
|
|
148
|
-
2. **Opt-In**: Only developers who want mocking need to install MSW
|
|
149
|
-
3. **Type Safety**: Full IntelliSense and type checking
|
|
150
|
-
4. **Developer Experience**: Works with popular testing frameworks
|
|
151
|
-
5. **Production Ready**: Well-documented with examples
|
|
152
|
-
|
|
153
|
-
## Testing
|
|
154
|
-
|
|
155
|
-
- All tests pass without MSW installed
|
|
156
|
-
- Build compiles successfully
|
|
157
|
-
- No TypeScript errors
|
|
158
|
-
- Example files demonstrate correct usage
|
|
159
|
-
|
|
160
|
-
## Installation for Users
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
# Install lambder (already done)
|
|
164
|
-
npm install lambder
|
|
165
|
-
|
|
166
|
-
# Install MSW (only if you want mocking)
|
|
167
|
-
npm install --save-dev msw
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Next Steps for Users
|
|
171
|
-
|
|
172
|
-
1. Read [MSW_MOCKING.md](./MSW_MOCKING.md) for detailed guide
|
|
173
|
-
2. Check [msw-correct-usage.ts](../examples/msw-correct-usage.ts) for example
|
|
174
|
-
3. Install MSW: `npm install --save-dev msw`
|
|
175
|
-
4. Create handlers using `mockLambderApi`
|
|
176
|
-
5. Set up MSW worker/server
|
|
177
|
-
6. Start mocking!
|
|
178
|
-
|
|
179
|
-
## Technical Details
|
|
180
|
-
|
|
181
|
-
- Uses CommonJS `require()` for dynamic import
|
|
182
|
-
- `@ts-ignore` suppresses optional dependency errors
|
|
183
|
-
- Each handler checks `body.apiName` for routing
|
|
184
|
-
- Supports all Lambder API response formats
|
|
185
|
-
- Compatible with MSW v2.0.0+
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
**Status**: ✅ Complete and tested
|
|
190
|
-
**Compatibility**: MSW v2.0.0+, TypeScript 5.9+
|
|
191
|
-
**Breaking Changes**: None
|