lambder 1.0.147 → 2.0.2
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 +355 -410
- package/dist/Lambder.d.ts +47 -21
- package/dist/Lambder.js +79 -24
- package/dist/LambderApiContract.d.ts +10 -42
- package/dist/LambderApiContract.js +2 -22
- package/dist/LambderCaller.js +2 -2
- package/dist/LambderMSW.js +0 -4
- package/dist/LambderResolver.d.ts +16 -17
- package/dist/LambderResponseBuilder.d.ts +2 -3
- package/dist/LambderResponseBuilder.js +1 -3
- package/dist/LambderUtils.js +1 -3
- package/dist/index.d.ts +1 -1
- package/docs/LAMBDER_MSW.md +6 -6
- package/docs/TYPE_SAFE_QUICK_START.md +54 -177
- package/examples/msw-testing-example.ts +36 -33
- package/examples/secure-session-example.ts +50 -34
- package/examples/zod-chained-api-example.ts +63 -0
- package/package.json +3 -2
- package/src/Lambder.ts +124 -83
- package/src/LambderApiContract.ts +7 -50
- package/src/LambderCaller.ts +2 -2
- package/src/LambderMSW.ts +0 -7
- package/src/LambderResolver.ts +21 -24
- package/src/LambderResponseBuilder.ts +4 -7
- package/src/LambderUtils.ts +1 -3
- package/src/index.ts +0 -3
- package/tests/UNTESTED_FEATURES.md +263 -0
- package/tests/error-handling.test.ts +585 -0
- package/tests/hooks.test.ts +561 -0
- package/tests/output-type-runtime.test.ts +80 -64
- package/tests/routes.test.ts +542 -0
- package/tests/session.test.ts +38 -24
- package/tests/type-safety.test.ts +147 -97
- package/tests/use-plugin.test.ts +437 -0
- package/OUTPUT_TYPE_ENFORCEMENT_SUMMARY.md +0 -90
- package/examples/output-type-enforcement-example.ts +0 -218
- package/examples/simplified-typed-api-example.ts +0 -365
- package/examples/test-output-type-enforcement.ts +0 -101
- package/test-type-enforcement.ts +0 -111
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Output Type Enforcement Example
|
|
3
|
-
*
|
|
4
|
-
* This example demonstrates how Lambder now enforces output types
|
|
5
|
-
* in addition to input types for type-safe APIs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import Lambder from '../src/Lambder.js';
|
|
9
|
-
import type { ApiContract } from '../src/index.js';
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Define Types
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
|
-
type User = {
|
|
16
|
-
id: string;
|
|
17
|
-
name: string;
|
|
18
|
-
email: string;
|
|
19
|
-
age: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type Product = {
|
|
23
|
-
id: string;
|
|
24
|
-
name: string;
|
|
25
|
-
price: number;
|
|
26
|
-
inStock: boolean;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type Stats = {
|
|
30
|
-
totalUsers: number;
|
|
31
|
-
totalProducts: number;
|
|
32
|
-
revenue: number;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// Define API Contract
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
type MyApiContract = ApiContract<{
|
|
40
|
-
// Simple primitive outputs
|
|
41
|
-
getCount: { input: void, output: number },
|
|
42
|
-
getMessage: { input: { id: string }, output: string },
|
|
43
|
-
isActive: { input: void, output: boolean },
|
|
44
|
-
|
|
45
|
-
// Complex object outputs
|
|
46
|
-
getUser: { input: { userId: string }, output: User },
|
|
47
|
-
getProduct: { input: { productId: string }, output: Product },
|
|
48
|
-
|
|
49
|
-
// Array outputs
|
|
50
|
-
listUsers: { input: void, output: User[] },
|
|
51
|
-
listProducts: { input: { category: string }, output: Product[] },
|
|
52
|
-
|
|
53
|
-
// Complex nested outputs
|
|
54
|
-
getStats: { input: void, output: Stats },
|
|
55
|
-
|
|
56
|
-
// Nullable outputs
|
|
57
|
-
findUser: { input: { email: string }, output: User | null },
|
|
58
|
-
}>;
|
|
59
|
-
|
|
60
|
-
// ============================================================================
|
|
61
|
-
// Setup Backend with Type Enforcement
|
|
62
|
-
// ============================================================================
|
|
63
|
-
|
|
64
|
-
const lambder = new Lambder<MyApiContract>({
|
|
65
|
-
publicPath: './public',
|
|
66
|
-
apiPath: '/api',
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// ✅ EXAMPLE 1: Simple primitive outputs are enforced
|
|
70
|
-
lambder.addApi('getCount', async (ctx, resolver) => {
|
|
71
|
-
const count = 42;
|
|
72
|
-
return resolver.api(count); // ✅ TypeScript validates this is a number
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
lambder.addApi('getMessage', async (ctx, resolver) => {
|
|
76
|
-
const message = `Message for ${ctx.apiPayload.id}`;
|
|
77
|
-
return resolver.api(message); // ✅ TypeScript validates this is a string
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
lambder.addApi('isActive', async (ctx, resolver) => {
|
|
81
|
-
const active = true;
|
|
82
|
-
return resolver.api(active); // ✅ TypeScript validates this is a boolean
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// ✅ EXAMPLE 2: Complex objects must match the shape
|
|
86
|
-
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
87
|
-
const user: User = {
|
|
88
|
-
id: ctx.apiPayload.userId,
|
|
89
|
-
name: "John Doe",
|
|
90
|
-
email: "john@example.com",
|
|
91
|
-
age: 30,
|
|
92
|
-
};
|
|
93
|
-
return resolver.api(user); // ✅ All required fields are present
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
lambder.addApi('getProduct', async (ctx, resolver) => {
|
|
97
|
-
const product: Product = {
|
|
98
|
-
id: ctx.apiPayload.productId,
|
|
99
|
-
name: "Widget",
|
|
100
|
-
price: 99.99,
|
|
101
|
-
inStock: true,
|
|
102
|
-
};
|
|
103
|
-
return resolver.api(product); // ✅ Correct shape
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ✅ EXAMPLE 3: Arrays are type-checked
|
|
107
|
-
lambder.addApi('listUsers', async (ctx, resolver) => {
|
|
108
|
-
const users: User[] = [
|
|
109
|
-
{ id: "1", name: "Alice", email: "alice@example.com", age: 25 },
|
|
110
|
-
{ id: "2", name: "Bob", email: "bob@example.com", age: 30 },
|
|
111
|
-
];
|
|
112
|
-
return resolver.api(users); // ✅ Array of User objects
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
lambder.addApi('listProducts', async (ctx, resolver) => {
|
|
116
|
-
const category = ctx.apiPayload.category;
|
|
117
|
-
const products: Product[] = [
|
|
118
|
-
{ id: "1", name: "Item 1", price: 10, inStock: true },
|
|
119
|
-
{ id: "2", name: "Item 2", price: 20, inStock: false },
|
|
120
|
-
];
|
|
121
|
-
return resolver.api(products); // ✅ Array of Product objects
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// ✅ EXAMPLE 4: Nested objects are validated
|
|
125
|
-
lambder.addApi('getStats', async (ctx, resolver) => {
|
|
126
|
-
const stats: Stats = {
|
|
127
|
-
totalUsers: 100,
|
|
128
|
-
totalProducts: 50,
|
|
129
|
-
revenue: 10000,
|
|
130
|
-
};
|
|
131
|
-
return resolver.api(stats); // ✅ Matches Stats shape
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// ✅ EXAMPLE 5: Nullable types work correctly
|
|
135
|
-
lambder.addApi('findUser', async (ctx, resolver) => {
|
|
136
|
-
const email = ctx.apiPayload.email;
|
|
137
|
-
|
|
138
|
-
if (email === "john@example.com") {
|
|
139
|
-
// Found user
|
|
140
|
-
const user: User = {
|
|
141
|
-
id: "123",
|
|
142
|
-
name: "John",
|
|
143
|
-
email: email,
|
|
144
|
-
age: 30,
|
|
145
|
-
};
|
|
146
|
-
return resolver.api(user); // ✅ Can return User
|
|
147
|
-
} else {
|
|
148
|
-
// Not found
|
|
149
|
-
return resolver.api(null); // ✅ Can return null
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// ✅ EXAMPLE 6: Using die.api() also enforces types
|
|
154
|
-
lambder.addApi('getCount', async (ctx, resolver) => {
|
|
155
|
-
return resolver.die.api(42); // ✅ die.api() also type-checks
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// ✅ EXAMPLE 7: Session APIs also enforce output types
|
|
159
|
-
lambder.addSessionApi('getUser', async (ctx, resolver) => {
|
|
160
|
-
const user: User = {
|
|
161
|
-
id: ctx.apiPayload.userId,
|
|
162
|
-
name: "Jane Doe",
|
|
163
|
-
email: "jane@example.com",
|
|
164
|
-
age: 28,
|
|
165
|
-
};
|
|
166
|
-
return resolver.api(user); // ✅ Type-safe for session APIs too
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// ============================================================================
|
|
170
|
-
// What TypeScript Prevents (these would cause compile errors)
|
|
171
|
-
// ============================================================================
|
|
172
|
-
|
|
173
|
-
/*
|
|
174
|
-
// ❌ ERROR: Wrong primitive type
|
|
175
|
-
lambder.addApi('getCount', async (ctx, resolver) => {
|
|
176
|
-
return resolver.api("not a number"); // Error: string not assignable to number
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// ❌ ERROR: Missing required field
|
|
180
|
-
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
181
|
-
return resolver.api({
|
|
182
|
-
id: "123",
|
|
183
|
-
name: "John",
|
|
184
|
-
email: "john@example.com",
|
|
185
|
-
// age is missing! - TypeScript error
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// ❌ ERROR: Wrong field type
|
|
190
|
-
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
191
|
-
return resolver.api({
|
|
192
|
-
id: "123",
|
|
193
|
-
name: "John",
|
|
194
|
-
email: "john@example.com",
|
|
195
|
-
age: "30", // Error: string not assignable to number
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// ❌ ERROR: Wrong array element type
|
|
200
|
-
lambder.addApi('listUsers', async (ctx, resolver) => {
|
|
201
|
-
return resolver.api([
|
|
202
|
-
{ id: "1", name: "Alice", email: "alice@example.com", age: 25 },
|
|
203
|
-
"not a user object", // Error: string not assignable to User
|
|
204
|
-
]);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// ❌ ERROR: Returning wrong type when null is not allowed
|
|
208
|
-
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
209
|
-
return resolver.api(null); // Error: null not assignable to User (findUser allows null, but getUser doesn't)
|
|
210
|
-
});
|
|
211
|
-
*/
|
|
212
|
-
|
|
213
|
-
console.log("✅ All examples demonstrate proper output type enforcement!");
|
|
214
|
-
console.log("✅ TypeScript will catch type mismatches at compile time!");
|
|
215
|
-
console.log("✅ This works with both resolver.api() and resolver.die.api()!");
|
|
216
|
-
console.log("✅ Both addApi() and addSessionApi() enforce output types!");
|
|
217
|
-
|
|
218
|
-
export default lambder;
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simplified Type-Safe API Example
|
|
3
|
-
*
|
|
4
|
-
* This example shows how to use Lambder's opt-in type-safe API system.
|
|
5
|
-
* Simply pass your API contract type to LambderCaller and Lambder constructors,
|
|
6
|
-
* and get full type safety with no extra wrapper functions needed!
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import Lambder from '../src/Lambder.js';
|
|
10
|
-
import LambderCaller from '../src/LambderCaller.js';
|
|
11
|
-
import type { ApiContract } from '../src/index.js';
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Step 1: Define your data types
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
type User = {
|
|
18
|
-
id: string;
|
|
19
|
-
name: string;
|
|
20
|
-
email: string;
|
|
21
|
-
role: 'admin' | 'user';
|
|
22
|
-
createdAt: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type CreateUserInput = {
|
|
26
|
-
name: string;
|
|
27
|
-
email: string;
|
|
28
|
-
password: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
type UpdateUserInput = {
|
|
32
|
-
userId: string;
|
|
33
|
-
name?: string;
|
|
34
|
-
email?: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
type LoginInput = {
|
|
38
|
-
email: string;
|
|
39
|
-
password: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
type LoginOutput = {
|
|
43
|
-
success: boolean;
|
|
44
|
-
user?: User;
|
|
45
|
-
token?: string;
|
|
46
|
-
error?: string;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Step 2: Define your API contract (shared between frontend and backend)
|
|
51
|
-
// ============================================================================
|
|
52
|
-
|
|
53
|
-
export type MyApiContract = ApiContract<{
|
|
54
|
-
// API with input and output
|
|
55
|
-
getUserById: { input: { userId: string }, output: User },
|
|
56
|
-
|
|
57
|
-
// API with complex input/output
|
|
58
|
-
createUser: { input: CreateUserInput, output: User },
|
|
59
|
-
updateUser: { input: UpdateUserInput, output: User },
|
|
60
|
-
|
|
61
|
-
// API with void input (no parameters needed)
|
|
62
|
-
listUsers: { input: void, output: User[] },
|
|
63
|
-
getCurrentUser: { input: void, output: User },
|
|
64
|
-
|
|
65
|
-
// API with conditional output
|
|
66
|
-
login: { input: LoginInput, output: LoginOutput },
|
|
67
|
-
|
|
68
|
-
// API with primitive output
|
|
69
|
-
getUserCount: { input: void, output: number },
|
|
70
|
-
deleteUser: { input: { userId: string }, output: boolean },
|
|
71
|
-
}>;
|
|
72
|
-
|
|
73
|
-
// ============================================================================
|
|
74
|
-
// Step 3: Backend - Pass contract type to Lambder
|
|
75
|
-
// ============================================================================
|
|
76
|
-
|
|
77
|
-
export function setupBackend() {
|
|
78
|
-
// Pass the contract type as a generic parameter
|
|
79
|
-
const lambder = new Lambder<MyApiContract>({
|
|
80
|
-
publicPath: './public',
|
|
81
|
-
apiPath: '/api',
|
|
82
|
-
apiVersion: '1.0.0',
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Now addApi is type-safe! ctx.apiPayload is automatically typed!
|
|
86
|
-
lambder.addApi('getUserById', async (ctx, resolver) => {
|
|
87
|
-
// ctx.apiPayload is typed as { userId: string }
|
|
88
|
-
const userId = ctx.apiPayload.userId; // ✅ TypeScript knows this!
|
|
89
|
-
|
|
90
|
-
// Mock database call
|
|
91
|
-
const user: User = {
|
|
92
|
-
id: userId,
|
|
93
|
-
name: 'John Doe',
|
|
94
|
-
email: 'john@example.com',
|
|
95
|
-
role: 'user',
|
|
96
|
-
createdAt: new Date().toISOString(),
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return resolver.api(user);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Session API with typed payload
|
|
103
|
-
lambder.addSessionApi('createUser', async (ctx, resolver) => {
|
|
104
|
-
// ctx.apiPayload is typed as CreateUserInput
|
|
105
|
-
const { name, email, password } = ctx.apiPayload;
|
|
106
|
-
|
|
107
|
-
// Validation with type safety
|
|
108
|
-
if (!name || !email || !password) {
|
|
109
|
-
return resolver.api(null, {
|
|
110
|
-
errorMessage: 'Missing required fields'
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Create user
|
|
115
|
-
const newUser: User = {
|
|
116
|
-
id: Math.random().toString(36).substr(2, 9),
|
|
117
|
-
name,
|
|
118
|
-
email,
|
|
119
|
-
role: 'user',
|
|
120
|
-
createdAt: new Date().toISOString(),
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
return resolver.api(newUser);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// API with void input
|
|
127
|
-
lambder.addApi('listUsers', async (ctx, resolver) => {
|
|
128
|
-
// ctx.apiPayload is void/undefined
|
|
129
|
-
const users: User[] = [
|
|
130
|
-
{ id: '1', name: 'John', email: 'john@example.com', role: 'user', createdAt: new Date().toISOString() },
|
|
131
|
-
{ id: '2', name: 'Jane', email: 'jane@example.com', role: 'admin', createdAt: new Date().toISOString() },
|
|
132
|
-
];
|
|
133
|
-
|
|
134
|
-
return resolver.api(users);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Complex API with conditional response
|
|
138
|
-
lambder.addApi('login', async (ctx, resolver) => {
|
|
139
|
-
// ctx.apiPayload is typed as LoginInput
|
|
140
|
-
const { email, password } = ctx.apiPayload;
|
|
141
|
-
|
|
142
|
-
// Mock authentication
|
|
143
|
-
if (email === 'test@example.com' && password === 'password123') {
|
|
144
|
-
const result: LoginOutput = {
|
|
145
|
-
success: true,
|
|
146
|
-
user: {
|
|
147
|
-
id: '1',
|
|
148
|
-
name: 'Test User',
|
|
149
|
-
email: email,
|
|
150
|
-
role: 'user',
|
|
151
|
-
createdAt: new Date().toISOString(),
|
|
152
|
-
},
|
|
153
|
-
token: 'mock-jwt-token',
|
|
154
|
-
};
|
|
155
|
-
return resolver.api(result);
|
|
156
|
-
} else {
|
|
157
|
-
const result: LoginOutput = {
|
|
158
|
-
success: false,
|
|
159
|
-
error: 'Invalid credentials',
|
|
160
|
-
};
|
|
161
|
-
return resolver.api(result);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// You can still use RegExp or functions for dynamic patterns (untyped)
|
|
166
|
-
lambder.addApi(/^admin\./, async (ctx, resolver) => {
|
|
167
|
-
// ctx.apiPayload is any (untyped)
|
|
168
|
-
return resolver.api({ message: 'Admin API' });
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
return lambder;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// ============================================================================
|
|
175
|
-
// Step 4: Frontend - Pass contract type to LambderCaller
|
|
176
|
-
// ============================================================================
|
|
177
|
-
|
|
178
|
-
export function setupFrontend() {
|
|
179
|
-
// Pass the contract type as a generic parameter
|
|
180
|
-
const caller = new LambderCaller<MyApiContract>({
|
|
181
|
-
apiPath: '/api',
|
|
182
|
-
apiVersion: '1.0.0',
|
|
183
|
-
isCorsEnabled: false,
|
|
184
|
-
errorHandler: (err) => {
|
|
185
|
-
console.error('API Error:', err);
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// Now all API calls are type-safe!
|
|
190
|
-
|
|
191
|
-
// Example 1: Get user by ID
|
|
192
|
-
async function example1() {
|
|
193
|
-
// TypeScript knows:
|
|
194
|
-
// - First parameter is 'getUserById' (autocomplete shows all API names!)
|
|
195
|
-
// - Second parameter must be { userId: string }
|
|
196
|
-
// - Return type is User | null | undefined
|
|
197
|
-
const user = await caller.api('getUserById', { userId: '123' });
|
|
198
|
-
|
|
199
|
-
if (user) {
|
|
200
|
-
console.log(user.name); // ✅ TypeScript knows 'name' exists
|
|
201
|
-
console.log(user.email); // ✅ TypeScript knows 'email' exists
|
|
202
|
-
console.log(user.role); // ✅ TypeScript knows 'role' is 'admin' | 'user'
|
|
203
|
-
// console.log(user.age); // ✗ Error: Property 'age' does not exist
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Example 2: Create user
|
|
208
|
-
async function example2() {
|
|
209
|
-
// TypeScript enforces the CreateUserInput type
|
|
210
|
-
const newUser = await caller.api('createUser', {
|
|
211
|
-
name: 'Alice',
|
|
212
|
-
email: 'alice@example.com',
|
|
213
|
-
password: 'secret123',
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
if (newUser) {
|
|
217
|
-
console.log('Created user:', newUser.id);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// This would be a TypeScript error:
|
|
221
|
-
// await caller.api('createUser', { name: 'Bob' }); // ✗ Missing email and password
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Example 3: Login
|
|
225
|
-
async function example3() {
|
|
226
|
-
const result = await caller.api('login', {
|
|
227
|
-
email: 'test@example.com',
|
|
228
|
-
password: 'password123',
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
if (result?.success && result.user) {
|
|
232
|
-
console.log('Logged in as:', result.user.name);
|
|
233
|
-
console.log('Token:', result.token);
|
|
234
|
-
} else {
|
|
235
|
-
console.error('Login failed:', result?.error);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Example 4: List users (void input)
|
|
240
|
-
async function example4() {
|
|
241
|
-
// For void input, pass undefined
|
|
242
|
-
const users = await caller.api('listUsers', undefined);
|
|
243
|
-
|
|
244
|
-
if (users) {
|
|
245
|
-
users.forEach(user => {
|
|
246
|
-
console.log(user.name); // ✅ TypeScript knows the array type
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Example 5: Get user count (primitive output)
|
|
252
|
-
async function example5() {
|
|
253
|
-
const count = await caller.api('getUserCount', undefined);
|
|
254
|
-
if (count !== null && count !== undefined) {
|
|
255
|
-
console.log(`Total users: ${count}`); // count is number
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Example 6: With custom headers
|
|
260
|
-
async function example6() {
|
|
261
|
-
const user = await caller.api(
|
|
262
|
-
'getUserById',
|
|
263
|
-
{ userId: '456' },
|
|
264
|
-
{ headers: { 'X-Custom-Header': 'value' } }
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Example 7: Using apiRaw for full response
|
|
269
|
-
async function example7() {
|
|
270
|
-
const response = await caller.apiRaw('getUserById', { userId: '123' });
|
|
271
|
-
|
|
272
|
-
if (response) {
|
|
273
|
-
console.log('Payload:', response.payload); // User | null
|
|
274
|
-
if (response.logList) {
|
|
275
|
-
console.log('Logs:', response.logList);
|
|
276
|
-
}
|
|
277
|
-
if (response.errorMessage) {
|
|
278
|
-
console.error('Error:', response.errorMessage);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return caller;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// ============================================================================
|
|
287
|
-
// Without Contract (Backward Compatibility)
|
|
288
|
-
// ============================================================================
|
|
289
|
-
|
|
290
|
-
export function setupWithoutContract() {
|
|
291
|
-
// If you don't pass a contract type, it works like before (untyped)
|
|
292
|
-
const caller = new LambderCaller({
|
|
293
|
-
apiPath: '/api',
|
|
294
|
-
isCorsEnabled: false,
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Still works, but no type safety
|
|
298
|
-
async function untypedExample() {
|
|
299
|
-
const user = await caller.api('getUserById', { userId: '123' });
|
|
300
|
-
// user is any
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const lambder = new Lambder({
|
|
304
|
-
publicPath: './public',
|
|
305
|
-
apiPath: '/api',
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Still works, but no type safety
|
|
309
|
-
lambder.addApi('getUserById', async (ctx, resolver) => {
|
|
310
|
-
// ctx.apiPayload is any
|
|
311
|
-
const user = { id: ctx.apiPayload.userId, name: 'User' };
|
|
312
|
-
return resolver.api(user);
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ============================================================================
|
|
317
|
-
// Type Safety Examples
|
|
318
|
-
// ============================================================================
|
|
319
|
-
|
|
320
|
-
export function typeSafetyExamples() {
|
|
321
|
-
const caller = new LambderCaller<MyApiContract>({ apiPath: '/api', isCorsEnabled: false });
|
|
322
|
-
|
|
323
|
-
async function examples() {
|
|
324
|
-
// ✓ VALID:
|
|
325
|
-
await caller.api('getUserById', { userId: '123' });
|
|
326
|
-
await caller.api('createUser', { name: 'Alice', email: 'alice@example.com', password: 'pass' });
|
|
327
|
-
await caller.api('listUsers', undefined);
|
|
328
|
-
|
|
329
|
-
// ✗ ERRORS (TypeScript prevents):
|
|
330
|
-
// await caller.api('getUserById'); // Missing required payload
|
|
331
|
-
// await caller.api('getUserById', { id: '123' }); // Wrong property name (should be userId)
|
|
332
|
-
// await caller.api('createUser', { name: 'Bob' }); // Missing email and password
|
|
333
|
-
// await caller.api('nonExistentApi', {}); // API doesn't exist in contract
|
|
334
|
-
|
|
335
|
-
// Type inference works:
|
|
336
|
-
const user = await caller.api('getUserById', { userId: '123' });
|
|
337
|
-
if (user) {
|
|
338
|
-
console.log(user.name); // ✓ TypeScript knows user has name
|
|
339
|
-
// console.log(user.age); // ✗ Error: Property 'age' does not exist
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const users = await caller.api('listUsers', undefined);
|
|
343
|
-
if (users) {
|
|
344
|
-
users.forEach(u => {
|
|
345
|
-
console.log(u.email); // ✓ TypeScript knows array item structure
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// ============================================================================
|
|
352
|
-
// Key Benefits
|
|
353
|
-
// ============================================================================
|
|
354
|
-
//
|
|
355
|
-
// 1. ✅ Type Safety - Frontend and backend share the same types
|
|
356
|
-
// 2. ✅ Autocomplete - IDE suggests available APIs as you type
|
|
357
|
-
// 3. ✅ No Wrappers - Use existing api() and addApi() methods
|
|
358
|
-
// 4. ✅ Opt-In - Add types when you want, or don't use them at all
|
|
359
|
-
// 5. ✅ Backward Compatible - Existing code works without changes
|
|
360
|
-
// 6. ✅ Simple - Just pass type to constructor, that's it!
|
|
361
|
-
// 7. ✅ Zero Runtime Overhead - Pure TypeScript types
|
|
362
|
-
//
|
|
363
|
-
// ============================================================================
|
|
364
|
-
|
|
365
|
-
export { Lambder, LambderCaller, type ApiContract };
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test file to verify output type enforcement
|
|
3
|
-
* This file should have TypeScript errors if output types are not correct
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import Lambder from '../src/Lambder.js';
|
|
7
|
-
import type { ApiContract } from '../src/index.js';
|
|
8
|
-
|
|
9
|
-
// Define a simple API contract
|
|
10
|
-
type TestContract = ApiContract<{
|
|
11
|
-
getNumber: { input: void, output: number },
|
|
12
|
-
getString: { input: { id: string }, output: string },
|
|
13
|
-
getUser: { input: { userId: string }, output: { id: string; name: string; age: number } },
|
|
14
|
-
}>;
|
|
15
|
-
|
|
16
|
-
const lambder = new Lambder<TestContract>({
|
|
17
|
-
publicPath: './public',
|
|
18
|
-
apiPath: '/api',
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// ✅ CORRECT: Returns the right type
|
|
22
|
-
lambder.addApi('getNumber', async (ctx, resolver) => {
|
|
23
|
-
// This should work - returning number
|
|
24
|
-
return resolver.api(42);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// ❌ INCORRECT: Should cause TypeScript error - returning wrong type
|
|
28
|
-
lambder.addApi('getNumber', async (ctx, resolver) => {
|
|
29
|
-
// @ts-expect-error - Testing type enforcement: should not accept string when output is number
|
|
30
|
-
return resolver.api("wrong type");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// ✅ CORRECT: Returns string
|
|
34
|
-
lambder.addApi('getString', async (ctx, resolver) => {
|
|
35
|
-
const id = ctx.apiPayload.id;
|
|
36
|
-
return resolver.api(`String for ${id}`);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// ❌ INCORRECT: Should cause TypeScript error - returning wrong type
|
|
40
|
-
lambder.addApi('getString', async (ctx, resolver) => {
|
|
41
|
-
// @ts-expect-error - Testing type enforcement: should not accept number when output is string
|
|
42
|
-
return resolver.api(123);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// ✅ CORRECT: Returns correct object shape
|
|
46
|
-
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
47
|
-
return resolver.api({
|
|
48
|
-
id: ctx.apiPayload.userId,
|
|
49
|
-
name: "John Doe",
|
|
50
|
-
age: 30,
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// ❌ INCORRECT: Should cause TypeScript error - missing required field
|
|
55
|
-
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
56
|
-
// @ts-expect-error - Testing type enforcement: missing 'age' field
|
|
57
|
-
return resolver.api({
|
|
58
|
-
id: ctx.apiPayload.userId,
|
|
59
|
-
name: "John Doe",
|
|
60
|
-
// age is missing
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// ❌ INCORRECT: Should cause TypeScript error - wrong field type
|
|
65
|
-
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
66
|
-
return resolver.api({
|
|
67
|
-
id: ctx.apiPayload.userId,
|
|
68
|
-
name: "John Doe",
|
|
69
|
-
// @ts-expect-error - Testing type enforcement: age should be number, not string
|
|
70
|
-
age: "30", // wrong type
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// ✅ CORRECT: Using null is allowed
|
|
75
|
-
lambder.addApi('getNumber', async (ctx, resolver) => {
|
|
76
|
-
return resolver.api(null);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// ✅ CORRECT: Using resolver.die.api also enforces types
|
|
80
|
-
lambder.addApi('getNumber', async (ctx, resolver) => {
|
|
81
|
-
return resolver.die.api(42);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// ❌ INCORRECT: resolver.die.api should also enforce types
|
|
85
|
-
lambder.addApi('getNumber', async (ctx, resolver) => {
|
|
86
|
-
// @ts-expect-error - Testing type enforcement: die.api should also check types
|
|
87
|
-
return resolver.die.api("wrong type");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Test with addSessionApi
|
|
91
|
-
lambder.addSessionApi('getString', async (ctx, resolver) => {
|
|
92
|
-
// Should also enforce output type for session APIs
|
|
93
|
-
return resolver.api("correct string");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
lambder.addSessionApi('getString', async (ctx, resolver) => {
|
|
97
|
-
// @ts-expect-error - Testing type enforcement: session API should also enforce types
|
|
98
|
-
return resolver.api(123);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
console.log("If this file compiles with @ts-expect-error comments, output type enforcement is working!");
|