lambder 1.0.124 → 1.0.126
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 +60 -1
- package/dist/Lambder.d.ts +14 -4
- package/dist/Lambder.js +4 -2
- package/dist/LambderApiContract.d.ts +42 -0
- package/dist/LambderApiContract.js +26 -0
- package/dist/LambderCaller.d.ts +15 -4
- package/dist/LambderCaller.js +11 -5
- package/dist/LambderResponseBuilder.d.ts +1 -0
- package/dist/LambderResponseBuilder.js +32 -1
- package/dist/index.d.ts +1 -0
- package/docs/TYPE_SAFE_QUICK_START.md +123 -0
- package/examples/simplified-typed-api-example.ts +365 -0
- package/package.json +1 -1
- package/src/Lambder.ts +43 -3
- package/src/LambderApiContract.ts +51 -0
- package/src/LambderCaller.ts +48 -18
- package/src/LambderResponseBuilder.ts +40 -1
- package/src/index.ts +7 -0
- package/tests/type-safety.test.ts +224 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Safety Tests
|
|
3
|
+
*
|
|
4
|
+
* This file tests that the type system works correctly.
|
|
5
|
+
* If this file compiles without errors, the types are working!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Lambder from '../src/Lambder.js';
|
|
9
|
+
import LambderCaller from '../src/LambderCaller.js';
|
|
10
|
+
import type { ApiContract } from '../src/index.js';
|
|
11
|
+
|
|
12
|
+
// Test contract
|
|
13
|
+
type TestApiContract = {
|
|
14
|
+
getUser: { input: { userId: string }, output: { id: string, name: string } },
|
|
15
|
+
createUser: { input: { name: string, email: string }, output: { id: string, name: string, email: string } },
|
|
16
|
+
listUsers: { input: void, output: Array<{ id: string, name: string }> },
|
|
17
|
+
deleteUser: { input: { userId: string }, output: boolean }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Test 1: LambderCaller Type Safety
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
function testCallerTypes() {
|
|
25
|
+
const caller = new LambderCaller<TestApiContract>({
|
|
26
|
+
apiPath: '/api',
|
|
27
|
+
isCorsEnabled: false
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
async function test() {
|
|
31
|
+
// ✅ Should work: Correct types
|
|
32
|
+
const user1 = await caller.api('getUser', { userId: '123' });
|
|
33
|
+
const user2 = await caller.api('createUser', { name: 'Alice', email: 'alice@example.com' });
|
|
34
|
+
const users = await caller.api('listUsers', undefined);
|
|
35
|
+
const deleted = await caller.api('deleteUser', { userId: '123' });
|
|
36
|
+
|
|
37
|
+
// Type assertions to verify return types
|
|
38
|
+
if (user1) {
|
|
39
|
+
const name: string = user1.name; // Should work
|
|
40
|
+
const id: string = user1.id; // Should work
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (user2) {
|
|
44
|
+
const email: string = user2.email; // Should work
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (users) {
|
|
48
|
+
const firstUser = users[0];
|
|
49
|
+
if (firstUser) {
|
|
50
|
+
const name: string = firstUser.name; // Should work
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (deleted !== null && deleted !== undefined) {
|
|
55
|
+
const result: boolean = deleted; // Should work
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ❌ These should cause TypeScript errors (commented out to allow compilation):
|
|
59
|
+
|
|
60
|
+
// Wrong property name
|
|
61
|
+
// await caller.api('getUser', { id: '123' }); // Error: should be userId
|
|
62
|
+
|
|
63
|
+
// Missing required property
|
|
64
|
+
// await caller.api('createUser', { name: 'Bob' }); // Error: missing email
|
|
65
|
+
|
|
66
|
+
// Wrong type
|
|
67
|
+
// await caller.api('getUser', { userId: 123 }); // Error: userId should be string
|
|
68
|
+
|
|
69
|
+
// Non-existent API
|
|
70
|
+
// await caller.api('nonExistent', {}); // Error: API doesn't exist
|
|
71
|
+
|
|
72
|
+
// Wrong payload for void input
|
|
73
|
+
// await caller.api('listUsers', { something: true }); // Error: should be undefined
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// Test 2: Lambder Type Safety
|
|
79
|
+
// ============================================================================
|
|
80
|
+
|
|
81
|
+
function testLambderTypes() {
|
|
82
|
+
const lambder = new Lambder<TestApiContract>({
|
|
83
|
+
publicPath: './public',
|
|
84
|
+
apiPath: '/api'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ✅ Should work: Typed API
|
|
88
|
+
lambder.addApi('getUser', async (ctx, resolver) => {
|
|
89
|
+
// ctx.apiPayload should be typed as { userId: string }
|
|
90
|
+
const userId: string = ctx.apiPayload.userId; // Should work
|
|
91
|
+
|
|
92
|
+
// Return correct type
|
|
93
|
+
return resolver.api({ id: userId, name: 'Test' });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ✅ Should work: Typed session API
|
|
97
|
+
lambder.addSessionApi('createUser', async (ctx, resolver) => {
|
|
98
|
+
// ctx.apiPayload should be typed as { name: string, email: string }
|
|
99
|
+
const name: string = ctx.apiPayload.name;
|
|
100
|
+
const email: string = ctx.apiPayload.email;
|
|
101
|
+
|
|
102
|
+
return resolver.api({ id: '1', name, email });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ✅ Should work: Void input API
|
|
106
|
+
lambder.addApi('listUsers', async (ctx, resolver) => {
|
|
107
|
+
// ctx.apiPayload should be void/undefined
|
|
108
|
+
return resolver.api([
|
|
109
|
+
{ id: '1', name: 'User 1' },
|
|
110
|
+
{ id: '2', name: 'User 2' }
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ✅ Should work: RegExp (untyped)
|
|
115
|
+
lambder.addApi(/^admin\./, async (ctx, resolver) => {
|
|
116
|
+
// ctx.apiPayload is any (untyped)
|
|
117
|
+
return resolver.api({});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ❌ These should cause TypeScript errors (commented out):
|
|
121
|
+
|
|
122
|
+
// Accessing wrong property
|
|
123
|
+
// lambder.addApi('getUser', async (ctx, resolver) => {
|
|
124
|
+
// const id = ctx.apiPayload.id; // Error: should be userId
|
|
125
|
+
// return resolver.api({ id, name: 'Test' });
|
|
126
|
+
// });
|
|
127
|
+
|
|
128
|
+
// Returning wrong type
|
|
129
|
+
// lambder.addApi('getUser', async (ctx, resolver) => {
|
|
130
|
+
// return resolver.api({ id: '1' }); // Error: missing name property
|
|
131
|
+
// });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Test 3: Backward Compatibility (No Contract)
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
function testBackwardCompatibility() {
|
|
139
|
+
// Without contract type - should work as before (untyped)
|
|
140
|
+
const caller = new LambderCaller({
|
|
141
|
+
apiPath: '/api',
|
|
142
|
+
isCorsEnabled: false
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const lambder = new Lambder({
|
|
146
|
+
publicPath: './public',
|
|
147
|
+
apiPath: '/api'
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
async function test() {
|
|
151
|
+
// Should work - untyped
|
|
152
|
+
const result = await caller.api('anyApi', { anything: true });
|
|
153
|
+
|
|
154
|
+
lambder.addApi('anyApi', async (ctx, resolver) => {
|
|
155
|
+
// ctx.apiPayload is any
|
|
156
|
+
return resolver.api({ anything: ctx.apiPayload });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Test 4: apiRaw Method
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
function testApiRaw() {
|
|
166
|
+
const caller = new LambderCaller<TestApiContract>({
|
|
167
|
+
apiPath: '/api',
|
|
168
|
+
isCorsEnabled: false
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
async function test() {
|
|
172
|
+
// apiRaw should also be typed
|
|
173
|
+
const response = await caller.apiRaw('getUser', { userId: '123' });
|
|
174
|
+
|
|
175
|
+
if (response) {
|
|
176
|
+
// response.payload should be typed
|
|
177
|
+
const user = response.payload;
|
|
178
|
+
if (user) {
|
|
179
|
+
const name: string = user.name; // Should work
|
|
180
|
+
const id: string = user.id; // Should work
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Test 5: Complex Types
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
type ComplexApiContract = {
|
|
191
|
+
update: {
|
|
192
|
+
input: { id: string } & Partial<{ name: string, email: string }>,
|
|
193
|
+
output: { id: string, name: string, email: string }
|
|
194
|
+
},
|
|
195
|
+
search: {
|
|
196
|
+
input: { query: string, filters?: { role?: string, active?: boolean } },
|
|
197
|
+
output: Array<{ id: string, name: string }>
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function testComplexTypes() {
|
|
202
|
+
const caller = new LambderCaller<ComplexApiContract>({
|
|
203
|
+
apiPath: '/api',
|
|
204
|
+
isCorsEnabled: false
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
async function test() {
|
|
208
|
+
// Should work with partial update
|
|
209
|
+
await caller.api('update', { id: '1', name: 'New Name' });
|
|
210
|
+
await caller.api('update', { id: '1', email: 'new@email.com' });
|
|
211
|
+
await caller.api('update', { id: '1' }); // Only id
|
|
212
|
+
|
|
213
|
+
// Should work with optional nested object
|
|
214
|
+
await caller.api('search', { query: 'test' });
|
|
215
|
+
await caller.api('search', { query: 'test', filters: { role: 'admin' } });
|
|
216
|
+
await caller.api('search', { query: 'test', filters: { role: 'admin', active: true } });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// If this file compiles without errors, the type system is working! ✅
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
export { };
|