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.
@@ -0,0 +1,365 @@
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 = {
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "1.0.124",
3
+ "version": "1.0.126",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/Lambder.ts CHANGED
@@ -8,6 +8,7 @@ import LambderResponseBuilder, { LambderResolverResponse } from "./LambderRespon
8
8
  import LambderUtils from "./LambderUtils.js";
9
9
  import LambderSessionManager, { type LambderSessionContext } from "./LambderSessionManager.js";
10
10
  import LambderSessionController from "./LambderSessionController.js";
11
+ import type { ApiContract } from "./LambderApiContract.js";
11
12
 
12
13
  type Path = `/${string}`;
13
14
 
@@ -23,6 +24,7 @@ export type LambderRenderContext = {
23
24
  apiPayload: any;
24
25
  headers: APIGatewayProxyEventHeaders;
25
26
  session: LambderSessionContext|null;
27
+ event: APIGatewayProxyEvent;
26
28
  lambdaContext: Context;
27
29
  _otherInternal: {
28
30
  isApiCall: boolean,
@@ -78,7 +80,7 @@ export const createContext = (
78
80
 
79
81
  return {
80
82
  host, path, pathParams, method,
81
- get, post, cookie,
83
+ get, post, cookie, event,
82
84
  apiName, apiPayload,
83
85
  headers, session, lambdaContext,
84
86
  _otherInternal: {
@@ -91,7 +93,7 @@ export const createContext = (
91
93
  }
92
94
 
93
95
 
94
- export default class Lambder {
96
+ export default class Lambder<TContract extends ApiContract = any> {
95
97
  public apiPath: string;
96
98
  public apiVersion: null|string;
97
99
  public isCorsEnabled: boolean = false;
@@ -133,7 +135,7 @@ export default class Lambder {
133
135
  this.utils = new LambderUtils({ ejsPath });
134
136
  }
135
137
 
136
- setIsCorsEnabled(isCorsEnabled: boolean){
138
+ enableCors(isCorsEnabled: boolean){
137
139
  this.isCorsEnabled = isCorsEnabled;
138
140
  }
139
141
 
@@ -237,6 +239,25 @@ export default class Lambder {
237
239
  });
238
240
  };
239
241
 
242
+ // Overload for untyped API with RegExp or function
243
+ addApi(
244
+ apiName: ConditionFunction|RegExp,
245
+ actionFn: ActionFunction
246
+ ):void;
247
+ // Overload for typed API with string name (must come before untyped string overload)
248
+ addApi<TApiName extends keyof TContract & string>(
249
+ apiName: TApiName,
250
+ actionFn: (
251
+ ctx: LambderRenderContext & { apiPayload: TContract[TApiName]['input'] },
252
+ resolver: LambderResolver
253
+ ) => LambderResolverResponse|Promise<LambderResolverResponse>
254
+ ):void;
255
+ // Overload for untyped API with string (backward compatibility, must be last)
256
+ addApi(
257
+ apiName: string,
258
+ actionFn: ActionFunction
259
+ ):void;
260
+ // Implementation
240
261
  addApi(apiName: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
241
262
  this.actionList.push({
242
263
  conditionFn: (ctx:LambderRenderContext) => (
@@ -250,6 +271,25 @@ export default class Lambder {
250
271
  });
251
272
  };
252
273
 
274
+ // Overload for untyped session API with RegExp or function
275
+ addSessionApi(
276
+ apiName: ConditionFunction|RegExp,
277
+ actionFn: ActionFunction
278
+ ):void;
279
+ // Overload for typed session API with string name (must come before untyped string overload)
280
+ addSessionApi<TApiName extends keyof TContract & string>(
281
+ apiName: TApiName,
282
+ actionFn: (
283
+ ctx: LambderRenderContext & { apiPayload: TContract[TApiName]['input'] },
284
+ resolver: LambderResolver
285
+ ) => LambderResolverResponse|Promise<LambderResolverResponse>
286
+ ):void;
287
+ // Overload for untyped session API with string (backward compatibility, must be last)
288
+ addSessionApi(
289
+ apiName: string,
290
+ actionFn: ActionFunction
291
+ ):void;
292
+ // Implementation
253
293
  addSessionApi(apiName: string|ConditionFunction|RegExp, actionFn: ActionFunction):void{
254
294
  this.actionList.push({
255
295
  conditionFn: (ctx:LambderRenderContext) => (
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Type-safe API Contract System
3
+ *
4
+ * Define your API contract as a TypeScript type to get full type safety
5
+ * across frontend and backend with no runtime overhead.
6
+ *
7
+ * Example:
8
+ *
9
+ * export type MyApiContract = {
10
+ * getUserById: { input: { userId: string }, output: User },
11
+ * createUser: { input: CreateUserInput, output: User },
12
+ * listUsers: { input: void, output: User[] }
13
+ * }
14
+ *
15
+ * Frontend:
16
+ * const caller = new LambderCaller<MyApiContract>({ ... });
17
+ * const user = await caller.api('getUserById', { userId: '123' }); // typed!
18
+ *
19
+ * Backend:
20
+ * const lambder = new Lambder<MyApiContract>({ ... });
21
+ * lambder.addApi('getUserById', async (ctx, resolver) => {
22
+ * // ctx.apiPayload is typed as { userId: string }
23
+ * return resolver.api(user); // user is typed as User
24
+ * });
25
+ */
26
+
27
+ /**
28
+ * Base type for API contracts
29
+ */
30
+ export type ApiContract = {
31
+ [apiName: string]: {
32
+ input: any;
33
+ output: any;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Extract input type from contract for a specific API
39
+ */
40
+ export type ApiInput<
41
+ TContract extends ApiContract,
42
+ TApiName extends keyof TContract
43
+ > = TContract[TApiName]['input'];
44
+
45
+ /**
46
+ * Extract output type from contract for a specific API
47
+ */
48
+ export type ApiOutput<
49
+ TContract extends ApiContract,
50
+ TApiName extends keyof TContract
51
+ > = TContract[TApiName]['output'];
@@ -1,5 +1,6 @@
1
1
  import Cookies from 'js-cookie';
2
2
  import { LambderApiResponse } from './LambderResponseBuilder';
3
+ import type { ApiContract } from './LambderApiContract';
3
4
 
4
5
  type VoidFunction = ()=>void|Promise<void>;
5
6
  type FetchTracker = { apiName: string, done: boolean, fetchEndCalled: boolean };
@@ -23,7 +24,7 @@ type FetchEndEventHandler = (params: {
23
24
  type ErrorHandler = (err: Error) => void|Promise<void>;
24
25
  type MessageHandler = (message:any) => void|Promise<void>;
25
26
 
26
- export default class LambderCaller {
27
+ export default class LambderCaller<TContract extends ApiContract = any> {
27
28
  private isCorsEnabled: boolean;
28
29
  private apiPath: string;
29
30
  private apiVersion?: string;
@@ -90,17 +91,24 @@ export default class LambderCaller {
90
91
  this.sessionCsrfCookieKey = sessionCsrfCookieKey;
91
92
  }
92
93
 
93
- async apiRaw<T=any>(apiName: string, payload?: any, options?: {
94
- headers?: Record<string, any>
95
- versionExpiredHandler?: VoidFunction,
96
- sessionExpiredHandler?: VoidFunction,
97
- messageHandler?: MessageHandler,
98
- errorMessageHandler?: MessageHandler,
99
- notAuthorizedHandler?: VoidFunction,
100
- errorHandler?: ErrorHandler,
101
- fetchStartedHandler?: FetchStartEventHandler,
102
- fetchEndedHandler?: FetchEndEventHandler,
103
- }): Promise<LambderApiResponse<T>|null|undefined>{
94
+ async apiRaw<
95
+ TApiName extends keyof TContract & string = string,
96
+ TOutput = TApiName extends keyof TContract ? TContract[TApiName]['output'] : any
97
+ >(
98
+ apiName: TApiName,
99
+ payload?: TApiName extends keyof TContract ? TContract[TApiName]['input'] : any,
100
+ options?: {
101
+ headers?: Record<string, any>
102
+ versionExpiredHandler?: VoidFunction,
103
+ sessionExpiredHandler?: VoidFunction,
104
+ messageHandler?: MessageHandler,
105
+ errorMessageHandler?: MessageHandler,
106
+ notAuthorizedHandler?: VoidFunction,
107
+ errorHandler?: ErrorHandler,
108
+ fetchStartedHandler?: FetchStartEventHandler,
109
+ fetchEndedHandler?: FetchEndEventHandler,
110
+ }
111
+ ): Promise<LambderApiResponse<TOutput>|null|undefined>{
104
112
  const headers = options?.headers;
105
113
  const fetchTracker: FetchTracker = { apiName, done: false, fetchEndCalled: false };
106
114
  try {
@@ -117,10 +125,15 @@ export default class LambderCaller {
117
125
  credentials: 'same-origin', redirect: 'follow', referrerPolicy: 'origin',
118
126
  headers: { 'Content-Type': 'application/json', ...(headers || {}) },
119
127
  body: JSON.stringify({ apiName, version, token, siteHost, payload, }),
120
- }).then(res=>{
128
+ }).then(async (res)=>{
121
129
  if(res.status >= 500) throw new Error("Request failed: " + res.status + " - " + res.statusText);
122
- return res.json();
123
- }) as LambderApiResponse<T>;
130
+ if(res.headers.get("Content-Type")?.includes("application/lambder-json-stream")){
131
+ const decompressed = res.json();
132
+ return decompressed;
133
+ }else{
134
+ return res.json();
135
+ }
136
+ }) as LambderApiResponse<TOutput>;
124
137
  fetchTracker.done = true;
125
138
  if(this.fetchEndedHandler){
126
139
  fetchTracker.fetchEndCalled = true;
@@ -157,7 +170,7 @@ export default class LambderCaller {
157
170
  if(this.notAuthorizedHandler){
158
171
  await this.notAuthorizedHandler();
159
172
  }else if(this.errorHandler){
160
- await this.errorHandler(new Error("Version Expired; Please refresh;"));
173
+ await this.errorHandler(new Error("Not Authorized;"));
161
174
  }
162
175
  return null;
163
176
  }
@@ -184,8 +197,25 @@ export default class LambderCaller {
184
197
  };
185
198
 
186
199
  // Use the same type for api but adjust the return type
187
- async api<T=any>(...params: Parameters<typeof LambderCaller.prototype.apiRaw>): Promise<T|null|undefined> {
188
- const result = await this.apiRaw<T>(...params);
200
+ async api<
201
+ TApiName extends keyof TContract & string = string,
202
+ TOutput = TApiName extends keyof TContract ? TContract[TApiName]['output'] : any
203
+ >(
204
+ apiName: TApiName,
205
+ payload?: TApiName extends keyof TContract ? TContract[TApiName]['input'] : any,
206
+ options?: {
207
+ headers?: Record<string, any>
208
+ versionExpiredHandler?: VoidFunction,
209
+ sessionExpiredHandler?: VoidFunction,
210
+ messageHandler?: MessageHandler,
211
+ errorMessageHandler?: MessageHandler,
212
+ notAuthorizedHandler?: VoidFunction,
213
+ errorHandler?: ErrorHandler,
214
+ fetchStartedHandler?: FetchStartEventHandler,
215
+ fetchEndedHandler?: FetchEndEventHandler,
216
+ }
217
+ ): Promise<TOutput|null|undefined> {
218
+ const result = await this.apiRaw<TApiName, TOutput>(apiName, payload, options);
189
219
  return result?.payload;
190
220
  }
191
221
 
@@ -199,7 +199,11 @@ export default class LambderResponseBuilder {
199
199
  }
200
200
  const mimeType = mimeTypeResolver.lookup(filePath);
201
201
  const body = this.readPublicFileSync(filePath);
202
- const bodyBase64 = Buffer.from(body).toString("base64");
202
+ if (body === "forbidden-public-path") {
203
+ throw { error: "Forbidden public path: " + filePath };
204
+ }
205
+ const bodyBuffer: Buffer = Buffer.isBuffer(body) ? body : Buffer.from(body);
206
+ const bodyBase64 = bodyBuffer.toString("base64");
203
207
  console.log("bodyBase64.length",bodyBase64.length);
204
208
  return this.fileBase64(bodyBase64, mimeType || "", headers);
205
209
  };
@@ -258,4 +262,39 @@ export default class LambderResponseBuilder {
258
262
  }, headers);
259
263
  };
260
264
 
265
+ private apiBinary<T=any>(
266
+ payload: T | null,
267
+ {
268
+ versionExpired, sessionExpired, notAuthorized,
269
+ message, errorMessage, logList,
270
+ }: LambderApiResponseConfig = {
271
+ versionExpired: undefined, sessionExpired: undefined, notAuthorized: undefined,
272
+ message: null, errorMessage: null, logList: undefined
273
+ },
274
+ headers?: Record<string, string|string[]>,
275
+ ): LambderResolverResponse {
276
+ const finalLogList = logList || this.ctx?._otherInternal?.logToApiResponseAccumulator;
277
+ const result = {
278
+ apiVersion: this.apiVersion,
279
+ payload,
280
+ ...(versionExpired ? {versionExpired} : {}),
281
+ ...(sessionExpired ? {sessionExpired} : {}),
282
+ ...(notAuthorized ? {notAuthorized} : {}),
283
+ ...(message ? {message} : {}),
284
+ ...(errorMessage ? {errorMessage} : {}),
285
+ ...(finalLogList?.length ? {logList: finalLogList} : {}),
286
+ };
287
+
288
+ return this.raw({
289
+ statusCode: 200,
290
+ isBase64Encoded: true,
291
+ multiValueHeaders: {
292
+ "Content-Type": ["application/lambder-json-stream"],
293
+ "Content-Encoding": ["gzip"],
294
+ ...convertToMultiHeader(headers)
295
+ },
296
+ body: Buffer.from(JSON.stringify(result)).toString("base64"),
297
+ });
298
+ };
299
+
261
300
  };
package/src/index.ts CHANGED
@@ -5,3 +5,10 @@ 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
+
9
+ // Type-safe API contract utilities
10
+ export {
11
+ type ApiContract,
12
+ type ApiInput,
13
+ type ApiOutput,
14
+ } from "./LambderApiContract.js";