@venturekit/runtime 0.0.0-dev.20260307234057
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 +191 -0
- package/dist/context.d.ts +86 -0
- package/dist/context.js +76 -0
- package/dist/errors.d.ts +80 -0
- package/dist/errors.js +134 -0
- package/dist/handler.d.ts +71 -0
- package/dist/handler.js +176 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +53 -0
- package/dist/logger.d.ts +72 -0
- package/dist/logger.js +105 -0
- package/dist/middleware.d.ts +46 -0
- package/dist/middleware.js +147 -0
- package/dist/response.d.ts +75 -0
- package/dist/response.js +107 -0
- package/dist/ws.d.ts +138 -0
- package/dist/ws.js +277 -0
- package/package.json +61 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VentureKit Middleware
|
|
4
|
+
*
|
|
5
|
+
* Composable middleware for request processing.
|
|
6
|
+
* Extensible for auth, tenancy, rate limiting, etc.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.compose = compose;
|
|
10
|
+
exports.loggingMiddleware = loggingMiddleware;
|
|
11
|
+
exports.corsMiddleware = corsMiddleware;
|
|
12
|
+
exports.timeoutMiddleware = timeoutMiddleware;
|
|
13
|
+
exports.errorBoundaryMiddleware = errorBoundaryMiddleware;
|
|
14
|
+
/**
|
|
15
|
+
* Compose multiple middleware into a single function
|
|
16
|
+
*/
|
|
17
|
+
function compose(middlewares) {
|
|
18
|
+
return async (ctx, finalHandler) => {
|
|
19
|
+
let index = -1;
|
|
20
|
+
const dispatch = async (i) => {
|
|
21
|
+
if (i <= index) {
|
|
22
|
+
throw new Error('next() called multiple times');
|
|
23
|
+
}
|
|
24
|
+
index = i;
|
|
25
|
+
if (i >= middlewares.length) {
|
|
26
|
+
return finalHandler();
|
|
27
|
+
}
|
|
28
|
+
const middleware = middlewares[i];
|
|
29
|
+
return middleware.fn(ctx, () => dispatch(i + 1));
|
|
30
|
+
};
|
|
31
|
+
return dispatch(0);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Logging middleware - logs request/response
|
|
36
|
+
*/
|
|
37
|
+
function loggingMiddleware(logger) {
|
|
38
|
+
return {
|
|
39
|
+
name: 'logging',
|
|
40
|
+
fn: async (ctx, next) => {
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
logger.info('Request started', {
|
|
43
|
+
method: ctx.method,
|
|
44
|
+
path: ctx.path,
|
|
45
|
+
sourceIp: ctx.sourceIp,
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
const response = await next();
|
|
49
|
+
const duration = Date.now() - start;
|
|
50
|
+
logger.info('Request completed', {
|
|
51
|
+
method: ctx.method,
|
|
52
|
+
path: ctx.path,
|
|
53
|
+
statusCode: typeof response === 'object' ? response.statusCode : undefined,
|
|
54
|
+
duration,
|
|
55
|
+
});
|
|
56
|
+
return response;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const duration = Date.now() - start;
|
|
60
|
+
logger.error('Request failed', {
|
|
61
|
+
method: ctx.method,
|
|
62
|
+
path: ctx.path,
|
|
63
|
+
duration,
|
|
64
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
65
|
+
});
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* CORS middleware - adds CORS headers
|
|
73
|
+
*/
|
|
74
|
+
function corsMiddleware(options) {
|
|
75
|
+
return {
|
|
76
|
+
name: 'cors',
|
|
77
|
+
fn: async (ctx, next) => {
|
|
78
|
+
const origin = ctx.rawEvent.headers?.origin || '';
|
|
79
|
+
const allowedOrigin = options.allowOrigins.includes('*')
|
|
80
|
+
? '*'
|
|
81
|
+
: options.allowOrigins.includes(origin)
|
|
82
|
+
? origin
|
|
83
|
+
: '';
|
|
84
|
+
// Handle preflight
|
|
85
|
+
if (ctx.method === 'OPTIONS') {
|
|
86
|
+
return {
|
|
87
|
+
statusCode: 204,
|
|
88
|
+
headers: {
|
|
89
|
+
'Access-Control-Allow-Origin': allowedOrigin,
|
|
90
|
+
'Access-Control-Allow-Methods': options.allowMethods.join(', '),
|
|
91
|
+
'Access-Control-Allow-Headers': options.allowHeaders.join(', '),
|
|
92
|
+
'Access-Control-Allow-Credentials': String(options.allowCredentials),
|
|
93
|
+
'Access-Control-Max-Age': String(options.maxAge),
|
|
94
|
+
},
|
|
95
|
+
body: '',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const response = await next();
|
|
99
|
+
// Add CORS headers to response
|
|
100
|
+
if (typeof response !== 'object' || response === null) {
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
const resp = response;
|
|
104
|
+
const corsHeaders = {
|
|
105
|
+
'Access-Control-Allow-Origin': allowedOrigin,
|
|
106
|
+
};
|
|
107
|
+
if (options.allowCredentials) {
|
|
108
|
+
corsHeaders['Access-Control-Allow-Credentials'] = 'true';
|
|
109
|
+
}
|
|
110
|
+
resp.headers = { ...resp.headers, ...corsHeaders };
|
|
111
|
+
return resp;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Timeout middleware - enforces request timeout
|
|
117
|
+
*/
|
|
118
|
+
function timeoutMiddleware(timeoutMs) {
|
|
119
|
+
return {
|
|
120
|
+
name: 'timeout',
|
|
121
|
+
fn: async (ctx, next) => {
|
|
122
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
125
|
+
}, timeoutMs);
|
|
126
|
+
});
|
|
127
|
+
return Promise.race([next(), timeoutPromise]);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Error boundary middleware - catches and formats errors
|
|
133
|
+
*/
|
|
134
|
+
function errorBoundaryMiddleware(errorHandler) {
|
|
135
|
+
return {
|
|
136
|
+
name: 'errorBoundary',
|
|
137
|
+
fn: async (ctx, next) => {
|
|
138
|
+
try {
|
|
139
|
+
return await next();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return errorHandler(error, ctx);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9taWRkbGV3YXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7R0FLRzs7QUF5QkgsMEJBb0JDO0FBS0QsOENBc0NDO0FBS0Qsd0NBaURDO0FBS0QsOENBYUM7QUFLRCwwREFhQztBQTVKRDs7R0FFRztBQUNILFNBQWdCLE9BQU8sQ0FBQyxXQUF5QjtJQUMvQyxPQUFPLEtBQUssRUFBRSxHQUFtQixFQUFFLFlBQW9ELEVBQUUsRUFBRTtRQUN6RixJQUFJLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQztRQUVmLE1BQU0sUUFBUSxHQUFHLEtBQUssRUFBRSxDQUFTLEVBQW9DLEVBQUU7WUFDckUsSUFBSSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUM7Z0JBQ2YsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO1lBQ2xELENBQUM7WUFDRCxLQUFLLEdBQUcsQ0FBQyxDQUFDO1lBRVYsSUFBSSxDQUFDLElBQUksV0FBVyxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUM1QixPQUFPLFlBQVksRUFBRSxDQUFDO1lBQ3hCLENBQUM7WUFFRCxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEMsT0FBTyxVQUFVLENBQUMsRUFBRSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbkQsQ0FBQyxDQUFDO1FBRUYsT0FBTyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDckIsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IsaUJBQWlCLENBQUMsTUFBYztJQUM5QyxPQUFPO1FBQ0wsSUFBSSxFQUFFLFNBQVM7UUFDZixFQUFFLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsRUFBRTtZQUN0QixNQUFN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QUNILE9BQU8sTUFBTSxJQUFJLEVBQUUsQ0FBQztZQUN0QixDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixPQUFPLFlBQVksQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFDbEMsQ0FBQztRQUNILENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVmVudHVyZUtpdCBNaWRkbGV3YXJlXG4gKiBcbiAqIENvbXBvc2FibGUgbWlkZGxld2FyZSBmb3IgcmVxdWVzdCBwcm9jZXNzaW5nLlxuICogRXh0ZW5zaWJsZSBmb3IgYXV0aCwgdGVuYW5jeSwgcmF0ZSBsaW1pdGluZywgZXRjLlxuICovXG5cbmltcG9ydCB0eXBlIHsgQVBJR2F0ZXdheVByb3h5RXZlbnRWMiwgQVBJR2F0ZXdheVByb3h5UmVzdWx0VjIsIEFQSUdhdGV3YXlQcm94eVN0cnVjdHVyZWRSZXN1bHRWMiB9IGZyb20gJ2F3cy1sYW1iZGEnO1xuaW1wb3J0IHsgUmVxdWVzdENvbnRleHQgfSBmcm9tICcuL2NvbnRleHQnO1xuaW1wb3J0IHsgTG9nZ2VyIH0gZnJvbSAnLi9sb2dnZXInO1xuXG4vKipcbiAqIE1pZGRsZXdhcmUgZnVuY3Rpb24gc2lnbmF0dXJlXG4gKi9cbmV4cG9ydCB0eXBlIE1pZGRsZXdhcmVGbiA9IChcbiAgY3R4OiBSZXF1ZXN0Q29udGV4dCxcbiAgbmV4dDogKCkgPT4gUHJvbWlzZTxBUElHYXRld2F5UHJveHlSZXN1bHRWMj5cbikgPT4gUHJvbWlzZTxBUElHYXRld2F5UHJveHlSZXN1bHRWMj47XG5cbi8qKlxuICogTWlkZGxld2FyZSB3aXRoIGNvbmZpZ3VyYXRpb25cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBNaWRkbGV3YXJlIHtcbiAgbmFtZTogc3RyaW5nO1xuICBmbjogTWlkZGxld2FyZUZuO1xufVxuXG4vKipcbiAqIENvbXBvc2UgbXVsdGlwbGUgbWlkZGxld2FyZSBpbnRvIGEgc2luZ2xlIGZ1bmN0aW9uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb21wb3NlKG1pZGRsZXdhcmVzOiBNaWRkbGV3YXJlW10pOiBNaWRkbGV3YXJlRm4ge1xuICByZXR1cm4gYXN5bmMgKGN0eDogUmVxdWVzdENvbnRleHQsIGZpbmFsSGFuZGxlcjogKCkgPT4gUHJvbWlzZTxBUElHYXRld2F5UHJveHlSZXN1bHRWMj4pID0+IHtcbiAgICBsZXQgaW5kZXggPSAtMTtcblxuICAgIGNvbnN0IGRpc3BhdGNoID0gYXN5bmMgKGk6IG51bWJlcik6IFByb21pc2U8QVBJR2F0ZXdheVByb3h5UmVzdWx0VjI+ID0+IHtcbiAgICAgIGlmIChpIDw9IGluZGV4KSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignbmV4dCgpIGNhbGxlZCBtdWx0aXBsZSB0aW1lcycpO1xuICAgICAgfVxuICAgICAgaW5kZXggPSBpO1xuXG4gICAgICBpZiAoaSA+PSBtaWRkbGV3YXJlcy5sZW5ndGgpIHtcbiAgICAgICAgcmV0dXJuIGZpbmFsSGFuZGxlcigpO1xuICAgICAgfVxuXG4gICAgICBjb25zdCBtaWRkbGV3YXJlID0gbWlkZGxld2FyZXNbaV07XG4gICAgICByZXR1cm4gbWlkZGxld2FyZS5mbihjdHgsICgpID0+IGRpc3BhdGNoKGkgKyAxKSk7XG4gICAgfTtcblxuICAgIHJldHVybiBkaXNwYXRjaCgwKTtcbiAgfTtcbn1cblxuLyoqXG4gKiBMb2dnaW5nIG1pZGRsZXdhcmUgLSBsb2dzIHJlcXVlc3QvcmVzcG9uc2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxvZ2dpbmdNaWRkbGV3YXJlKGxvZ2dlcjogTG9nZ2VyKTogTWlkZGxld2FyZSB7XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ2xvZ2dpbmcnLFxuICAgIGZuOiBhc3luYyAoY3R4LCBuZXh0KSA9PiB7XG4gICAgICBjb25zdCBzdGFydCA9IERhdGUubm93KCk7XG4gICAgICBcbiAgICAgIGxvZ2dlci5pbmZvKCdSZXF1ZXN0IHN0YXJ0ZWQnLCB7XG4gICAgICAgIG1ldGhvZDogY3R4Lm1ldGhvZCxcbiAgICAgICAgcGF0aDogY3R4LnBhdGgsXG4gICAgICAgIHNvdXJjZUlwOiBjdHguc291cmNlSXAsXG4gICAgICB9KTtcblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBuZXh0KCk7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uID0gRGF0ZS5ub3coKSAtIHN0YXJ0O1xuXG4gICAgICAgIGxvZ2dlci5pbmZvKCdSZXF1ZXN0IGNvbXBsZXRlZCcsIHtcbiAgICAgICAgICBtZXRob2Q6IGN0eC5tZXRob2QsXG4gICAgICAgICAgcGF0aDogY3R4LnBhdGgsXG4gICAgICAgICAgc3RhdHVzQ29kZTogdHlwZW9mIHJlc3BvbnNlID09PSAnb2JqZWN0JyA/IChyZXNwb25zZSBhcyBhbnkpLnN0YXR1c0NvZGUgOiB1bmRlZmluZWQsXG4gICAgICAgICAgZHVyYXRpb24sXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHJldHVybiByZXNwb25zZTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uID0gRGF0ZS5ub3coKSAtIHN0YXJ0O1xuXG4gICAgICAgIGxvZ2dlci5lcnJvcignUmVxdWVzdCBmYWlsZWQnLCB7XG4gICAgICAgICAgbWV0aG9kOiBjdHgubWV0aG9kLFxuICAgICAgICAgIHBhdGg6IGN0eC5wYXRoLFxuICAgICAgICAgIGR1cmF0aW9uLFxuICAgICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6ICdVbmtub3duIGVycm9yJyxcbiAgICAgICAgfSk7XG5cbiAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICB9XG4gICAgfSxcbiAgfTtcbn1cblxuLyoqXG4gKiBDT1JTIG1pZGRsZXdhcmUgLSBhZGRzIENPUlMgaGVhZGVyc1xuICovXG5leHBvcnQgZnVuY3Rpb24gY29yc01pZGRsZXdhcmUob3B0aW9uczoge1xuICBhbGxvd09yaWdpbnM6IHN0cmluZ1tdO1xuICBhbGxvd01ldGhvZHM6IHN0cmluZ1tdO1xuICBhbGxvd0hlYWRlcnM6IHN0cmluZ1tdO1xuICBhbGxvd0NyZWRlbnRpYWxzOiBib29sZWFuO1xuICBtYXhBZ2U6IG51bWJlcjtcbn0pOiBNaWRkbGV3YXJlIHtcbiAgcmV0dXJuIHtcbiAgICBuYW1lOiAnY29ycycsXG4gICAgZm46IGFzeW5jIChjdHgsIG5leHQpID0+IHtcbiAgICAgIGNvbnN0IG9yaWdpbiA9IGN0eC5yYXdFdmVudC5oZWFkZXJzPy5vcmlnaW4gfHwgJyc7XG4gICAgICBjb25zdCBhbGxvd2VkT3JpZ2luID0gb3B0aW9ucy5hbGxvd09yaWdpbnMuaW5jbHVkZXMoJyonKVxuICAgICAgICA/ICcqJ1xuICAgICAgICA6IG9wdGlvbnMuYWxsb3dPcmlnaW5zLmluY2x1ZGVzKG9yaWdpbilcbiAgICAgICAgICA/IG9yaWdpblxuICAgICAgICAgIDogJyc7XG5cbiAgICAgIC8vIEhhbmRsZSBwcmVmbGlnaHRcbiAgICAgIGlmIChjdHgubWV0aG9kID09PSAnT1BUSU9OUycpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBzdGF0dXNDb2RlOiAyMDQsXG4gICAgICAgICAgaGVhZGVyczoge1xuICAgICAgICAgICAgJ0FjY2Vzcy1Db250cm9sLUFsbG93LU9yaWdpbic6IGFsbG93ZWRPcmlnaW4sXG4gICAgICAgICAgICAnQWNjZXNzLUNvbnRyb2wtQWxsb3ctTWV0aG9kcyc6IG9wdGlvbnMuYWxsb3dNZXRob2RzLmpvaW4oJywgJyksXG4gICAgICAgICAgICAnQWNjZXNzLUNvbnRyb2wtQWxsb3ctSGVhZGVycyc6IG9wdGlvbnMuYWxsb3dIZWFkZXJzLmpvaW4oJywgJyksXG4gICAgICAgICAgICAnQWNjZXNzLUNvbnRyb2wtQWxsb3ctQ3JlZGVudGlhbHMnOiBTdHJpbmcob3B0aW9ucy5hbGxvd0NyZWRlbnRpYWxzKSxcbiAgICAgICAgICAgICdBY2Nlc3MtQ29udHJvbC1NYXgtQWdlJzogU3RyaW5nKG9wdGlvbnMubWF4QWdlKSxcbiAgICAgICAgICB9LFxuICAgICAgICAgIGJvZHk6ICcnLFxuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IG5leHQoKTtcblxuICAgICAgLy8gQWRkIENPUlMgaGVhZGVycyB0byByZXNwb25zZVxuICAgICAgaWYgKHR5cGVvZiByZXNwb25zZSAhPT0gJ29iamVjdCcgfHwgcmVzcG9uc2UgPT09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlO1xuICAgICAgfVxuICAgICAgY29uc3QgcmVzcCA9IHJlc3BvbnNlIGFzIEFQSUdhdGV3YXlQcm94eVN0cnVjdHVyZWRSZXN1bHRWMjtcbiAgICAgIGNvbnN0IGNvcnNIZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgICAgICAnQWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2luJzogYWxsb3dlZE9yaWdpbixcbiAgICAgIH07XG4gICAgICBpZiAob3B0aW9ucy5hbGxvd0NyZWRlbnRpYWxzKSB7XG4gICAgICAgIGNvcnNIZWFkZXJzWydBY2Nlc3MtQ29udHJvbC1BbGxvdy1DcmVkZW50aWFscyddID0gJ3RydWUnO1xuICAgICAgfVxuICAgICAgcmVzcC5oZWFkZXJzID0geyAuLi5yZXNwLmhlYWRlcnMsIC4uLmNvcnNIZWFkZXJzIH07XG4gICAgICByZXR1cm4gcmVzcDtcbiAgICB9LFxuICB9O1xufVxuXG4vKipcbiAqIFRpbWVvdXQgbWlkZGxld2FyZSAtIGVuZm9yY2VzIHJlcXVlc3QgdGltZW91dFxuICovXG5leHBvcnQgZnVuY3Rpb24gdGltZW91dE1pZGRsZXdhcmUodGltZW91dE1zOiBudW1iZXIpOiBNaWRkbGV3YXJlIHtcbiAgcmV0dXJuIHtcbiAgICBuYW1lOiAndGltZW91dCcsXG4gICAgZm46IGFzeW5jIChjdHgsIG5leHQpID0+IHtcbiAgICAgIGNvbnN0IHRpbWVvdXRQcm9taXNlID0gbmV3IFByb21pc2U8bmV2ZXI+KChfLCByZWplY3QpID0+IHtcbiAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgUmVxdWVzdCB0aW1lb3V0IGFmdGVyICR7dGltZW91dE1zfW1zYCkpO1xuICAgICAgICB9LCB0aW1lb3V0TXMpO1xuICAgICAgfSk7XG5cbiAgICAgIHJldHVybiBQcm9taXNlLnJhY2UoW25leHQoKSwgdGltZW91dFByb21pc2VdKTtcbiAgICB9LFxuICB9O1xufVxuXG4vKipcbiAqIEVycm9yIGJvdW5kYXJ5IG1pZGRsZXdhcmUgLSBjYXRjaGVzIGFuZCBmb3JtYXRzIGVycm9yc1xuICovXG5leHBvcnQgZnVuY3Rpb24gZXJyb3JCb3VuZGFyeU1pZGRsZXdhcmUoXG4gIGVycm9ySGFuZGxlcjogKGVycm9yOiB1bmtub3duLCBjdHg6IFJlcXVlc3RDb250ZXh0KSA9PiBBUElHYXRld2F5UHJveHlSZXN1bHRWMlxuKTogTWlkZGxld2FyZSB7XG4gIHJldHVybiB7XG4gICAgbmFtZTogJ2Vycm9yQm91bmRhcnknLFxuICAgIGZuOiBhc3luYyAoY3R4LCBuZXh0KSA9PiB7XG4gICAgICB0cnkge1xuICAgICAgICByZXR1cm4gYXdhaXQgbmV4dCgpO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgcmV0dXJuIGVycm9ySGFuZGxlcihlcnJvciwgY3R4KTtcbiAgICAgIH1cbiAgICB9LFxuICB9O1xufVxuIl19
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VentureKit Response Utilities
|
|
3
|
+
*
|
|
4
|
+
* Standardized response formatting for API Gateway.
|
|
5
|
+
*/
|
|
6
|
+
import type { APIGatewayProxyResultV2 } from 'aws-lambda';
|
|
7
|
+
import { VentureError } from './errors';
|
|
8
|
+
/**
|
|
9
|
+
* Standard success response
|
|
10
|
+
*/
|
|
11
|
+
export interface SuccessResponse<T = unknown> {
|
|
12
|
+
data: T;
|
|
13
|
+
meta?: {
|
|
14
|
+
requestId?: string;
|
|
15
|
+
timestamp?: string;
|
|
16
|
+
pagination?: {
|
|
17
|
+
page: number;
|
|
18
|
+
pageSize: number;
|
|
19
|
+
total: number;
|
|
20
|
+
totalPages: number;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Standard error response
|
|
26
|
+
*/
|
|
27
|
+
export interface ErrorResponse {
|
|
28
|
+
error: {
|
|
29
|
+
code: string;
|
|
30
|
+
message: string;
|
|
31
|
+
details?: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
meta?: {
|
|
34
|
+
requestId?: string;
|
|
35
|
+
timestamp?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a success response
|
|
40
|
+
*/
|
|
41
|
+
export declare function success<T>(data: T, options?: {
|
|
42
|
+
statusCode?: number;
|
|
43
|
+
headers?: Record<string, string>;
|
|
44
|
+
requestId?: string;
|
|
45
|
+
}): APIGatewayProxyResultV2;
|
|
46
|
+
/**
|
|
47
|
+
* Create a created response (201)
|
|
48
|
+
*/
|
|
49
|
+
export declare function created<T>(data: T, options?: {
|
|
50
|
+
headers?: Record<string, string>;
|
|
51
|
+
requestId?: string;
|
|
52
|
+
}): APIGatewayProxyResultV2;
|
|
53
|
+
/**
|
|
54
|
+
* Create a no content response (204)
|
|
55
|
+
*/
|
|
56
|
+
export declare function noContent(options?: {
|
|
57
|
+
requestId?: string;
|
|
58
|
+
}): APIGatewayProxyResultV2;
|
|
59
|
+
/**
|
|
60
|
+
* Create an error response from a VentureError
|
|
61
|
+
*/
|
|
62
|
+
export declare function error(err: VentureError, options?: {
|
|
63
|
+
requestId?: string;
|
|
64
|
+
}): APIGatewayProxyResultV2;
|
|
65
|
+
/**
|
|
66
|
+
* Convert any error to an API Gateway response
|
|
67
|
+
*/
|
|
68
|
+
export declare function errorResponse(err: unknown, options?: {
|
|
69
|
+
requestId?: string;
|
|
70
|
+
logError?: boolean;
|
|
71
|
+
}): APIGatewayProxyResultV2;
|
|
72
|
+
/**
|
|
73
|
+
* Create a redirect response
|
|
74
|
+
*/
|
|
75
|
+
export declare function redirect(location: string, permanent?: boolean): APIGatewayProxyResultV2;
|
package/dist/response.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VentureKit Response Utilities
|
|
4
|
+
*
|
|
5
|
+
* Standardized response formatting for API Gateway.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.success = success;
|
|
9
|
+
exports.created = created;
|
|
10
|
+
exports.noContent = noContent;
|
|
11
|
+
exports.error = error;
|
|
12
|
+
exports.errorResponse = errorResponse;
|
|
13
|
+
exports.redirect = redirect;
|
|
14
|
+
const errors_1 = require("./errors");
|
|
15
|
+
/**
|
|
16
|
+
* Create a success response
|
|
17
|
+
*/
|
|
18
|
+
function success(data, options = {}) {
|
|
19
|
+
const { statusCode = 200, headers = {}, requestId } = options;
|
|
20
|
+
const body = {
|
|
21
|
+
data,
|
|
22
|
+
...(requestId && {
|
|
23
|
+
meta: {
|
|
24
|
+
requestId,
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
statusCode,
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
...headers,
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a created response (201)
|
|
40
|
+
*/
|
|
41
|
+
function created(data, options = {}) {
|
|
42
|
+
return success(data, { ...options, statusCode: 201 });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a no content response (204)
|
|
46
|
+
*/
|
|
47
|
+
function noContent(options = {}) {
|
|
48
|
+
return {
|
|
49
|
+
statusCode: 204,
|
|
50
|
+
body: '',
|
|
51
|
+
headers: options.requestId ? { 'x-request-id': options.requestId } : undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create an error response from a VentureError
|
|
56
|
+
*/
|
|
57
|
+
function error(err, options = {}) {
|
|
58
|
+
const { requestId } = options;
|
|
59
|
+
const body = {
|
|
60
|
+
error: {
|
|
61
|
+
code: err.code,
|
|
62
|
+
message: err.message,
|
|
63
|
+
...(err.details && { details: err.details }),
|
|
64
|
+
},
|
|
65
|
+
...(requestId && {
|
|
66
|
+
meta: {
|
|
67
|
+
requestId,
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
statusCode: err.statusCode,
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Convert any error to an API Gateway response
|
|
82
|
+
*/
|
|
83
|
+
function errorResponse(err, options = {}) {
|
|
84
|
+
const { requestId, logError = true } = options;
|
|
85
|
+
if ((0, errors_1.isVentureError)(err)) {
|
|
86
|
+
return error(err, { requestId });
|
|
87
|
+
}
|
|
88
|
+
// Log unexpected errors
|
|
89
|
+
if (logError) {
|
|
90
|
+
console.error('Unexpected error:', err);
|
|
91
|
+
}
|
|
92
|
+
// Wrap in InternalError for consistent response
|
|
93
|
+
return error(new errors_1.InternalError(), { requestId });
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Create a redirect response
|
|
97
|
+
*/
|
|
98
|
+
function redirect(location, permanent = false) {
|
|
99
|
+
return {
|
|
100
|
+
statusCode: permanent ? 301 : 302,
|
|
101
|
+
headers: {
|
|
102
|
+
Location: location,
|
|
103
|
+
},
|
|
104
|
+
body: '',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzcG9uc2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcmVzcG9uc2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7O0dBSUc7O0FBd0NILDBCQTRCQztBQUtELDBCQUtDO0FBS0QsOEJBTUM7QUFLRCxzQkEyQkM7QUFLRCxzQ0FpQkM7QUFLRCw0QkFXQztBQTVKRCxxQ0FBdUU7QUFrQ3ZFOztHQUVHO0FBQ0gsU0FBZ0IsT0FBTyxDQUNyQixJQUFPLEVBQ1AsVUFJSSxFQUFFO0lBRU4sTUFBTSxFQUFFLFVBQVUsR0FBRyxHQUFHLEVBQUUsT0FBTyxHQUFHLEVBQUUsRUFBRSxTQUFTLEVBQUUsR0FBRyxPQUFPLENBQUM7SUFFOUQsTUFBTSxJQUFJLEdBQXVCO1FBQy9CLElBQUk7UUFDSixHQUFHLENBQUMsU0FBUyxJQUFJO1lBQ2YsSUFBSSxFQUFFO2dCQUNKLFNBQVM7Z0JBQ1QsU0FBUyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFO2FBQ3BDO1NBQ0YsQ0FBQztLQUNILENBQUM7SUFFRixPQUFPO1FBQ0wsVUFBVTtRQUNWLE9BQU8sRUFBRTtZQUNQLGNBQWMsRUFBRSxrQkFBa0I7WUFDbEMsR0FBRyxPQUFPO1NBQ1g7UUFDRCxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUM7S0FDM0IsQ0FBQztBQUNKLENBQUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLE9BQU8sQ0FDckIsSUFBTyxFQUNQLFVBQW9FLEVBQUU7SUFFdEUsT0FBTyxPQUFPLENBQUMsSUFBSSxFQUFFLEVBQUUsR0FBRyxPQUFPLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUM7QUFDeEQsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IsU0FBUyxDQUFDLFVBQWtDLEVBQUU7SUFDNUQsT0FBTztRQUNMLFVBQVUsRUFBRSxHQUFHO1FBQ2YsSUFBSSxFQUFFLEVBQUU7UUFDUixPQUFPLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxjQUFjLEVBQUUsT0FBTyxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTO0tBQy9FLENBQUM7QUFDSixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxTQUFnQixLQUFLLENBQ25CLEdBQWlCLEVBQ2pCLFVBQWtDLEVBQUU7SUFFcEMsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLE9BQU8sQ0FBQztJQUU5QixNQUFNLElBQUksR0FBa0I7UUFDMUIsS0FBSyxFQUFFO1lBQ0wsSUFBSSxFQUFFLEdBQUcsQ0FBQyxJQUFJO1lBQ2QsT0FBTyxFQUFFLEdBQUcsQ0FBQyxPQUFPO1lBQ3BCLEdBQUcsQ0FBQyxHQUFHLENBQUMsT0FBTyxJQUFJLEVBQUUsT0FBTyxFQUFFLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQztTQUM3QztRQUNELEdBQUcsQ0FBQyxTQUFTLElBQUk7WUFDZixJQUFJLEVBQUU7Z0JBQ0osU0FBUztnQkFDVCxTQUFTLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUU7YUFDcEM7U0FDRixDQUFDO0tBQ0gsQ0FBQztJQUVGLE9BQU87UUFDTCxVQUFVLEVBQUUsR0FBRyxDQUFDLFVBQVU7UUFDMUIsT0FBTyxFQUFFO1lBQ1AsY0FBYyxFQUFFLGtCQUFrQjtTQUNuQztRQUNELElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQztLQUMzQixDQUFDO0FBQ0osQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IsYUFBYSxDQUMzQixHQUFZLEVBQ1osVUFBc0QsRUFBRTtJQUV4RCxNQUFNLEVBQUUsU0FBUyxFQUFFLFFBQVEsR0FBRyxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUM7SUFFL0MsSUFBSSxJQUFBLHVCQUFjLEVBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN4QixPQUFPLEtBQUssQ0FBQyxHQUFHLEVBQUUsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFRCx3QkFBd0I7SUFDeEIsSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUNiLE9BQU8sQ0FBQyxLQUFLLENBQUMsbUJBQW1CLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDMUMsQ0FBQztJQUVELGdEQUFnRDtJQUNoRCxPQUFPLEtBQUssQ0FBQyxJQUFJLHNCQUFhLEVBQUUsRUFBRSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUM7QUFDbkQsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IsUUFBUSxDQUN0QixRQUFnQixFQUNoQixZQUFxQixLQUFLO0lBRTFCLE9BQU87UUFDTCxVQUFVLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUc7UUFDakMsT0FBTyxFQUFFO1lBQ1AsUUFBUSxFQUFFLFFBQVE7U0FDbkI7UUFDRCxJQUFJLEVBQUUsRUFBRTtLQUNULENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBWZW50dXJlS2l0IFJlc3BvbnNlIFV0aWxpdGllc1xuICogXG4gKiBTdGFuZGFyZGl6ZWQgcmVzcG9uc2UgZm9ybWF0dGluZyBmb3IgQVBJIEdhdGV3YXkuXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBBUElHYXRld2F5UHJveHlSZXN1bHRWMiB9IGZyb20gJ2F3cy1sYW1iZGEnO1xuaW1wb3J0IHsgVmVudHVyZUVycm9yLCBpc1ZlbnR1cmVFcnJvciwgSW50ZXJuYWxFcnJvciB9IGZyb20gJy4vZXJyb3JzJztcblxuLyoqXG4gKiBTdGFuZGFyZCBzdWNjZXNzIHJlc3BvbnNlXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU3VjY2Vzc1Jlc3BvbnNlPFQgPSB1bmtub3duPiB7XG4gIGRhdGE6IFQ7XG4gIG1ldGE/OiB7XG4gICAgcmVxdWVzdElkPzogc3RyaW5nO1xuICAgIHRpbWVzdGFtcD86IHN0cmluZztcbiAgICBwYWdpbmF0aW9uPzoge1xuICAgICAgcGFnZTogbnVtYmVyO1xuICAgICAgcGFnZVNpemU6IG51bWJlcjtcbiAgICAgIHRvdGFsOiBudW1iZXI7XG4gICAgICB0b3RhbFBhZ2VzOiBudW1iZXI7XG4gICAgfTtcbiAgfTtcbn1cblxuLyoqXG4gKiBTdGFuZGFyZCBlcnJvciByZXNwb25zZVxuICovXG5leHBvcnQgaW50ZXJmYWNlIEVycm9yUmVzcG9uc2Uge1xuICBlcnJvcjoge1xuICAgIGNvZGU6IHN0cmluZztcbiAgICBtZXNzYWdlOiBzdHJpbmc7XG4gICAgZGV0YWlscz86IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICB9O1xuICBtZXRhPzoge1xuICAgIHJlcXVlc3RJZD86IHN0cmluZztcbiAgICB0aW1lc3RhbXA/OiBzdHJpbmc7XG4gIH07XG59XG5cbi8qKlxuICogQ3JlYXRlIGEgc3VjY2VzcyByZXNwb25zZVxuICovXG5leHBvcnQgZnVuY3Rpb24gc3VjY2VzczxUPihcbiAgZGF0YTogVCxcbiAgb3B0aW9uczoge1xuICAgIHN0YXR1c0NvZGU/OiBudW1iZXI7XG4gICAgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG4gICAgcmVxdWVzdElkPzogc3RyaW5nO1xuICB9ID0ge31cbik6IEFQSUdhdGV3YXlQcm94eVJlc3VsdFYyIHtcbiAgY29uc3QgeyBzdGF0dXNDb2RlID0gMjAwLCBoZWFkZXJzID0ge30sIHJlcXVlc3RJZCB9ID0gb3B0aW9ucztcbiAgXG4gIGNvbnN0IGJvZHk6IFN1Y2Nlc3NSZXNwb25zZTxUPiA9IHtcbiAgICBkYXRhLFxuICAgIC4uLihyZXF1ZXN0SWQgJiYge1xuICAgICAgbWV0YToge1xuICAgICAgICByZXF1ZXN0SWQsXG4gICAgICAgIHRpbWVzdGFtcDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgfSxcbiAgICB9KSxcbiAgfTtcbiAgXG4gIHJldHVybiB7XG4gICAgc3RhdHVzQ29kZSxcbiAgICBoZWFkZXJzOiB7XG4gICAgICAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nLFxuICAgICAgLi4uaGVhZGVycyxcbiAgICB9LFxuICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KGJvZHkpLFxuICB9O1xufVxuXG4vKipcbiAqIENyZWF0ZSBhIGNyZWF0ZWQgcmVzcG9uc2UgKDIwMSlcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZWQ8VD4oXG4gIGRhdGE6IFQsXG4gIG9wdGlvbnM6IHsgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz47IHJlcXVlc3RJZD86IHN0cmluZyB9ID0ge31cbik6IEFQSUdhdGV3YXlQcm94eVJlc3VsdFYyIHtcbiAgcmV0dXJuIHN1Y2Nlc3MoZGF0YSwgeyAuLi5vcHRpb25zLCBzdGF0dXNDb2RlOiAyMDEgfSk7XG59XG5cbi8qKlxuICogQ3JlYXRlIGEgbm8gY29udGVudCByZXNwb25zZSAoMjA0KVxuICovXG5leHBvcnQgZnVuY3Rpb24gbm9Db250ZW50KG9wdGlvbnM6IHsgcmVxdWVzdElkPzogc3RyaW5nIH0gPSB7fSk6IEFQSUdhdGV3YXlQcm94eVJlc3VsdFYyIHtcbiAgcmV0dXJuIHtcbiAgICBzdGF0dXNDb2RlOiAyMDQsXG4gICAgYm9keTogJycsXG4gICAgaGVhZGVyczogb3B0aW9ucy5yZXF1ZXN0SWQgPyB7ICd4LXJlcXVlc3QtaWQnOiBvcHRpb25zLnJlcXVlc3RJZCB9IDogdW5kZWZpbmVkLFxuICB9O1xufVxuXG4vKipcbiAqIENyZWF0ZSBhbiBlcnJvciByZXNwb25zZSBmcm9tIGEgVmVudHVyZUVycm9yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBlcnJvcihcbiAgZXJyOiBWZW50dXJlRXJyb3IsXG4gIG9wdGlvbnM6IHsgcmVxdWVzdElkPzogc3RyaW5nIH0gPSB7fVxuKTogQVBJR2F0ZXdheVByb3h5UmVzdWx0VjIge1xuICBjb25zdCB7IHJlcXVlc3RJZCB9ID0gb3B0aW9ucztcbiAgXG4gIGNvbnN0IGJvZHk6IEVycm9yUmVzcG9uc2UgPSB7XG4gICAgZXJyb3I6IHtcbiAgICAgIGNvZGU6IGVyci5jb2RlLFxuICAgICAgbWVzc2FnZTogZXJyLm1lc3NhZ2UsXG4gICAgICAuLi4oZXJyLmRldGFpbHMgJiYgeyBkZXRhaWxzOiBlcnIuZGV0YWlscyB9KSxcbiAgICB9LFxuICAgIC4uLihyZXF1ZXN0SWQgJiYge1xuICAgICAgbWV0YToge1xuICAgICAgICByZXF1ZXN0SWQsXG4gICAgICAgIHRpbWVzdGFtcDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgfSxcbiAgICB9KSxcbiAgfTtcbiAgXG4gIHJldHVybiB7XG4gICAgc3RhdHVzQ29kZTogZXJyLnN0YXR1c0NvZGUsXG4gICAgaGVhZGVyczoge1xuICAgICAgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyxcbiAgICB9LFxuICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KGJvZHkpLFxuICB9O1xufVxuXG4vKipcbiAqIENvbnZlcnQgYW55IGVycm9yIHRvIGFuIEFQSSBHYXRld2F5IHJlc3BvbnNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBlcnJvclJlc3BvbnNlKFxuICBlcnI6IHVua25vd24sXG4gIG9wdGlvbnM6IHsgcmVxdWVzdElkPzogc3RyaW5nOyBsb2dFcnJvcj86IGJvb2xlYW4gfSA9IHt9XG4pOiBBUElHYXRld2F5UHJveHlSZXN1bHRWMiB7XG4gIGNvbnN0IHsgcmVxdWVzdElkLCBsb2dFcnJvciA9IHRydWUgfSA9IG9wdGlvbnM7XG4gIFxuICBpZiAoaXNWZW50dXJlRXJyb3IoZXJyKSkge1xuICAgIHJldHVybiBlcnJvcihlcnIsIHsgcmVxdWVzdElkIH0pO1xuICB9XG4gIFxuICAvLyBMb2cgdW5leHBlY3RlZCBlcnJvcnNcbiAgaWYgKGxvZ0Vycm9yKSB7XG4gICAgY29uc29sZS5lcnJvcignVW5leHBlY3RlZCBlcnJvcjonLCBlcnIpO1xuICB9XG4gIFxuICAvLyBXcmFwIGluIEludGVybmFsRXJyb3IgZm9yIGNvbnNpc3RlbnQgcmVzcG9uc2VcbiAgcmV0dXJuIGVycm9yKG5ldyBJbnRlcm5hbEVycm9yKCksIHsgcmVxdWVzdElkIH0pO1xufVxuXG4vKipcbiAqIENyZWF0ZSBhIHJlZGlyZWN0IHJlc3BvbnNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWRpcmVjdChcbiAgbG9jYXRpb246IHN0cmluZyxcbiAgcGVybWFuZW50OiBib29sZWFuID0gZmFsc2Vcbik6IEFQSUdhdGV3YXlQcm94eVJlc3VsdFYyIHtcbiAgcmV0dXJuIHtcbiAgICBzdGF0dXNDb2RlOiBwZXJtYW5lbnQgPyAzMDEgOiAzMDIsXG4gICAgaGVhZGVyczoge1xuICAgICAgTG9jYXRpb246IGxvY2F0aW9uLFxuICAgIH0sXG4gICAgYm9keTogJycsXG4gIH07XG59XG4iXX0=
|
package/dist/ws.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Connection Store
|
|
3
|
+
*
|
|
4
|
+
* DynamoDB-backed connection management for API Gateway WebSocket APIs.
|
|
5
|
+
*
|
|
6
|
+
* Authentication:
|
|
7
|
+
* Two-phase auth — the client connects without credentials, then sends
|
|
8
|
+
* an { action: "auth", token: "<jwt>" } message over the encrypted channel.
|
|
9
|
+
* The connection is stored as unauthenticated on $connect and upgraded
|
|
10
|
+
* to authenticated via connectionStore.authenticate().
|
|
11
|
+
* This avoids leaking JWTs in query strings, server logs, and access logs.
|
|
12
|
+
*
|
|
13
|
+
* Multi-tenancy:
|
|
14
|
+
* If enabled, each connection stores a tenantId alongside userId.
|
|
15
|
+
* Use sendToTenant() to broadcast within a tenant boundary.
|
|
16
|
+
*
|
|
17
|
+
* API:
|
|
18
|
+
* connectionStore.save(connectionId) — $connect (unauthenticated)
|
|
19
|
+
* connectionStore.authenticate(connectionId, metadata) — after JWT verification
|
|
20
|
+
* connectionStore.remove(connectionId) — $disconnect
|
|
21
|
+
* connectionStore.get(connectionId) — single connection
|
|
22
|
+
* connectionStore.getAll() — all connections
|
|
23
|
+
* connectionStore.getByUser(userId) — by user (GSI)
|
|
24
|
+
* connectionStore.getByTenant(tenantId) — by tenant (GSI)
|
|
25
|
+
* connectionStore.postToConnection(domain, stage, id, data) — send to one
|
|
26
|
+
* connectionStore.sendToUser(domain, stage, userId, data) — send to all user sessions
|
|
27
|
+
* connectionStore.sendToTenant(domain, stage, tenantId, data) — broadcast within tenant
|
|
28
|
+
* connectionStore.broadcast(domain, stage, data) — broadcast to all
|
|
29
|
+
*
|
|
30
|
+
* Environment variables:
|
|
31
|
+
* CONNECTIONS_TABLE — DynamoDB table name (required)
|
|
32
|
+
*
|
|
33
|
+
* DynamoDB table schema:
|
|
34
|
+
* Partition key: connectionId (String)
|
|
35
|
+
* TTL attribute: ttl
|
|
36
|
+
* GSI: userId-index (partition key: userId)
|
|
37
|
+
* GSI: tenantId-index (partition key: tenantId) — only if multi-tenancy enabled
|
|
38
|
+
*/
|
|
39
|
+
export interface ConnectionRecord {
|
|
40
|
+
connectionId: string;
|
|
41
|
+
authenticated: boolean;
|
|
42
|
+
userId?: string;
|
|
43
|
+
tenantId?: string;
|
|
44
|
+
email?: string;
|
|
45
|
+
connectedAt: number;
|
|
46
|
+
authenticatedAt?: number;
|
|
47
|
+
ttl: number;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
export interface ConnectionMetadata {
|
|
51
|
+
userId: string;
|
|
52
|
+
tenantId?: string;
|
|
53
|
+
email?: string;
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* WebSocket connection store backed by DynamoDB.
|
|
58
|
+
*
|
|
59
|
+
* Two-phase authentication flow:
|
|
60
|
+
*
|
|
61
|
+
* 1. $connect handler:
|
|
62
|
+
* ```typescript
|
|
63
|
+
* await connectionStore.save(connectionId);
|
|
64
|
+
* // Connection is unauthenticated, TTL = 30s
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* 2. Client sends auth message over the encrypted WebSocket:
|
|
68
|
+
* ```json
|
|
69
|
+
* { "action": "auth", "token": "<jwt>" }
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* 3. $default handler verifies JWT, then:
|
|
73
|
+
* ```typescript
|
|
74
|
+
* await connectionStore.authenticate(connectionId, { userId, tenantId, email });
|
|
75
|
+
* // Connection is now authenticated, TTL extended to 2h
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare const connectionStore: {
|
|
79
|
+
/**
|
|
80
|
+
* Save a new connection on $connect.
|
|
81
|
+
* The connection is unauthenticated with a short TTL.
|
|
82
|
+
* The client must send an auth message to upgrade.
|
|
83
|
+
*/
|
|
84
|
+
save(connectionId: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Authenticate a connection after JWT verification.
|
|
87
|
+
* Updates the record with user metadata and extends the TTL.
|
|
88
|
+
*
|
|
89
|
+
* @param connectionId — the connection to authenticate
|
|
90
|
+
* @param metadata — must include userId, optionally tenantId, email, etc.
|
|
91
|
+
*/
|
|
92
|
+
authenticate(connectionId: string, metadata: ConnectionMetadata): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Remove a connection on $disconnect.
|
|
95
|
+
*/
|
|
96
|
+
remove(connectionId: string): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Get a single connection record.
|
|
99
|
+
*/
|
|
100
|
+
get(connectionId: string): Promise<ConnectionRecord | null>;
|
|
101
|
+
/**
|
|
102
|
+
* Get all authenticated connections.
|
|
103
|
+
* For large-scale apps, prefer getByUser() or getByTenant().
|
|
104
|
+
*/
|
|
105
|
+
getAll(): Promise<ConnectionRecord[]>;
|
|
106
|
+
/**
|
|
107
|
+
* Get all connections for a specific user.
|
|
108
|
+
* A user can have multiple active connections (e.g. multiple tabs/devices).
|
|
109
|
+
* Requires GSI: userId-index (partition key: userId).
|
|
110
|
+
*/
|
|
111
|
+
getByUser(userId: string): Promise<ConnectionRecord[]>;
|
|
112
|
+
/**
|
|
113
|
+
* Get all connections for a specific tenant.
|
|
114
|
+
* Requires GSI: tenantId-index (partition key: tenantId).
|
|
115
|
+
*/
|
|
116
|
+
getByTenant(tenantId: string): Promise<ConnectionRecord[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Send data to a specific connection via API Gateway Management API.
|
|
119
|
+
* Automatically cleans up stale connections (GoneException).
|
|
120
|
+
* Returns true if sent, false if the connection was stale and removed.
|
|
121
|
+
*/
|
|
122
|
+
postToConnection(domainName: string, stage: string, connectionId: string, data: unknown): Promise<boolean>;
|
|
123
|
+
/**
|
|
124
|
+
* Send data to all connections belonging to a specific user.
|
|
125
|
+
* Returns the number of connections that received the message.
|
|
126
|
+
*/
|
|
127
|
+
sendToUser(domainName: string, stage: string, userId: string, data: unknown): Promise<number>;
|
|
128
|
+
/**
|
|
129
|
+
* Send data to all connections within a specific tenant.
|
|
130
|
+
* Returns the number of connections that received the message.
|
|
131
|
+
*/
|
|
132
|
+
sendToTenant(domainName: string, stage: string, tenantId: string, data: unknown): Promise<number>;
|
|
133
|
+
/**
|
|
134
|
+
* Broadcast data to all authenticated connections.
|
|
135
|
+
* Returns the number of clients that received the message.
|
|
136
|
+
*/
|
|
137
|
+
broadcast(domainName: string, stage: string, data: unknown): Promise<number>;
|
|
138
|
+
};
|