@unrdf/serverless 26.4.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 +305 -0
- package/package.json +81 -0
- package/src/api/api-gateway-config.mjs +390 -0
- package/src/api/index.mjs +12 -0
- package/src/cdk/index.mjs +6 -0
- package/src/cdk/unrdf-stack.mjs +363 -0
- package/src/deploy/index.mjs +10 -0
- package/src/deploy/lambda-bundler.mjs +310 -0
- package/src/index.mjs +91 -0
- package/src/storage/dynamodb-adapter.mjs +324 -0
- package/src/storage/index.mjs +6 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview API Gateway Configuration - REST API setup for UNRDF
|
|
3
|
+
*
|
|
4
|
+
* @description
|
|
5
|
+
* Configures API Gateway REST endpoints for UNRDF serverless deployments.
|
|
6
|
+
* Handles routing, authentication, rate limiting, and request/response transformations.
|
|
7
|
+
*
|
|
8
|
+
* @module serverless/api/api-gateway-config
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
* @license MIT
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* API endpoint configuration
|
|
17
|
+
* @typedef {Object} EndpointConfig
|
|
18
|
+
* @property {string} path - API path
|
|
19
|
+
* @property {string} method - HTTP method
|
|
20
|
+
* @property {string} functionName - Lambda function name
|
|
21
|
+
* @property {boolean} authRequired - Require authentication
|
|
22
|
+
* @property {number} rateLimit - Requests per second limit
|
|
23
|
+
*/
|
|
24
|
+
const EndpointConfigSchema = z.object({
|
|
25
|
+
path: z.string(),
|
|
26
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
|
|
27
|
+
functionName: z.string(),
|
|
28
|
+
authRequired: z.boolean().default(false),
|
|
29
|
+
rateLimit: z.number().min(1).default(100),
|
|
30
|
+
timeout: z.number().min(1).max(29).default(29),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* API Gateway configuration
|
|
35
|
+
* @typedef {Object} ApiGatewayConfig
|
|
36
|
+
* @property {string} apiName - API name
|
|
37
|
+
* @property {string} stage - Deployment stage
|
|
38
|
+
* @property {EndpointConfig[]} endpoints - API endpoints
|
|
39
|
+
* @property {Object} cors - CORS configuration
|
|
40
|
+
*/
|
|
41
|
+
const ApiGatewayConfigSchema = z.object({
|
|
42
|
+
apiName: z.string(),
|
|
43
|
+
stage: z.enum(['dev', 'staging', 'prod']).default('dev'),
|
|
44
|
+
endpoints: z.array(EndpointConfigSchema),
|
|
45
|
+
cors: z
|
|
46
|
+
.object({
|
|
47
|
+
allowOrigins: z.array(z.string()).default(['*']),
|
|
48
|
+
allowMethods: z.array(z.string()).default(['GET', 'POST', 'PUT', 'DELETE']),
|
|
49
|
+
allowHeaders: z.array(z.string()).default(['Content-Type', 'Authorization']),
|
|
50
|
+
maxAge: z.number().default(3600),
|
|
51
|
+
})
|
|
52
|
+
.default({}),
|
|
53
|
+
throttling: z
|
|
54
|
+
.object({
|
|
55
|
+
burstLimit: z.number().default(5000),
|
|
56
|
+
rateLimit: z.number().default(10000),
|
|
57
|
+
})
|
|
58
|
+
.default({}),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* API Gateway Configuration Builder
|
|
63
|
+
*
|
|
64
|
+
* @class ApiGatewayConfig
|
|
65
|
+
*
|
|
66
|
+
* @description
|
|
67
|
+
* Fluent builder for API Gateway configurations with:
|
|
68
|
+
* - Endpoint routing and method mapping
|
|
69
|
+
* - CORS policy configuration
|
|
70
|
+
* - Rate limiting and throttling
|
|
71
|
+
* - Request/response validation
|
|
72
|
+
* - Authentication integration
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```javascript
|
|
76
|
+
* const config = new ApiGatewayConfig('unrdf-api')
|
|
77
|
+
* .addEndpoint('/query', 'POST', 'queryFunction')
|
|
78
|
+
* .addEndpoint('/triples', 'GET', 'listFunction')
|
|
79
|
+
* .enableCors(['https://example.com'])
|
|
80
|
+
* .setRateLimit(1000)
|
|
81
|
+
* .build();
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export class ApiGatewayConfig {
|
|
85
|
+
/**
|
|
86
|
+
* Configuration object
|
|
87
|
+
* @type {Object}
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
#config;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create API Gateway configuration
|
|
94
|
+
*
|
|
95
|
+
* @param {string} apiName - API name
|
|
96
|
+
* @param {string} [stage='dev'] - Deployment stage
|
|
97
|
+
*/
|
|
98
|
+
constructor(apiName, stage = 'dev') {
|
|
99
|
+
this.#config = {
|
|
100
|
+
apiName,
|
|
101
|
+
stage,
|
|
102
|
+
endpoints: [],
|
|
103
|
+
cors: {
|
|
104
|
+
allowOrigins: ['*'],
|
|
105
|
+
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
106
|
+
allowHeaders: ['Content-Type', 'Authorization'],
|
|
107
|
+
maxAge: 3600,
|
|
108
|
+
},
|
|
109
|
+
throttling: {
|
|
110
|
+
burstLimit: 5000,
|
|
111
|
+
rateLimit: 10000,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Add API endpoint
|
|
118
|
+
*
|
|
119
|
+
* @param {string} path - Endpoint path
|
|
120
|
+
* @param {string} method - HTTP method
|
|
121
|
+
* @param {string} functionName - Lambda function name
|
|
122
|
+
* @param {Object} [options={}] - Additional options
|
|
123
|
+
* @returns {ApiGatewayConfig} This instance for chaining
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```javascript
|
|
127
|
+
* config.addEndpoint('/query', 'POST', 'queryFunction', {
|
|
128
|
+
* authRequired: true,
|
|
129
|
+
* rateLimit: 100
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
addEndpoint(path, method, functionName, options = {}) {
|
|
134
|
+
this.#config.endpoints.push({
|
|
135
|
+
path,
|
|
136
|
+
method,
|
|
137
|
+
functionName,
|
|
138
|
+
authRequired: options.authRequired || false,
|
|
139
|
+
rateLimit: options.rateLimit || 100,
|
|
140
|
+
timeout: options.timeout || 29,
|
|
141
|
+
});
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Enable CORS with custom origins
|
|
147
|
+
*
|
|
148
|
+
* @param {string[]} allowOrigins - Allowed origins
|
|
149
|
+
* @param {Object} [options={}] - CORS options
|
|
150
|
+
* @returns {ApiGatewayConfig} This instance for chaining
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```javascript
|
|
154
|
+
* config.enableCors(['https://example.com'], {
|
|
155
|
+
* allowMethods: ['GET', 'POST'],
|
|
156
|
+
* maxAge: 7200
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
enableCors(allowOrigins, options = {}) {
|
|
161
|
+
this.#config.cors = {
|
|
162
|
+
allowOrigins,
|
|
163
|
+
allowMethods: options.allowMethods || this.#config.cors.allowMethods,
|
|
164
|
+
allowHeaders: options.allowHeaders || this.#config.cors.allowHeaders,
|
|
165
|
+
maxAge: options.maxAge || this.#config.cors.maxAge,
|
|
166
|
+
};
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Set API-wide rate limiting
|
|
172
|
+
*
|
|
173
|
+
* @param {number} rateLimit - Requests per second
|
|
174
|
+
* @param {number} [burstLimit] - Burst capacity
|
|
175
|
+
* @returns {ApiGatewayConfig} This instance for chaining
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```javascript
|
|
179
|
+
* config.setRateLimit(1000, 5000);
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
setRateLimit(rateLimit, burstLimit) {
|
|
183
|
+
this.#config.throttling = {
|
|
184
|
+
rateLimit,
|
|
185
|
+
burstLimit: burstLimit || rateLimit * 5,
|
|
186
|
+
};
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Set deployment stage
|
|
192
|
+
*
|
|
193
|
+
* @param {string} stage - Stage name
|
|
194
|
+
* @returns {ApiGatewayConfig} This instance for chaining
|
|
195
|
+
*/
|
|
196
|
+
setStage(stage) {
|
|
197
|
+
this.#config.stage = stage;
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Build and validate configuration
|
|
203
|
+
*
|
|
204
|
+
* @returns {Object} Validated configuration
|
|
205
|
+
*
|
|
206
|
+
* @throws {Error} If configuration is invalid
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```javascript
|
|
210
|
+
* const config = builder.build();
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
build() {
|
|
214
|
+
return ApiGatewayConfigSchema.parse(this.#config);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Export as OpenAPI 3.0 specification
|
|
219
|
+
*
|
|
220
|
+
* @returns {Object} OpenAPI specification
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```javascript
|
|
224
|
+
* const openapi = config.toOpenAPI();
|
|
225
|
+
* await fs.writeFile('openapi.json', JSON.stringify(openapi, null, 2));
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
toOpenAPI() {
|
|
229
|
+
const paths = {};
|
|
230
|
+
|
|
231
|
+
for (const endpoint of this.#config.endpoints) {
|
|
232
|
+
if (!paths[endpoint.path]) {
|
|
233
|
+
paths[endpoint.path] = {};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
paths[endpoint.path][endpoint.method.toLowerCase()] = {
|
|
237
|
+
summary: `${endpoint.method} ${endpoint.path}`,
|
|
238
|
+
operationId: endpoint.functionName,
|
|
239
|
+
security: endpoint.authRequired ? [{ apiKey: [] }] : [],
|
|
240
|
+
responses: {
|
|
241
|
+
200: {
|
|
242
|
+
description: 'Successful response',
|
|
243
|
+
content: {
|
|
244
|
+
'application/json': {
|
|
245
|
+
schema: { type: 'object' },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
400: { description: 'Bad request' },
|
|
250
|
+
401: { description: 'Unauthorized' },
|
|
251
|
+
429: { description: 'Too many requests' },
|
|
252
|
+
500: { description: 'Internal server error' },
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
openapi: '3.0.0',
|
|
259
|
+
info: {
|
|
260
|
+
title: this.#config.apiName,
|
|
261
|
+
version: '1.0.0',
|
|
262
|
+
description: 'UNRDF Serverless API',
|
|
263
|
+
},
|
|
264
|
+
servers: [
|
|
265
|
+
{
|
|
266
|
+
url: `https://api.example.com/${this.#config.stage}`,
|
|
267
|
+
description: `${this.#config.stage} environment`,
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
paths,
|
|
271
|
+
components: {
|
|
272
|
+
securitySchemes: {
|
|
273
|
+
apiKey: {
|
|
274
|
+
type: 'apiKey',
|
|
275
|
+
in: 'header',
|
|
276
|
+
name: 'X-API-Key',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Create default UNRDF API configuration
|
|
286
|
+
*
|
|
287
|
+
* @param {string} [stage='dev'] - Deployment stage
|
|
288
|
+
* @returns {Object} API Gateway configuration
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```javascript
|
|
292
|
+
* const config = createDefaultApiConfig('prod');
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
export function createDefaultApiConfig(stage = 'dev') {
|
|
296
|
+
return new ApiGatewayConfig(`unrdf-api-${stage}`, stage)
|
|
297
|
+
.addEndpoint('/query', 'POST', 'queryFunction', {
|
|
298
|
+
authRequired: stage === 'prod',
|
|
299
|
+
rateLimit: 100,
|
|
300
|
+
})
|
|
301
|
+
.addEndpoint('/triples', 'GET', 'queryFunction', {
|
|
302
|
+
rateLimit: 200,
|
|
303
|
+
})
|
|
304
|
+
.addEndpoint('/triples', 'POST', 'ingestFunction', {
|
|
305
|
+
authRequired: true,
|
|
306
|
+
rateLimit: 50,
|
|
307
|
+
})
|
|
308
|
+
.addEndpoint('/health', 'GET', 'queryFunction', {
|
|
309
|
+
rateLimit: 1000,
|
|
310
|
+
})
|
|
311
|
+
.enableCors(['*'])
|
|
312
|
+
.setRateLimit(10000, 50000)
|
|
313
|
+
.build();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Validate API Gateway request
|
|
318
|
+
*
|
|
319
|
+
* @param {Object} event - API Gateway event
|
|
320
|
+
* @param {Object} schema - Zod validation schema
|
|
321
|
+
* @returns {Object} Validated request body
|
|
322
|
+
*
|
|
323
|
+
* @throws {Error} If validation fails
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```javascript
|
|
327
|
+
* export async function handler(event) {
|
|
328
|
+
* const body = validateApiRequest(event, z.object({
|
|
329
|
+
* query: z.string(),
|
|
330
|
+
* bindings: z.record(z.string()).optional()
|
|
331
|
+
* }));
|
|
332
|
+
* // ... handle request
|
|
333
|
+
* }
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
export function validateApiRequest(event, schema) {
|
|
337
|
+
try {
|
|
338
|
+
const body = JSON.parse(event.body || '{}');
|
|
339
|
+
return schema.parse(body);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
throw new Error(`Request validation failed: ${error.message}`, { cause: error });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Create API Gateway response
|
|
347
|
+
*
|
|
348
|
+
* @param {number} statusCode - HTTP status code
|
|
349
|
+
* @param {Object} body - Response body
|
|
350
|
+
* @param {Object} [headers={}] - Additional headers
|
|
351
|
+
* @returns {Object} API Gateway response
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```javascript
|
|
355
|
+
* return createApiResponse(200, { results: data });
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
export function createApiResponse(statusCode, body, headers = {}) {
|
|
359
|
+
return {
|
|
360
|
+
statusCode,
|
|
361
|
+
headers: {
|
|
362
|
+
'Content-Type': 'application/json',
|
|
363
|
+
'Access-Control-Allow-Origin': '*',
|
|
364
|
+
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE',
|
|
365
|
+
...headers,
|
|
366
|
+
},
|
|
367
|
+
body: JSON.stringify(body),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Create error response
|
|
373
|
+
*
|
|
374
|
+
* @param {Error} error - Error object
|
|
375
|
+
* @param {number} [statusCode=500] - HTTP status code
|
|
376
|
+
* @returns {Object} Error response
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```javascript
|
|
380
|
+
* catch (error) {
|
|
381
|
+
* return createErrorResponse(error, 400);
|
|
382
|
+
* }
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
export function createErrorResponse(error, statusCode = 500) {
|
|
386
|
+
return createApiResponse(statusCode, {
|
|
387
|
+
error: error.message,
|
|
388
|
+
type: error.constructor.name,
|
|
389
|
+
});
|
|
390
|
+
}
|