@carno.js/core 0.2.3 → 0.2.4
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/dist/Carno.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare class Carno {
|
|
|
16
16
|
config: ApplicationConfig;
|
|
17
17
|
router: Memoirist<TokenRouteWithProvider>;
|
|
18
18
|
private injector;
|
|
19
|
+
private corsCache?;
|
|
19
20
|
private fetch;
|
|
20
21
|
private server;
|
|
21
22
|
constructor(config?: ApplicationConfig);
|
|
@@ -57,7 +58,6 @@ export declare class Carno {
|
|
|
57
58
|
private reportHookFailure;
|
|
58
59
|
private isCorsEnabled;
|
|
59
60
|
private isOriginAllowed;
|
|
60
|
-
private buildCorsHeaders;
|
|
61
61
|
private handlePreflightRequest;
|
|
62
62
|
private applyCorsHeaders;
|
|
63
63
|
close(closeActiveConnections?: boolean): void;
|
package/dist/Carno.js
CHANGED
|
@@ -12,7 +12,7 @@ const createInjector_1 = require("./container/createInjector");
|
|
|
12
12
|
const domain_1 = require("./domain");
|
|
13
13
|
const Context_1 = require("./domain/Context");
|
|
14
14
|
const LocalsContainer_1 = require("./domain/LocalsContainer");
|
|
15
|
-
const
|
|
15
|
+
const cors_headers_cache_1 = require("./domain/cors-headers-cache");
|
|
16
16
|
const on_event_1 = require("./events/on-event");
|
|
17
17
|
const HttpException_1 = require("./exceptions/HttpException");
|
|
18
18
|
const RouteExecutor_1 = require("./route/RouteExecutor");
|
|
@@ -53,6 +53,9 @@ class Carno {
|
|
|
53
53
|
console.error("Unhandled error:", error);
|
|
54
54
|
return new Response("Internal Server Error", { status: 500 });
|
|
55
55
|
};
|
|
56
|
+
if (config.cors) {
|
|
57
|
+
this.corsCache = new cors_headers_cache_1.CorsHeadersCache(config.cors);
|
|
58
|
+
}
|
|
56
59
|
void this.bootstrapApplication();
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
@@ -249,50 +252,19 @@ class Carno {
|
|
|
249
252
|
}
|
|
250
253
|
return false;
|
|
251
254
|
}
|
|
252
|
-
buildCorsHeaders(origin) {
|
|
253
|
-
const cors = this.config.cors;
|
|
254
|
-
const headers = {};
|
|
255
|
-
const allowedOrigin = typeof cors.origins === "string" && cors.origins === "*"
|
|
256
|
-
? "*"
|
|
257
|
-
: origin;
|
|
258
|
-
headers["Access-Control-Allow-Origin"] = allowedOrigin;
|
|
259
|
-
if (cors.credentials) {
|
|
260
|
-
headers["Access-Control-Allow-Credentials"] = "true";
|
|
261
|
-
}
|
|
262
|
-
const methods = cors.methods || cors_config_1.DEFAULT_CORS_METHODS;
|
|
263
|
-
headers["Access-Control-Allow-Methods"] = methods.join(", ");
|
|
264
|
-
const allowedHeaders = cors.allowedHeaders || cors_config_1.DEFAULT_CORS_ALLOWED_HEADERS;
|
|
265
|
-
headers["Access-Control-Allow-Headers"] = allowedHeaders.join(", ");
|
|
266
|
-
if (cors.exposedHeaders && cors.exposedHeaders.length > 0) {
|
|
267
|
-
headers["Access-Control-Expose-Headers"] = cors.exposedHeaders.join(", ");
|
|
268
|
-
}
|
|
269
|
-
if (cors.maxAge !== undefined) {
|
|
270
|
-
headers["Access-Control-Max-Age"] = cors.maxAge.toString();
|
|
271
|
-
}
|
|
272
|
-
return headers;
|
|
273
|
-
}
|
|
274
255
|
handlePreflightRequest(request) {
|
|
275
256
|
const origin = request.headers.get("origin");
|
|
276
257
|
if (!this.isOriginAllowed(origin)) {
|
|
277
258
|
return new Response(null, { status: 403 });
|
|
278
259
|
}
|
|
279
|
-
const corsHeaders = this.
|
|
260
|
+
const corsHeaders = this.corsCache.get(origin);
|
|
280
261
|
return new Response(null, {
|
|
281
262
|
status: 204,
|
|
282
263
|
headers: corsHeaders,
|
|
283
264
|
});
|
|
284
265
|
}
|
|
285
266
|
applyCorsHeaders(response, origin) {
|
|
286
|
-
|
|
287
|
-
const newHeaders = new Headers(response.headers);
|
|
288
|
-
for (const [key, value] of Object.entries(corsHeaders)) {
|
|
289
|
-
newHeaders.set(key, value);
|
|
290
|
-
}
|
|
291
|
-
return new Response(response.body, {
|
|
292
|
-
status: response.status,
|
|
293
|
-
statusText: response.statusText,
|
|
294
|
-
headers: newHeaders,
|
|
295
|
-
});
|
|
267
|
+
return this.corsCache.applyToResponse(response, origin);
|
|
296
268
|
}
|
|
297
269
|
close(closeActiveConnections = false) {
|
|
298
270
|
this.server?.stop(closeActiveConnections);
|
package/dist/domain/Context.d.ts
CHANGED
|
@@ -23,4 +23,9 @@ export declare class Context {
|
|
|
23
23
|
getResponseStatus(): number;
|
|
24
24
|
private buildQueryObject;
|
|
25
25
|
private resolveBody;
|
|
26
|
+
private parseJsonFromBuffer;
|
|
27
|
+
private parseJsonText;
|
|
28
|
+
private isEmptyBuffer;
|
|
29
|
+
private parseUrlEncodedFromBuffer;
|
|
30
|
+
private decodeBuffer;
|
|
26
31
|
}
|
package/dist/domain/Context.js
CHANGED
|
@@ -11,7 +11,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
var Context_1;
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.Context = void 0;
|
|
14
|
+
const http_code_enum_1 = require("../commons/http-code.enum");
|
|
14
15
|
const Injectable_decorator_1 = require("../commons/decorators/Injectable.decorator");
|
|
16
|
+
const HttpException_1 = require("../exceptions/HttpException");
|
|
15
17
|
const provider_scope_1 = require("./provider-scope");
|
|
16
18
|
let Context = Context_1 = class Context {
|
|
17
19
|
constructor() {
|
|
@@ -88,16 +90,54 @@ let Context = Context_1 = class Context {
|
|
|
88
90
|
}
|
|
89
91
|
async resolveBody(request) {
|
|
90
92
|
const contentType = request.headers.get('content-type') || '';
|
|
91
|
-
|
|
93
|
+
// Clone request once - preserve original request untouched
|
|
94
|
+
const clonedRequest = request.clone();
|
|
95
|
+
// FormData multipart requires consuming as formData
|
|
96
|
+
if (contentType.includes('multipart/form-data')) {
|
|
97
|
+
// Need separate clone for rawBody since formData() consumes the body
|
|
98
|
+
this.rawBody = await request.clone().arrayBuffer();
|
|
99
|
+
this.setBody(await clonedRequest.formData());
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// For all other content types, consume body once as ArrayBuffer from clone
|
|
103
|
+
this.rawBody = await clonedRequest.arrayBuffer();
|
|
92
104
|
if (contentType.includes('application/json')) {
|
|
93
|
-
this.body =
|
|
105
|
+
this.body = this.parseJsonFromBuffer(this.rawBody);
|
|
94
106
|
return;
|
|
95
107
|
}
|
|
96
|
-
if (contentType.includes('application/x-www-form-urlencoded')
|
|
97
|
-
this.
|
|
108
|
+
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
109
|
+
this.body = this.parseUrlEncodedFromBuffer(this.rawBody);
|
|
98
110
|
return;
|
|
99
111
|
}
|
|
100
|
-
|
|
112
|
+
// Plain text or unknown content type
|
|
113
|
+
this.body = { body: this.decodeBuffer(this.rawBody) };
|
|
114
|
+
}
|
|
115
|
+
parseJsonFromBuffer(buffer) {
|
|
116
|
+
if (this.isEmptyBuffer(buffer)) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
return this.parseJsonText(this.decodeBuffer(buffer));
|
|
120
|
+
}
|
|
121
|
+
parseJsonText(text) {
|
|
122
|
+
try {
|
|
123
|
+
return JSON.parse(text);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
throw new HttpException_1.HttpException("Invalid JSON body", http_code_enum_1.HttpCode.BAD_REQUEST);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
isEmptyBuffer(buffer) {
|
|
130
|
+
return buffer.byteLength === 0;
|
|
131
|
+
}
|
|
132
|
+
parseUrlEncodedFromBuffer(buffer) {
|
|
133
|
+
if (buffer.byteLength === 0) {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
const text = this.decodeBuffer(buffer);
|
|
137
|
+
return Object.fromEntries(new URLSearchParams(text));
|
|
138
|
+
}
|
|
139
|
+
decodeBuffer(buffer) {
|
|
140
|
+
return new TextDecoder().decode(buffer);
|
|
101
141
|
}
|
|
102
142
|
};
|
|
103
143
|
exports.Context = Context;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CorsConfig } from './cors-config';
|
|
2
|
+
export declare class CorsHeadersCache {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly cache;
|
|
5
|
+
private readonly methodsString;
|
|
6
|
+
private readonly allowedHeadersString;
|
|
7
|
+
private readonly exposedHeadersString;
|
|
8
|
+
private readonly maxAgeString;
|
|
9
|
+
private readonly hasCredentials;
|
|
10
|
+
private readonly isWildcard;
|
|
11
|
+
constructor(config: CorsConfig);
|
|
12
|
+
get(origin: string): Record<string, string>;
|
|
13
|
+
private buildHeaders;
|
|
14
|
+
applyToResponse(response: Response, origin: string): Response;
|
|
15
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CorsHeadersCache = void 0;
|
|
4
|
+
const cors_config_1 = require("./cors-config");
|
|
5
|
+
class CorsHeadersCache {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.cache = new Map();
|
|
9
|
+
const methods = config.methods || cors_config_1.DEFAULT_CORS_METHODS;
|
|
10
|
+
this.methodsString = methods.join(', ');
|
|
11
|
+
const allowedHeaders = config.allowedHeaders || cors_config_1.DEFAULT_CORS_ALLOWED_HEADERS;
|
|
12
|
+
this.allowedHeadersString = allowedHeaders.join(', ');
|
|
13
|
+
this.exposedHeadersString = config.exposedHeaders?.length
|
|
14
|
+
? config.exposedHeaders.join(', ')
|
|
15
|
+
: null;
|
|
16
|
+
this.maxAgeString = config.maxAge !== undefined
|
|
17
|
+
? config.maxAge.toString()
|
|
18
|
+
: null;
|
|
19
|
+
this.hasCredentials = !!config.credentials;
|
|
20
|
+
this.isWildcard = config.origins === '*';
|
|
21
|
+
}
|
|
22
|
+
get(origin) {
|
|
23
|
+
const cacheKey = this.isWildcard ? '*' : origin;
|
|
24
|
+
let cached = this.cache.get(cacheKey);
|
|
25
|
+
if (cached) {
|
|
26
|
+
return cached;
|
|
27
|
+
}
|
|
28
|
+
cached = this.buildHeaders(origin);
|
|
29
|
+
this.cache.set(cacheKey, cached);
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
buildHeaders(origin) {
|
|
33
|
+
const headers = {
|
|
34
|
+
'Access-Control-Allow-Origin': this.isWildcard ? '*' : origin,
|
|
35
|
+
'Access-Control-Allow-Methods': this.methodsString,
|
|
36
|
+
'Access-Control-Allow-Headers': this.allowedHeadersString,
|
|
37
|
+
};
|
|
38
|
+
if (this.hasCredentials) {
|
|
39
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
40
|
+
}
|
|
41
|
+
if (this.exposedHeadersString) {
|
|
42
|
+
headers['Access-Control-Expose-Headers'] = this.exposedHeadersString;
|
|
43
|
+
}
|
|
44
|
+
if (this.maxAgeString) {
|
|
45
|
+
headers['Access-Control-Max-Age'] = this.maxAgeString;
|
|
46
|
+
}
|
|
47
|
+
return headers;
|
|
48
|
+
}
|
|
49
|
+
applyToResponse(response, origin) {
|
|
50
|
+
const corsHeaders = this.get(origin);
|
|
51
|
+
for (const key in corsHeaders) {
|
|
52
|
+
response.headers.set(key, corsHeaders[key]);
|
|
53
|
+
}
|
|
54
|
+
return response;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.CorsHeadersCache = CorsHeadersCache;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carno.js/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Carno.js is a framework for building web applications object oriented with TypeScript and Bun.sh",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "git+ssh://git@github.com:
|
|
31
|
+
"url": "git+ssh://git@github.com:carnojs/carno.js.git"
|
|
32
32
|
},
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "91d257751a1386de821cd4dd3252fce3c070cfca"
|
|
55
55
|
}
|