@koralabs/kora-labs-common 6.6.3 → 6.7.0
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/fn/index.d.ts +45 -0
- package/fn/index.js +77 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/logger/index.d.ts +1 -0
- package/logger/index.js +50 -2
- package/package.json +1 -1
package/fn/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
interface AlbEvent {
|
|
4
|
+
requestContext: {
|
|
5
|
+
elb: {
|
|
6
|
+
targetGroupArn: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
httpMethod: string;
|
|
10
|
+
path: string;
|
|
11
|
+
queryStringParameters: Record<string, string> | null;
|
|
12
|
+
multiValueQueryStringParameters?: Record<string, string[]> | null;
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
body: string | null;
|
|
15
|
+
isBase64Encoded: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface AlbResult {
|
|
18
|
+
statusCode?: number;
|
|
19
|
+
statusDescription?: string;
|
|
20
|
+
headers?: Record<string, string | number | boolean>;
|
|
21
|
+
multiValueHeaders?: Record<string, Array<string | number | boolean>>;
|
|
22
|
+
body?: string;
|
|
23
|
+
isBase64Encoded?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export type AlbHandler = (event: AlbEvent, context: Record<string, unknown>) => Promise<AlbResult> | AlbResult;
|
|
26
|
+
export interface FnHttpGateway {
|
|
27
|
+
requestURL: string;
|
|
28
|
+
method: string;
|
|
29
|
+
headers: Record<string, string | string[]>;
|
|
30
|
+
statusCode: number;
|
|
31
|
+
setResponseHeader(key: string, ...values: string[]): void;
|
|
32
|
+
addResponseHeader(key: string, ...values: string[]): void;
|
|
33
|
+
}
|
|
34
|
+
export interface FnContext {
|
|
35
|
+
httpGateway?: FnHttpGateway;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Drive an app's ALB (serverless-express) handler from an Fn HTTP invocation.
|
|
39
|
+
* @param albHandler the app's existing @vendia/serverless-express handler
|
|
40
|
+
* @param ctx the Fn FDK context (must be HTTP-triggered → ctx.httpGateway present)
|
|
41
|
+
* @param input the request body (Buffer, with fdk inputMode 'buffer')
|
|
42
|
+
* @returns the response body (Buffer or string); status + headers are set on ctx.httpGateway
|
|
43
|
+
*/
|
|
44
|
+
export declare const invokeExpressViaAlb: (albHandler: AlbHandler, ctx: FnContext, input?: Buffer | Uint8Array | string) => Promise<Buffer | string>;
|
|
45
|
+
export {};
|
package/fn/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Fn <-> Express bridge for the self-hosted runtime (MIGRATION-PLAN §4).
|
|
3
|
+
//
|
|
4
|
+
// Each app already exposes an @vendia/serverless-express ALB handler (e.g.
|
|
5
|
+
// lambdas/api.app.ts `handler(event, ctx)`), which is how it ran on Lambda/ALB. Rather
|
|
6
|
+
// than invent a new req/res bridge, `invokeExpressViaAlb` adapts an Fn HTTP-trigger
|
|
7
|
+
// invocation INTO an ALB event, calls that existing handler, and maps the ALB response
|
|
8
|
+
// back to Fn. So the Fn function reuses the exact Express request handling the app already
|
|
9
|
+
// has, and apps need only a tiny func.js entrypoint:
|
|
10
|
+
//
|
|
11
|
+
// const fdk = require('@fnproject/fdk');
|
|
12
|
+
// const { handler } = require('./dist/lambdas/api.app'); // the app's ALB handler
|
|
13
|
+
// const { invokeExpressViaAlb } = require('@koralabs/kora-labs-common');
|
|
14
|
+
// fdk.handle((input, ctx) => invokeExpressViaAlb(handler, ctx, input), { inputMode: 'buffer' });
|
|
15
|
+
//
|
|
16
|
+
// Typed loosely (no @types/aws-lambda / @fnproject/fdk dependency here) — the shapes below
|
|
17
|
+
// match the ALB event/result and the Fn HTTPGatewayContext.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.invokeExpressViaAlb = void 0;
|
|
20
|
+
const flattenHeaders = (h) => {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [k, v] of Object.entries(h || {})) {
|
|
23
|
+
out[k.toLowerCase()] = Array.isArray(v) ? v.join(', ') : String(v);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Drive an app's ALB (serverless-express) handler from an Fn HTTP invocation.
|
|
29
|
+
* @param albHandler the app's existing @vendia/serverless-express handler
|
|
30
|
+
* @param ctx the Fn FDK context (must be HTTP-triggered → ctx.httpGateway present)
|
|
31
|
+
* @param input the request body (Buffer, with fdk inputMode 'buffer')
|
|
32
|
+
* @returns the response body (Buffer or string); status + headers are set on ctx.httpGateway
|
|
33
|
+
*/
|
|
34
|
+
const invokeExpressViaAlb = async (albHandler, ctx, input) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const h = ctx.httpGateway;
|
|
37
|
+
if (!h)
|
|
38
|
+
throw new Error('invokeExpressViaAlb: ctx.httpGateway missing (the function must be HTTP-triggered)');
|
|
39
|
+
// requestURL may be a bare path (`/handles?x=1`) or absolute; URL() needs a base.
|
|
40
|
+
const url = new URL(h.requestURL, 'http://kora.local');
|
|
41
|
+
const qs = {};
|
|
42
|
+
const mqs = {};
|
|
43
|
+
for (const key of new Set(url.searchParams.keys())) {
|
|
44
|
+
const all = url.searchParams.getAll(key);
|
|
45
|
+
qs[key] = all[all.length - 1];
|
|
46
|
+
mqs[key] = all;
|
|
47
|
+
}
|
|
48
|
+
const bodyBuf = input == null
|
|
49
|
+
? Buffer.alloc(0)
|
|
50
|
+
: Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
51
|
+
const event = {
|
|
52
|
+
requestContext: { elb: { targetGroupArn: 'arn:kora:fn' } },
|
|
53
|
+
httpMethod: h.method,
|
|
54
|
+
path: url.pathname,
|
|
55
|
+
queryStringParameters: Object.keys(qs).length ? qs : null,
|
|
56
|
+
multiValueQueryStringParameters: Object.keys(mqs).length ? mqs : null,
|
|
57
|
+
headers: flattenHeaders(h.headers),
|
|
58
|
+
body: bodyBuf.length ? bodyBuf.toString('base64') : null,
|
|
59
|
+
isBase64Encoded: true
|
|
60
|
+
};
|
|
61
|
+
const res = (await albHandler(event, {})) || {};
|
|
62
|
+
h.statusCode = (_a = res.statusCode) !== null && _a !== void 0 ? _a : 200;
|
|
63
|
+
if (res.headers) {
|
|
64
|
+
for (const [k, v] of Object.entries(res.headers))
|
|
65
|
+
h.setResponseHeader(k, String(v));
|
|
66
|
+
}
|
|
67
|
+
if (res.multiValueHeaders) {
|
|
68
|
+
for (const [k, arr] of Object.entries(res.multiValueHeaders)) {
|
|
69
|
+
for (const v of arr)
|
|
70
|
+
h.addResponseHeader(k, String(v));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (res.body == null)
|
|
74
|
+
return '';
|
|
75
|
+
return res.isBase64Encoded ? Buffer.from(res.body, 'base64') : res.body;
|
|
76
|
+
};
|
|
77
|
+
exports.invokeExpressViaAlb = invokeExpressViaAlb;
|
package/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './constants';
|
|
|
2
2
|
export * from './aws';
|
|
3
3
|
export { ComputeEnvironment, Environment } from './environment';
|
|
4
4
|
export * from './errors';
|
|
5
|
+
export * from './fn';
|
|
5
6
|
export * from './handles';
|
|
6
7
|
export * from './http';
|
|
7
8
|
export { LogCategory, Logger } from './logger';
|
package/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var environment_1 = require("./environment");
|
|
|
21
21
|
Object.defineProperty(exports, "ComputeEnvironment", { enumerable: true, get: function () { return environment_1.ComputeEnvironment; } });
|
|
22
22
|
Object.defineProperty(exports, "Environment", { enumerable: true, get: function () { return environment_1.Environment; } });
|
|
23
23
|
__exportStar(require("./errors"), exports);
|
|
24
|
+
__exportStar(require("./fn"), exports);
|
|
24
25
|
__exportStar(require("./handles"), exports);
|
|
25
26
|
__exportStar(require("./http"), exports);
|
|
26
27
|
var logger_1 = require("./logger");
|
package/logger/index.d.ts
CHANGED
package/logger/index.js
CHANGED
|
@@ -102,11 +102,11 @@ class Logger {
|
|
|
102
102
|
}
|
|
103
103
|
static log_entry(category, message, event, milliseconds, count, dimensions, context) {
|
|
104
104
|
const now = new Date().toISOString();
|
|
105
|
-
message =
|
|
105
|
+
message = Logger.escapeJsonStringContent(message);
|
|
106
106
|
const logCategory = category !== null && category !== void 0 ? category : LogCategory.INFO;
|
|
107
107
|
const logCategoryColor = constants_1.IS_LOCAL ? LOCAL_CATEGORY_COLORS[logCategory] : undefined;
|
|
108
108
|
const displayCategory = logCategoryColor ? this.colorize(logCategory, logCategoryColor) : logCategory;
|
|
109
|
-
const log_event = event ? `, "event": "${event}"` : '';
|
|
109
|
+
const log_event = event ? `, "event": "${Logger.escapeJsonStringContent(event)}"` : '';
|
|
110
110
|
const log_milliseconds = milliseconds != undefined && milliseconds != null ? `, "milliseconds": ${milliseconds}` : '';
|
|
111
111
|
const log_count = count != undefined && count != null ? `, "count": ${count}` : '';
|
|
112
112
|
const log_dimensions = dimensions && Object.keys(dimensions).length ? `, "dimensions": ${JSON.stringify(dimensions)}` : '';
|
|
@@ -132,6 +132,54 @@ class Logger {
|
|
|
132
132
|
static colorize(value, ansiColor) {
|
|
133
133
|
return `${ansiColor}${value}${ANSI_RESET}`;
|
|
134
134
|
}
|
|
135
|
+
// Escape characters that JSON forbids inside a string value: backslash,
|
|
136
|
+
// double quote, and control chars 0x00-0x1F. ESC (0x1B) is left alone so
|
|
137
|
+
// ANSI color sequences from `colorize`/`Logger.local` keep rendering on
|
|
138
|
+
// developer terminals; in production no ANSI is ever introduced, so the
|
|
139
|
+
// emitted line is valid JSON. Newlines/tabs in error stacks would otherwise
|
|
140
|
+
// produce invalid JSON that downstream consumers (CloudWatch subscribers,
|
|
141
|
+
// app-alerts ingestion) can't peel — the previous regex only handled \\ and
|
|
142
|
+
// \", letting stack traces through unescaped.
|
|
143
|
+
static escapeJsonStringContent(s) {
|
|
144
|
+
let out = '';
|
|
145
|
+
for (let i = 0; i < s.length; i++) {
|
|
146
|
+
const ch = s.charCodeAt(i);
|
|
147
|
+
if (ch === 0x5c) {
|
|
148
|
+
out += '\\\\';
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (ch === 0x22) {
|
|
152
|
+
out += '\\"';
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (ch === 0x08) {
|
|
156
|
+
out += '\\b';
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (ch === 0x09) {
|
|
160
|
+
out += '\\t';
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (ch === 0x0a) {
|
|
164
|
+
out += '\\n';
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (ch === 0x0c) {
|
|
168
|
+
out += '\\f';
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (ch === 0x0d) {
|
|
172
|
+
out += '\\r';
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (ch < 0x20 && ch !== 0x1b) {
|
|
176
|
+
out += '\\u' + ch.toString(16).padStart(4, '0');
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
out += s[i];
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
135
183
|
}
|
|
136
184
|
exports.Logger = Logger;
|
|
137
185
|
Logger.isInitialized = false;
|