@fnd-platform/api 1.0.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/lib/api-project.d.ts +99 -0
- package/lib/api-project.d.ts.map +1 -0
- package/lib/api-project.js +668 -0
- package/lib/api-project.js.map +1 -0
- package/lib/handlers/content.d.ts +52 -0
- package/lib/handlers/content.d.ts.map +1 -0
- package/lib/handlers/content.js +122 -0
- package/lib/handlers/content.js.map +1 -0
- package/lib/handlers/health.d.ts +43 -0
- package/lib/handlers/health.d.ts.map +1 -0
- package/lib/handlers/health.js +43 -0
- package/lib/handlers/health.js.map +1 -0
- package/lib/handlers/media.d.ts +86 -0
- package/lib/handlers/media.d.ts.map +1 -0
- package/lib/handlers/media.js +287 -0
- package/lib/handlers/media.js.map +1 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +50 -0
- package/lib/index.js.map +1 -0
- package/lib/lib/errors.d.ts +114 -0
- package/lib/lib/errors.d.ts.map +1 -0
- package/lib/lib/errors.js +154 -0
- package/lib/lib/errors.js.map +1 -0
- package/lib/lib/middleware.d.ts +33 -0
- package/lib/lib/middleware.d.ts.map +1 -0
- package/lib/lib/middleware.js +36 -0
- package/lib/lib/middleware.js.map +1 -0
- package/lib/lib/request.d.ts +90 -0
- package/lib/lib/request.d.ts.map +1 -0
- package/lib/lib/request.js +115 -0
- package/lib/lib/request.js.map +1 -0
- package/lib/lib/response.d.ts +131 -0
- package/lib/lib/response.d.ts.map +1 -0
- package/lib/lib/response.js +163 -0
- package/lib/lib/response.js.map +1 -0
- package/lib/middleware/auth.d.ts +57 -0
- package/lib/middleware/auth.d.ts.map +1 -0
- package/lib/middleware/auth.js +62 -0
- package/lib/middleware/auth.js.map +1 -0
- package/lib/middleware/cors.d.ts +51 -0
- package/lib/middleware/cors.d.ts.map +1 -0
- package/lib/middleware/cors.js +62 -0
- package/lib/middleware/cors.js.map +1 -0
- package/lib/middleware/error-handler.d.ts +38 -0
- package/lib/middleware/error-handler.d.ts.map +1 -0
- package/lib/middleware/error-handler.js +49 -0
- package/lib/middleware/error-handler.js.map +1 -0
- package/lib/middleware/index.d.ts +15 -0
- package/lib/middleware/index.d.ts.map +1 -0
- package/lib/middleware/index.js +49 -0
- package/lib/middleware/index.js.map +1 -0
- package/lib/middleware/logging.d.ts +48 -0
- package/lib/middleware/logging.d.ts.map +1 -0
- package/lib/middleware/logging.js +58 -0
- package/lib/middleware/logging.js.map +1 -0
- package/lib/middleware/validation.d.ts +41 -0
- package/lib/middleware/validation.d.ts.map +1 -0
- package/lib/middleware/validation.js +76 -0
- package/lib/middleware/validation.js.map +1 -0
- package/lib/options.d.ts +48 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +3 -0
- package/lib/options.js.map +1 -0
- package/lib/types/api.d.ts +108 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/api.js +11 -0
- package/lib/types/api.js.map +1 -0
- package/lib/types/middleware.d.ts +59 -0
- package/lib/types/middleware.d.ts.map +1 -0
- package/lib/types/middleware.js +11 -0
- package/lib/types/middleware.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FndApiProject = void 0;
|
|
4
|
+
const projen_1 = require("projen");
|
|
5
|
+
const { TypeScriptModuleResolution } = projen_1.javascript;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Lambda API package within an fnd-platform monorepo.
|
|
8
|
+
*
|
|
9
|
+
* This class generates a complete API package scaffold with:
|
|
10
|
+
* - Lambda handler templates
|
|
11
|
+
* - Response helper utilities
|
|
12
|
+
* - Error handling classes
|
|
13
|
+
* - TypeScript types for API interactions
|
|
14
|
+
*
|
|
15
|
+
* Files are generated using Projen's SampleFile, allowing users to modify
|
|
16
|
+
* them after generation while still maintaining the project structure.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { FndMonorepoProject } from '@fnd-platform/core';
|
|
21
|
+
* import { FndApiProject } from '@fnd-platform/api';
|
|
22
|
+
*
|
|
23
|
+
* const monorepo = new FndMonorepoProject({
|
|
24
|
+
* name: 'my-app',
|
|
25
|
+
* defaultReleaseBranch: 'main',
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* const api = new FndApiProject({
|
|
29
|
+
* parent: monorepo,
|
|
30
|
+
* name: 'api',
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* monorepo.synth();
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class FndApiProject extends projen_1.typescript.TypeScriptProject {
|
|
37
|
+
/**
|
|
38
|
+
* Reference to the parent monorepo project.
|
|
39
|
+
*/
|
|
40
|
+
parentProject;
|
|
41
|
+
/**
|
|
42
|
+
* Whether DynamoDB integration is enabled.
|
|
43
|
+
*/
|
|
44
|
+
dynamodbEnabled;
|
|
45
|
+
/**
|
|
46
|
+
* Whether Cognito auth integration is enabled.
|
|
47
|
+
*/
|
|
48
|
+
cognitoEnabled;
|
|
49
|
+
/**
|
|
50
|
+
* Whether CORS is enabled.
|
|
51
|
+
*/
|
|
52
|
+
corsEnabled;
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new FndApiProject.
|
|
55
|
+
*
|
|
56
|
+
* @param options - Configuration options for the API project
|
|
57
|
+
* @throws {Error} If required options are missing
|
|
58
|
+
*/
|
|
59
|
+
constructor(options) {
|
|
60
|
+
// Validate required options
|
|
61
|
+
if (!options.parent) {
|
|
62
|
+
throw new Error('FndApiProject requires a parent option');
|
|
63
|
+
}
|
|
64
|
+
if (!options.name) {
|
|
65
|
+
throw new Error('FndApiProject requires a name option');
|
|
66
|
+
}
|
|
67
|
+
const outdir = options.outdir ?? `packages/${options.name}`;
|
|
68
|
+
const packageName = `@${options.parent.name}/${options.name}`;
|
|
69
|
+
super({
|
|
70
|
+
name: packageName,
|
|
71
|
+
parent: options.parent,
|
|
72
|
+
outdir,
|
|
73
|
+
defaultReleaseBranch: 'main',
|
|
74
|
+
// TypeScript configuration for Lambda
|
|
75
|
+
tsconfig: {
|
|
76
|
+
compilerOptions: {
|
|
77
|
+
target: 'ES2022',
|
|
78
|
+
module: 'NodeNext',
|
|
79
|
+
moduleResolution: TypeScriptModuleResolution.NODE_NEXT,
|
|
80
|
+
outDir: 'lib',
|
|
81
|
+
rootDir: 'src',
|
|
82
|
+
declaration: true,
|
|
83
|
+
declarationMap: true,
|
|
84
|
+
sourceMap: true,
|
|
85
|
+
strict: true,
|
|
86
|
+
esModuleInterop: true,
|
|
87
|
+
skipLibCheck: true,
|
|
88
|
+
forceConsistentCasingInFileNames: true,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
// Disable Projen defaults - will inherit from monorepo or configure manually
|
|
92
|
+
sampleCode: false,
|
|
93
|
+
jest: false,
|
|
94
|
+
eslint: false,
|
|
95
|
+
prettier: false,
|
|
96
|
+
projenrcTs: false,
|
|
97
|
+
});
|
|
98
|
+
this.parentProject = options.parent;
|
|
99
|
+
this.dynamodbEnabled = options.dynamodb ?? true;
|
|
100
|
+
this.cognitoEnabled = options.cognito ?? true;
|
|
101
|
+
this.corsEnabled = options.cors ?? true;
|
|
102
|
+
// Add dependencies
|
|
103
|
+
this.addApiDependencies();
|
|
104
|
+
// Generate directory structure and files
|
|
105
|
+
this.generateHandlersDirectory();
|
|
106
|
+
this.generateLibDirectory();
|
|
107
|
+
this.generateTypesDirectory();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Adds API-specific dependencies to the project.
|
|
111
|
+
*/
|
|
112
|
+
addApiDependencies() {
|
|
113
|
+
// Runtime dependencies
|
|
114
|
+
if (this.dynamodbEnabled) {
|
|
115
|
+
this.addDeps('@aws-sdk/client-dynamodb', '@aws-sdk/lib-dynamodb');
|
|
116
|
+
}
|
|
117
|
+
// Dev dependencies
|
|
118
|
+
this.addDevDeps('@types/aws-lambda', 'esbuild', 'vitest', '@vitest/coverage-v8');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Generates the handlers directory with Lambda handler templates.
|
|
122
|
+
*/
|
|
123
|
+
generateHandlersDirectory() {
|
|
124
|
+
// Health check handler - always included
|
|
125
|
+
new projen_1.SampleFile(this, 'src/handlers/health.ts', {
|
|
126
|
+
contents: this.getHealthHandlerTemplate(),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Generates the lib directory with utility files.
|
|
131
|
+
*/
|
|
132
|
+
generateLibDirectory() {
|
|
133
|
+
// Response helpers
|
|
134
|
+
new projen_1.SampleFile(this, 'src/lib/response.ts', {
|
|
135
|
+
contents: this.getResponseTemplate(),
|
|
136
|
+
});
|
|
137
|
+
// Error classes
|
|
138
|
+
new projen_1.SampleFile(this, 'src/lib/errors.ts', {
|
|
139
|
+
contents: this.getErrorsTemplate(),
|
|
140
|
+
});
|
|
141
|
+
// Middleware utilities (stub for Sprint 03)
|
|
142
|
+
new projen_1.SampleFile(this, 'src/lib/middleware.ts', {
|
|
143
|
+
contents: this.getMiddlewareTemplate(),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generates the types directory with TypeScript type definitions.
|
|
148
|
+
*/
|
|
149
|
+
generateTypesDirectory() {
|
|
150
|
+
// API types
|
|
151
|
+
new projen_1.SampleFile(this, 'src/types/api.ts', {
|
|
152
|
+
contents: this.getApiTypesTemplate(),
|
|
153
|
+
});
|
|
154
|
+
// Entity types stub
|
|
155
|
+
new projen_1.SampleFile(this, 'src/types/entities.ts', {
|
|
156
|
+
contents: this.getEntityTypesTemplate(),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Returns the health handler template content.
|
|
161
|
+
*/
|
|
162
|
+
getHealthHandlerTemplate() {
|
|
163
|
+
return `import type { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda';
|
|
164
|
+
import { success } from '../lib/response';
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Health check endpoint handler.
|
|
168
|
+
* Returns the current health status of the API.
|
|
169
|
+
*
|
|
170
|
+
* @returns Health status with timestamp
|
|
171
|
+
*/
|
|
172
|
+
export const handler: APIGatewayProxyHandler = async (): Promise<APIGatewayProxyResult> => {
|
|
173
|
+
return success({
|
|
174
|
+
status: 'healthy',
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
version: process.env.API_VERSION ?? '0.0.0',
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the response helpers template content.
|
|
183
|
+
*/
|
|
184
|
+
getResponseTemplate() {
|
|
185
|
+
const corsHeaders = this.corsEnabled
|
|
186
|
+
? `
|
|
187
|
+
'Access-Control-Allow-Origin': '*',
|
|
188
|
+
'Access-Control-Allow-Headers': 'Content-Type,Authorization',`
|
|
189
|
+
: '';
|
|
190
|
+
return `import type { APIGatewayProxyResult } from 'aws-lambda';
|
|
191
|
+
import {
|
|
192
|
+
ApiError,
|
|
193
|
+
NotFoundError,
|
|
194
|
+
UnauthorizedError,
|
|
195
|
+
ForbiddenError,
|
|
196
|
+
ValidationError,
|
|
197
|
+
} from './errors';
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Success response envelope structure.
|
|
201
|
+
*/
|
|
202
|
+
export interface SuccessResponse<T = unknown> {
|
|
203
|
+
success: true;
|
|
204
|
+
data: T;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Error response envelope structure.
|
|
209
|
+
*/
|
|
210
|
+
export interface ErrorResponse {
|
|
211
|
+
success: false;
|
|
212
|
+
error: {
|
|
213
|
+
code: string;
|
|
214
|
+
message: string;
|
|
215
|
+
details?: unknown;
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Default headers included in all responses.
|
|
221
|
+
*/
|
|
222
|
+
function defaultHeaders(): Record<string, string> {
|
|
223
|
+
return {
|
|
224
|
+
'Content-Type': 'application/json',${corsHeaders}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Creates a successful API response.
|
|
230
|
+
*
|
|
231
|
+
* @param data - Response body data
|
|
232
|
+
* @param statusCode - HTTP status code (default: 200)
|
|
233
|
+
* @returns API Gateway proxy result with success envelope
|
|
234
|
+
*/
|
|
235
|
+
export function success<T>(data: T, statusCode = 200): APIGatewayProxyResult {
|
|
236
|
+
const body: SuccessResponse<T> = { success: true, data };
|
|
237
|
+
return {
|
|
238
|
+
statusCode,
|
|
239
|
+
headers: defaultHeaders(),
|
|
240
|
+
body: JSON.stringify(body),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Creates an error API response.
|
|
246
|
+
*
|
|
247
|
+
* @param err - Error object or message string
|
|
248
|
+
* @param statusCode - HTTP status code (default: 500)
|
|
249
|
+
* @returns API Gateway proxy result with error envelope
|
|
250
|
+
*/
|
|
251
|
+
export function error(err: Error | string, statusCode = 500): APIGatewayProxyResult {
|
|
252
|
+
const message = typeof err === 'string' ? err : err.message;
|
|
253
|
+
const code = err instanceof ApiError ? err.code : 'INTERNAL_ERROR';
|
|
254
|
+
const details = err instanceof ApiError ? err.details : undefined;
|
|
255
|
+
|
|
256
|
+
const body: ErrorResponse = {
|
|
257
|
+
success: false,
|
|
258
|
+
error: { code, message, details },
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
statusCode,
|
|
263
|
+
headers: defaultHeaders(),
|
|
264
|
+
body: JSON.stringify(body),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Creates a 404 Not Found response.
|
|
270
|
+
*
|
|
271
|
+
* @param message - Error message (default: 'Resource not found')
|
|
272
|
+
* @returns API Gateway proxy result with 404 status
|
|
273
|
+
*/
|
|
274
|
+
export function notFound(message = 'Resource not found'): APIGatewayProxyResult {
|
|
275
|
+
return error(new NotFoundError(message), 404);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Creates a 401 Unauthorized response.
|
|
280
|
+
*
|
|
281
|
+
* @param message - Error message (default: 'Unauthorized')
|
|
282
|
+
* @returns API Gateway proxy result with 401 status
|
|
283
|
+
*/
|
|
284
|
+
export function unauthorized(message = 'Unauthorized'): APIGatewayProxyResult {
|
|
285
|
+
return error(new UnauthorizedError(message), 401);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Creates a 403 Forbidden response.
|
|
290
|
+
*
|
|
291
|
+
* @param message - Error message (default: 'Forbidden')
|
|
292
|
+
* @returns API Gateway proxy result with 403 status
|
|
293
|
+
*/
|
|
294
|
+
export function forbidden(message = 'Forbidden'): APIGatewayProxyResult {
|
|
295
|
+
return error(new ForbiddenError(message), 403);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Creates a 400 Bad Request response.
|
|
300
|
+
*
|
|
301
|
+
* @param message - Error message (default: 'Bad request')
|
|
302
|
+
* @param details - Optional validation error details
|
|
303
|
+
* @returns API Gateway proxy result with 400 status
|
|
304
|
+
*/
|
|
305
|
+
export function badRequest(message = 'Bad request', details?: unknown): APIGatewayProxyResult {
|
|
306
|
+
return error(new ValidationError(message, details), 400);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Creates a 201 Created response.
|
|
311
|
+
*
|
|
312
|
+
* @param data - Created resource data
|
|
313
|
+
* @returns API Gateway proxy result with 201 status
|
|
314
|
+
*/
|
|
315
|
+
export function created<T>(data: T): APIGatewayProxyResult {
|
|
316
|
+
return success(data, 201);
|
|
317
|
+
}
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Returns the error classes template content.
|
|
322
|
+
*/
|
|
323
|
+
getErrorsTemplate() {
|
|
324
|
+
return `/**
|
|
325
|
+
* Custom error classes for API responses.
|
|
326
|
+
*
|
|
327
|
+
* All API errors extend ApiError and include an error code,
|
|
328
|
+
* HTTP status code, and optional details for structured error handling.
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Base API error class.
|
|
333
|
+
* All API errors should extend this class.
|
|
334
|
+
*/
|
|
335
|
+
export class ApiError extends Error {
|
|
336
|
+
/**
|
|
337
|
+
* Creates a new ApiError.
|
|
338
|
+
*
|
|
339
|
+
* @param message - Human-readable error message
|
|
340
|
+
* @param code - Machine-readable error code (UPPER_SNAKE_CASE)
|
|
341
|
+
* @param statusCode - HTTP status code (default: 500)
|
|
342
|
+
* @param details - Optional additional error details
|
|
343
|
+
*/
|
|
344
|
+
constructor(
|
|
345
|
+
message: string,
|
|
346
|
+
public readonly code: string,
|
|
347
|
+
public readonly statusCode: number = 500,
|
|
348
|
+
public readonly details?: unknown
|
|
349
|
+
) {
|
|
350
|
+
super(message);
|
|
351
|
+
this.name = 'ApiError';
|
|
352
|
+
|
|
353
|
+
// Maintains proper stack trace for where error was thrown
|
|
354
|
+
Error.captureStackTrace(this, this.constructor);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Converts the error to a JSON-serializable object.
|
|
359
|
+
*/
|
|
360
|
+
toJSON(): { code: string; message: string; details?: unknown } {
|
|
361
|
+
return {
|
|
362
|
+
code: this.code,
|
|
363
|
+
message: this.message,
|
|
364
|
+
details: this.details,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 404 Not Found error.
|
|
371
|
+
*/
|
|
372
|
+
export class NotFoundError extends ApiError {
|
|
373
|
+
constructor(message = 'Resource not found') {
|
|
374
|
+
super(message, 'NOT_FOUND', 404);
|
|
375
|
+
this.name = 'NotFoundError';
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* 400 Validation error.
|
|
381
|
+
*/
|
|
382
|
+
export class ValidationError extends ApiError {
|
|
383
|
+
constructor(message = 'Validation failed', details?: unknown) {
|
|
384
|
+
super(message, 'VALIDATION_ERROR', 400, details);
|
|
385
|
+
this.name = 'ValidationError';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 401 Unauthorized error.
|
|
391
|
+
*/
|
|
392
|
+
export class UnauthorizedError extends ApiError {
|
|
393
|
+
constructor(message = 'Unauthorized') {
|
|
394
|
+
super(message, 'UNAUTHORIZED', 401);
|
|
395
|
+
this.name = 'UnauthorizedError';
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 403 Forbidden error.
|
|
401
|
+
*/
|
|
402
|
+
export class ForbiddenError extends ApiError {
|
|
403
|
+
constructor(message = 'Forbidden') {
|
|
404
|
+
super(message, 'FORBIDDEN', 403);
|
|
405
|
+
this.name = 'ForbiddenError';
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 409 Conflict error.
|
|
411
|
+
*/
|
|
412
|
+
export class ConflictError extends ApiError {
|
|
413
|
+
constructor(message = 'Resource conflict') {
|
|
414
|
+
super(message, 'CONFLICT', 409);
|
|
415
|
+
this.name = 'ConflictError';
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
`;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Returns the middleware template content.
|
|
422
|
+
*/
|
|
423
|
+
getMiddlewareTemplate() {
|
|
424
|
+
return `import type { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda';
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Middleware function type.
|
|
428
|
+
* Takes a handler and returns a wrapped handler.
|
|
429
|
+
*/
|
|
430
|
+
export type Middleware = (
|
|
431
|
+
handler: APIGatewayProxyHandler
|
|
432
|
+
) => APIGatewayProxyHandler;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Composes multiple middleware functions into a single middleware.
|
|
436
|
+
* Middleware is applied from right to left (last middleware wraps first).
|
|
437
|
+
*
|
|
438
|
+
* @param middlewares - Middleware functions to compose
|
|
439
|
+
* @returns Composed middleware function
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* \`\`\`typescript
|
|
443
|
+
* const handler = compose(
|
|
444
|
+
* withErrorHandler(),
|
|
445
|
+
* withAuth(),
|
|
446
|
+
* withValidation(schema)
|
|
447
|
+
* )(baseHandler);
|
|
448
|
+
* \`\`\`
|
|
449
|
+
*
|
|
450
|
+
* @note Full middleware implementation will be added in Sprint 03.
|
|
451
|
+
*/
|
|
452
|
+
export function compose(...middlewares: Middleware[]): Middleware {
|
|
453
|
+
return (handler: APIGatewayProxyHandler): APIGatewayProxyHandler => {
|
|
454
|
+
return middlewares.reduceRight(
|
|
455
|
+
(wrapped, middleware) => middleware(wrapped),
|
|
456
|
+
handler
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Error handler middleware.
|
|
463
|
+
* Catches errors thrown by the handler and converts them to API responses.
|
|
464
|
+
*
|
|
465
|
+
* @returns Middleware that wraps error handling
|
|
466
|
+
*
|
|
467
|
+
* @note Full implementation will be added in Sprint 03.
|
|
468
|
+
*/
|
|
469
|
+
export function withErrorHandler(): Middleware {
|
|
470
|
+
return (handler: APIGatewayProxyHandler): APIGatewayProxyHandler => {
|
|
471
|
+
return async (event, context, callback): Promise<APIGatewayProxyResult> => {
|
|
472
|
+
try {
|
|
473
|
+
const result = await handler(event, context, callback);
|
|
474
|
+
return result as APIGatewayProxyResult;
|
|
475
|
+
} catch (err) {
|
|
476
|
+
const message = err instanceof Error ? err.message : 'Internal Server Error';
|
|
477
|
+
const statusCode = (err as { statusCode?: number }).statusCode ?? 500;
|
|
478
|
+
const code = (err as { code?: string }).code ?? 'INTERNAL_ERROR';
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
statusCode,
|
|
482
|
+
headers: { 'Content-Type': 'application/json' },
|
|
483
|
+
body: JSON.stringify({
|
|
484
|
+
success: false,
|
|
485
|
+
error: { code, message },
|
|
486
|
+
}),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Additional middleware will be implemented in Sprint 03:
|
|
494
|
+
// - withAuth()
|
|
495
|
+
// - withValidation()
|
|
496
|
+
// - withCors()
|
|
497
|
+
// - withRateLimit()
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Returns the API types template content.
|
|
502
|
+
*/
|
|
503
|
+
getApiTypesTemplate() {
|
|
504
|
+
return `import type {
|
|
505
|
+
APIGatewayProxyEvent,
|
|
506
|
+
APIGatewayProxyResult,
|
|
507
|
+
Context,
|
|
508
|
+
} from 'aws-lambda';
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Standard Lambda handler type.
|
|
512
|
+
*/
|
|
513
|
+
export type Handler = (
|
|
514
|
+
event: APIGatewayProxyEvent,
|
|
515
|
+
context: Context
|
|
516
|
+
) => Promise<APIGatewayProxyResult>;
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Cognito JWT claims structure.
|
|
520
|
+
*/
|
|
521
|
+
export interface CognitoClaims {
|
|
522
|
+
/** User's unique identifier (Cognito sub) */
|
|
523
|
+
sub: string;
|
|
524
|
+
/** User's email address */
|
|
525
|
+
email: string;
|
|
526
|
+
/** User's Cognito groups (roles) */
|
|
527
|
+
'cognito:groups'?: string[];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* API Gateway event with Cognito authorizer claims.
|
|
532
|
+
*/
|
|
533
|
+
export interface AuthenticatedEvent extends APIGatewayProxyEvent {
|
|
534
|
+
requestContext: APIGatewayProxyEvent['requestContext'] & {
|
|
535
|
+
authorizer: {
|
|
536
|
+
claims: CognitoClaims;
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Lambda handler type for authenticated requests.
|
|
543
|
+
*/
|
|
544
|
+
export type AuthenticatedHandler = (
|
|
545
|
+
event: AuthenticatedEvent,
|
|
546
|
+
context: Context
|
|
547
|
+
) => Promise<APIGatewayProxyResult>;
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Pagination parameters for list endpoints.
|
|
551
|
+
*/
|
|
552
|
+
export interface PaginatedRequest {
|
|
553
|
+
/** Maximum number of items to return */
|
|
554
|
+
limit?: number;
|
|
555
|
+
/** Cursor for pagination */
|
|
556
|
+
cursor?: string;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Paginated response structure.
|
|
561
|
+
*/
|
|
562
|
+
export interface PaginatedResponse<T> {
|
|
563
|
+
/** Array of items for the current page */
|
|
564
|
+
items: T[];
|
|
565
|
+
/** Cursor for the next page, undefined if no more pages */
|
|
566
|
+
nextCursor?: string;
|
|
567
|
+
/** Total count of items (optional) */
|
|
568
|
+
total?: number;
|
|
569
|
+
}
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Returns the entity types template content.
|
|
574
|
+
*/
|
|
575
|
+
getEntityTypesTemplate() {
|
|
576
|
+
return `/**
|
|
577
|
+
* Base entity interface with common fields.
|
|
578
|
+
*/
|
|
579
|
+
export interface BaseEntity {
|
|
580
|
+
/**
|
|
581
|
+
* Unique identifier.
|
|
582
|
+
*/
|
|
583
|
+
id: string;
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* ISO 8601 timestamp when the entity was created.
|
|
587
|
+
*/
|
|
588
|
+
createdAt: string;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* ISO 8601 timestamp when the entity was last updated.
|
|
592
|
+
*/
|
|
593
|
+
updatedAt: string;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Content entity for CMS content items.
|
|
598
|
+
* This is a template - customize based on your content model.
|
|
599
|
+
*/
|
|
600
|
+
export interface ContentEntity extends BaseEntity {
|
|
601
|
+
/**
|
|
602
|
+
* Content title.
|
|
603
|
+
*/
|
|
604
|
+
title: string;
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* URL-friendly slug.
|
|
608
|
+
*/
|
|
609
|
+
slug: string;
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Content body (HTML or markdown).
|
|
613
|
+
*/
|
|
614
|
+
content: string;
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Content status.
|
|
618
|
+
*/
|
|
619
|
+
status: 'draft' | 'published' | 'archived';
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Author user ID.
|
|
623
|
+
*/
|
|
624
|
+
authorId: string;
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Content type identifier.
|
|
628
|
+
*/
|
|
629
|
+
contentType: string;
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Optional metadata.
|
|
633
|
+
*/
|
|
634
|
+
metadata?: Record<string, unknown>;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* User profile entity.
|
|
639
|
+
* This is a template - customize based on your user model.
|
|
640
|
+
*/
|
|
641
|
+
export interface UserEntity extends BaseEntity {
|
|
642
|
+
/**
|
|
643
|
+
* User's email address.
|
|
644
|
+
*/
|
|
645
|
+
email: string;
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Display name.
|
|
649
|
+
*/
|
|
650
|
+
displayName?: string;
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* User role.
|
|
654
|
+
*/
|
|
655
|
+
role: 'admin' | 'editor' | 'viewer';
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Profile image URL.
|
|
659
|
+
*/
|
|
660
|
+
avatarUrl?: string;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Add your custom entity types below
|
|
664
|
+
`;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
exports.FndApiProject = FndApiProject;
|
|
668
|
+
//# sourceMappingURL=api-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-project.js","sourceRoot":"","sources":["../src/api-project.ts"],"names":[],"mappings":"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"}
|