@holo-js/kernel 0.3.27 → 0.4.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/LICENSE +21 -0
- package/dist/.holo-build-fingerprint +1 -0
- package/dist/chunk-CGVSGOEO.mjs +185 -0
- package/dist/chunk-K4RCM65I.mjs +13 -0
- package/dist/chunk-QQ5YD7HZ.mjs +52 -0
- package/dist/error-reporting.d.ts +19 -0
- package/dist/error-reporting.mjs +10 -0
- package/dist/file-content.d.ts +10 -0
- package/dist/file-content.mjs +54 -0
- package/dist/http-errors.d.ts +2 -1
- package/dist/image-decoding.browser.d.ts +6 -0
- package/dist/image-decoding.browser.mjs +12 -0
- package/dist/image-decoding.d.ts +12 -0
- package/dist/image-decoding.mjs +8 -0
- package/dist/image-decoding.node.d.ts +6 -0
- package/dist/image-decoding.node.mjs +81 -0
- package/dist/index.d.ts +76 -1
- package/dist/index.mjs +281 -4
- package/dist/managed-resources.d.ts +24 -0
- package/dist/managed-resources.mjs +155 -0
- package/dist/request-body.d.ts +26 -0
- package/dist/request-body.mjs +28 -0
- package/package.json +52 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Holo-JS contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
9eb8051fbe40b02e51b66e4640091d67d598fe4ce40e80d8d7454e882cb62bc6
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// src/requestBody.ts
|
|
2
|
+
var DEFAULT_MAX_REQUEST_BODY_BYTES = 1024 * 1024;
|
|
3
|
+
var DEFAULT_MAX_UPLOAD_BODY_BYTES = 8 * 1024 * 1024;
|
|
4
|
+
var maxRequestBodyBytes = DEFAULT_MAX_REQUEST_BODY_BYTES;
|
|
5
|
+
var maxUploadBodyBytes = DEFAULT_MAX_UPLOAD_BODY_BYTES;
|
|
6
|
+
function isMultipartContentType(contentType) {
|
|
7
|
+
return contentType?.split(";", 1)[0]?.trim().toLowerCase() === "multipart/form-data";
|
|
8
|
+
}
|
|
9
|
+
function requestBodyLimitFor(contentType) {
|
|
10
|
+
return isMultipartContentType(contentType) ? maxUploadBodyBytes : maxRequestBodyBytes;
|
|
11
|
+
}
|
|
12
|
+
var HoloRequestBodyTooLargeError = class extends Error {
|
|
13
|
+
status = 413;
|
|
14
|
+
constructor(limit) {
|
|
15
|
+
super(`[@holo-js/kernel] Request body exceeds the ${limit}-byte limit.`);
|
|
16
|
+
this.name = "HoloRequestBodyTooLargeError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function resolveLimit(maxBytes) {
|
|
20
|
+
return maxBytes ?? maxRequestBodyBytes;
|
|
21
|
+
}
|
|
22
|
+
function concatChunks(chunks, total) {
|
|
23
|
+
const bytes = new Uint8Array(total);
|
|
24
|
+
let offset = 0;
|
|
25
|
+
for (const chunk of chunks) {
|
|
26
|
+
bytes.set(chunk, offset);
|
|
27
|
+
offset += chunk.byteLength;
|
|
28
|
+
}
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
async function readBoundedRequestBody(request, maxBytes) {
|
|
32
|
+
const limit = maxBytes ?? requestBodyLimitFor(request.headers.get("content-type"));
|
|
33
|
+
const declared = request.headers.get("content-length");
|
|
34
|
+
if (declared !== null && Number(declared) > limit) {
|
|
35
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
36
|
+
}
|
|
37
|
+
if (!request.body) {
|
|
38
|
+
return new Uint8Array(0);
|
|
39
|
+
}
|
|
40
|
+
const reader = request.body.getReader();
|
|
41
|
+
const chunks = [];
|
|
42
|
+
let total = 0;
|
|
43
|
+
try {
|
|
44
|
+
for (; ; ) {
|
|
45
|
+
const { done, value } = await reader.read();
|
|
46
|
+
if (done) break;
|
|
47
|
+
total += value.byteLength;
|
|
48
|
+
if (total > limit) {
|
|
49
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
50
|
+
}
|
|
51
|
+
chunks.push(value);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
void reader.cancel(error).catch(() => void 0);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
return concatChunks(chunks, total);
|
|
58
|
+
}
|
|
59
|
+
function isWebReadableStream(value) {
|
|
60
|
+
return typeof value === "object" && value !== null && typeof value.getReader === "function";
|
|
61
|
+
}
|
|
62
|
+
function isPlainBufferedObject(value) {
|
|
63
|
+
return typeof value === "object" && value !== null && value.constructor === Object;
|
|
64
|
+
}
|
|
65
|
+
async function readWebStreamBounded(stream, limit) {
|
|
66
|
+
const reader = stream.getReader();
|
|
67
|
+
const chunks = [];
|
|
68
|
+
let total = 0;
|
|
69
|
+
try {
|
|
70
|
+
for (; ; ) {
|
|
71
|
+
const { done, value } = await reader.read();
|
|
72
|
+
if (done) break;
|
|
73
|
+
total += value.byteLength;
|
|
74
|
+
if (total > limit) {
|
|
75
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
76
|
+
}
|
|
77
|
+
chunks.push(value);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
void reader.cancel(error).catch(() => void 0);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
return concatChunks(chunks, total);
|
|
84
|
+
}
|
|
85
|
+
async function readBoundedCachedBody(cached, maxBytes) {
|
|
86
|
+
return (await readBoundedBufferedBody(cached, maxBytes))?.bytes;
|
|
87
|
+
}
|
|
88
|
+
async function readBoundedBufferedBody(cached, maxBytes) {
|
|
89
|
+
const value = await cached;
|
|
90
|
+
const limit = resolveLimit(maxBytes);
|
|
91
|
+
if (value instanceof Uint8Array) {
|
|
92
|
+
if (value.byteLength > limit) {
|
|
93
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
94
|
+
}
|
|
95
|
+
return { bytes: new Uint8Array(value) };
|
|
96
|
+
}
|
|
97
|
+
if (value instanceof ArrayBuffer) {
|
|
98
|
+
if (value.byteLength > limit) {
|
|
99
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
100
|
+
}
|
|
101
|
+
return { bytes: new Uint8Array(value.slice(0)) };
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === "string" || value instanceof URLSearchParams) {
|
|
104
|
+
const bytes = new TextEncoder().encode(value.toString());
|
|
105
|
+
if (bytes.byteLength > limit) {
|
|
106
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
107
|
+
}
|
|
108
|
+
return { bytes };
|
|
109
|
+
}
|
|
110
|
+
if (typeof FormData !== "undefined" && value instanceof FormData) {
|
|
111
|
+
const encoded = new Response(value);
|
|
112
|
+
const bytes = new Uint8Array(await encoded.arrayBuffer());
|
|
113
|
+
if (bytes.byteLength > limit) {
|
|
114
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
115
|
+
}
|
|
116
|
+
return { bytes, contentType: encoded.headers.get("content-type") ?? void 0 };
|
|
117
|
+
}
|
|
118
|
+
if (isWebReadableStream(value)) {
|
|
119
|
+
return { bytes: await readWebStreamBounded(value, limit) };
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === "object" && value !== null && Symbol.asyncIterator in value) {
|
|
122
|
+
return { bytes: await readBoundedNodeRequestBody(value, limit) };
|
|
123
|
+
}
|
|
124
|
+
if (isPlainBufferedObject(value)) {
|
|
125
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
126
|
+
if (bytes.byteLength > limit) {
|
|
127
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
128
|
+
}
|
|
129
|
+
return { bytes };
|
|
130
|
+
}
|
|
131
|
+
return void 0;
|
|
132
|
+
}
|
|
133
|
+
async function readBoundedRequestText(request, maxBytes) {
|
|
134
|
+
return new TextDecoder().decode(await readBoundedRequestBody(request, maxBytes));
|
|
135
|
+
}
|
|
136
|
+
async function readBoundedRequestJson(request, maxBytes) {
|
|
137
|
+
return JSON.parse(await readBoundedRequestText(request, maxBytes));
|
|
138
|
+
}
|
|
139
|
+
async function readBoundedRequestFormData(request, maxBytes) {
|
|
140
|
+
const bytes = await readBoundedRequestBody(request, maxBytes);
|
|
141
|
+
return new Response(bytes, { headers: { "content-type": request.headers.get("content-type") ?? "" } }).formData();
|
|
142
|
+
}
|
|
143
|
+
async function readBoundedNodeRequestBody(stream, maxBytes, declaredLength) {
|
|
144
|
+
const limit = resolveLimit(maxBytes);
|
|
145
|
+
if (declaredLength != null && Number(declaredLength) > limit) {
|
|
146
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
147
|
+
}
|
|
148
|
+
const chunks = [];
|
|
149
|
+
let total = 0;
|
|
150
|
+
for await (const chunk of stream) {
|
|
151
|
+
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
|
|
152
|
+
total += bytes.byteLength;
|
|
153
|
+
if (total > limit) {
|
|
154
|
+
throw new HoloRequestBodyTooLargeError(limit);
|
|
155
|
+
}
|
|
156
|
+
chunks.push(bytes);
|
|
157
|
+
}
|
|
158
|
+
return concatChunks(chunks, total);
|
|
159
|
+
}
|
|
160
|
+
var requestBodyInternals = Object.freeze({
|
|
161
|
+
configureMaxRequestBodyBytes(bytes) {
|
|
162
|
+
maxRequestBodyBytes = bytes ?? DEFAULT_MAX_REQUEST_BODY_BYTES;
|
|
163
|
+
},
|
|
164
|
+
configureMaxUploadBodyBytes(bytes) {
|
|
165
|
+
maxUploadBodyBytes = bytes ?? DEFAULT_MAX_UPLOAD_BODY_BYTES;
|
|
166
|
+
},
|
|
167
|
+
getMaxRequestBodyBytes() {
|
|
168
|
+
return maxRequestBodyBytes;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
DEFAULT_MAX_REQUEST_BODY_BYTES,
|
|
174
|
+
DEFAULT_MAX_UPLOAD_BODY_BYTES,
|
|
175
|
+
requestBodyLimitFor,
|
|
176
|
+
HoloRequestBodyTooLargeError,
|
|
177
|
+
readBoundedRequestBody,
|
|
178
|
+
readBoundedCachedBody,
|
|
179
|
+
readBoundedBufferedBody,
|
|
180
|
+
readBoundedRequestText,
|
|
181
|
+
readBoundedRequestJson,
|
|
182
|
+
readBoundedRequestFormData,
|
|
183
|
+
readBoundedNodeRequestBody,
|
|
184
|
+
requestBodyInternals
|
|
185
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/imageDecoding.ts
|
|
2
|
+
var imageDecodingLimits = Object.freeze({
|
|
3
|
+
maximumDecodedPixels: 4e7,
|
|
4
|
+
maximumFrames: 1e3
|
|
5
|
+
});
|
|
6
|
+
async function verifyDecodedImage(_contents, _mimeType, _aspectRatios = []) {
|
|
7
|
+
throw new Error("[@holo-js/kernel] Server-side image validation must run on Node.js, where images can be fully decoded.");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
imageDecodingLimits,
|
|
12
|
+
verifyDecodedImage
|
|
13
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/errorReporting.ts
|
|
2
|
+
function errorReportingState() {
|
|
3
|
+
const runtime = globalThis;
|
|
4
|
+
runtime.__holoErrorReporting__ ??= {};
|
|
5
|
+
return runtime.__holoErrorReporting__;
|
|
6
|
+
}
|
|
7
|
+
function describeContext(context) {
|
|
8
|
+
return `[holo:${context.capability}] ${context.operation}`;
|
|
9
|
+
}
|
|
10
|
+
function logReporterFailure(error, context, reporterError) {
|
|
11
|
+
console.error(describeContext(context), error, reporterError);
|
|
12
|
+
}
|
|
13
|
+
function isUnreported(error, exceptions) {
|
|
14
|
+
return exceptions.dontReport?.some((errorClass) => error instanceof errorClass) ?? false;
|
|
15
|
+
}
|
|
16
|
+
function deliver(exceptions, error, context) {
|
|
17
|
+
if (exceptions && isUnreported(error, exceptions)) return;
|
|
18
|
+
if (!exceptions?.report) {
|
|
19
|
+
console.error(describeContext(context), error);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
void Promise.resolve(exceptions.report(error, context)).catch((reporterError) => logReporterFailure(error, context, reporterError));
|
|
23
|
+
}
|
|
24
|
+
function reportWith(exceptions, error, context) {
|
|
25
|
+
try {
|
|
26
|
+
deliver(exceptions, error, context);
|
|
27
|
+
} catch (reporterError) {
|
|
28
|
+
logReporterFailure(error, context, reporterError);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function configure(exceptions) {
|
|
32
|
+
const state = errorReportingState();
|
|
33
|
+
const previous = state.exceptions;
|
|
34
|
+
state.exceptions = exceptions;
|
|
35
|
+
return previous;
|
|
36
|
+
}
|
|
37
|
+
function reportError(error, context) {
|
|
38
|
+
reportWith(errorReportingState().exceptions, error, context);
|
|
39
|
+
}
|
|
40
|
+
function captureErrorReporter() {
|
|
41
|
+
const exceptions = errorReportingState().exceptions;
|
|
42
|
+
return (error, context) => reportWith(exceptions, error, context);
|
|
43
|
+
}
|
|
44
|
+
var errorReportingInternals = {
|
|
45
|
+
configure
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
reportError,
|
|
50
|
+
captureErrorReporter,
|
|
51
|
+
errorReportingInternals
|
|
52
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface HoloErrorReportContext {
|
|
2
|
+
readonly capability: string;
|
|
3
|
+
readonly operation: string;
|
|
4
|
+
}
|
|
5
|
+
type HoloUnreportedErrorClass = new (...args: never[]) => Error;
|
|
6
|
+
interface HoloExceptionsConfig {
|
|
7
|
+
report?(error: unknown, context: HoloErrorReportContext): void | Promise<void>;
|
|
8
|
+
dontReport?: readonly HoloUnreportedErrorClass[];
|
|
9
|
+
}
|
|
10
|
+
type HoloErrorReporter = (error: unknown, context: HoloErrorReportContext) => void;
|
|
11
|
+
declare function configure(exceptions: HoloExceptionsConfig | undefined): HoloExceptionsConfig | undefined;
|
|
12
|
+
declare function reportError(error: unknown, context: HoloErrorReportContext): void;
|
|
13
|
+
declare function captureErrorReporter(): HoloErrorReporter;
|
|
14
|
+
declare const errorReportingInternals: {
|
|
15
|
+
configure: typeof configure;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { captureErrorReporter, errorReportingInternals, reportError };
|
|
19
|
+
export type { HoloErrorReportContext, HoloErrorReporter, HoloExceptionsConfig, HoloUnreportedErrorClass };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type FileContentInspection = {
|
|
2
|
+
readonly mimeType: string | null;
|
|
3
|
+
readonly passive: boolean;
|
|
4
|
+
};
|
|
5
|
+
declare const fileContentSampleBytes = 65536;
|
|
6
|
+
declare function mimeTypeFromFileName(fileName: string): string | null;
|
|
7
|
+
declare function inspectFileContent(sample: Uint8Array, fileName?: string): Promise<FileContentInspection>;
|
|
8
|
+
|
|
9
|
+
export { fileContentSampleBytes, inspectFileContent, mimeTypeFromFileName };
|
|
10
|
+
export type { FileContentInspection };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/fileContent.ts
|
|
2
|
+
var fileContentSampleBytes = 65536;
|
|
3
|
+
var mimeTypesByExtension = Object.freeze({
|
|
4
|
+
avif: "image/avif",
|
|
5
|
+
css: "text/css",
|
|
6
|
+
csv: "text/csv",
|
|
7
|
+
gif: "image/gif",
|
|
8
|
+
htm: "text/html",
|
|
9
|
+
html: "text/html",
|
|
10
|
+
jpeg: "image/jpeg",
|
|
11
|
+
jpg: "image/jpeg",
|
|
12
|
+
json: "application/json",
|
|
13
|
+
md: "text/markdown",
|
|
14
|
+
mp3: "audio/mpeg",
|
|
15
|
+
pdf: "application/pdf",
|
|
16
|
+
png: "image/png",
|
|
17
|
+
svg: "image/svg+xml",
|
|
18
|
+
txt: "text/plain",
|
|
19
|
+
webp: "image/webp",
|
|
20
|
+
woff: "font/woff",
|
|
21
|
+
woff2: "font/woff2",
|
|
22
|
+
xml: "application/xml"
|
|
23
|
+
});
|
|
24
|
+
var passiveDocumentMimeTypes = /* @__PURE__ */ new Set([
|
|
25
|
+
"application/json",
|
|
26
|
+
"application/pdf",
|
|
27
|
+
"text/css",
|
|
28
|
+
"text/csv",
|
|
29
|
+
"text/markdown",
|
|
30
|
+
"text/plain"
|
|
31
|
+
]);
|
|
32
|
+
function isPassiveMimeType(mimeType) {
|
|
33
|
+
if (!mimeType) return false;
|
|
34
|
+
return passiveDocumentMimeTypes.has(mimeType) || /^(?:image|audio|video|font)\//u.test(mimeType) && !mimeType.endsWith("+xml");
|
|
35
|
+
}
|
|
36
|
+
function mimeTypeFromFileName(fileName) {
|
|
37
|
+
const baseName = fileName.slice(Math.max(fileName.lastIndexOf("/"), fileName.lastIndexOf("\\")) + 1);
|
|
38
|
+
const separator = baseName.lastIndexOf(".");
|
|
39
|
+
if (separator < 1) return null;
|
|
40
|
+
return mimeTypesByExtension[baseName.slice(separator + 1).toLowerCase()] ?? null;
|
|
41
|
+
}
|
|
42
|
+
async function inspectFileContent(sample, fileName) {
|
|
43
|
+
const { fileTypeFromBuffer, supportedMimeTypes } = await import("file-type");
|
|
44
|
+
const detectableMimeTypes = supportedMimeTypes;
|
|
45
|
+
const detected = await fileTypeFromBuffer(sample.subarray(0, fileContentSampleBytes)).catch(() => void 0);
|
|
46
|
+
const named = fileName ? mimeTypeFromFileName(fileName) : null;
|
|
47
|
+
const mimeType = detected?.mime ?? (named && !detectableMimeTypes.has(named) ? named : null);
|
|
48
|
+
return { mimeType, passive: isPassiveMimeType(mimeType) };
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
fileContentSampleBytes,
|
|
52
|
+
inspectFileContent,
|
|
53
|
+
mimeTypeFromFileName
|
|
54
|
+
};
|
package/dist/http-errors.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ type NormalizedHoloHttpError = {
|
|
|
8
8
|
declare function isHoloHttpErrorStatus(status: number): status is HoloHttpErrorStatus;
|
|
9
9
|
declare function normalizeHoloHttpError(error: unknown): NormalizedHoloHttpError | undefined;
|
|
10
10
|
|
|
11
|
-
export {
|
|
11
|
+
export { isHoloHttpErrorStatus, normalizeHoloHttpError };
|
|
12
|
+
export type { HoloHttpErrorStatus, NormalizedHoloHttpError };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DecodedImageVerification } from './image-decoding.js';
|
|
2
|
+
export { imageDecodingLimits } from './image-decoding.js';
|
|
3
|
+
|
|
4
|
+
declare function verifyDecodedImage(_contents: Uint8Array, _mimeType: string, aspectRatios?: readonly (readonly number[])[]): Promise<DecodedImageVerification>;
|
|
5
|
+
|
|
6
|
+
export { verifyDecodedImage };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
imageDecodingLimits
|
|
3
|
+
} from "./chunk-K4RCM65I.mjs";
|
|
4
|
+
|
|
5
|
+
// src/imageDecoding.browser.ts
|
|
6
|
+
async function verifyDecodedImage(_contents, _mimeType, aspectRatios = []) {
|
|
7
|
+
return { valid: true, aspectRatios: aspectRatios.map(() => true) };
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
imageDecodingLimits,
|
|
11
|
+
verifyDecodedImage
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const imageDecodingLimits: Readonly<{
|
|
2
|
+
maximumDecodedPixels: 40000000;
|
|
3
|
+
maximumFrames: 1000;
|
|
4
|
+
}>;
|
|
5
|
+
interface DecodedImageVerification {
|
|
6
|
+
readonly valid: boolean;
|
|
7
|
+
readonly aspectRatios: readonly boolean[];
|
|
8
|
+
}
|
|
9
|
+
declare function verifyDecodedImage(_contents: Uint8Array, _mimeType: string, _aspectRatios?: readonly (readonly number[])[]): Promise<DecodedImageVerification>;
|
|
10
|
+
|
|
11
|
+
export { imageDecodingLimits, verifyDecodedImage };
|
|
12
|
+
export type { DecodedImageVerification };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DecodedImageVerification } from './image-decoding.js';
|
|
2
|
+
export { imageDecodingLimits } from './image-decoding.js';
|
|
3
|
+
|
|
4
|
+
declare function verifyDecodedImage(contents: Uint8Array, mimeType: string, aspectRatios?: readonly (readonly number[])[]): Promise<DecodedImageVerification>;
|
|
5
|
+
|
|
6
|
+
export { verifyDecodedImage };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
imageDecodingLimits
|
|
3
|
+
} from "./chunk-K4RCM65I.mjs";
|
|
4
|
+
|
|
5
|
+
// src/imageDecoding.node.ts
|
|
6
|
+
var decodedFormatMatchers = Object.freeze({
|
|
7
|
+
"image/avif": (metadata) => metadata.format === "heif" && metadata.compression === "av1",
|
|
8
|
+
"image/gif": (metadata) => metadata.format === "gif",
|
|
9
|
+
"image/jpeg": (metadata) => metadata.format === "jpeg",
|
|
10
|
+
"image/png": (metadata) => metadata.format === "png",
|
|
11
|
+
"image/tiff": (metadata) => metadata.format === "tiff",
|
|
12
|
+
"image/webp": (metadata) => metadata.format === "webp"
|
|
13
|
+
});
|
|
14
|
+
function isMissingModule(error) {
|
|
15
|
+
if (!(error instanceof Error)) return false;
|
|
16
|
+
const code = error.code;
|
|
17
|
+
return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || isMissingModule(error.cause);
|
|
18
|
+
}
|
|
19
|
+
async function loadSharp() {
|
|
20
|
+
try {
|
|
21
|
+
return (await import("sharp")).default;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (!isMissingModule(error)) throw error;
|
|
24
|
+
throw new Error('[@holo-js/kernel] Server-side image validation requires the optional "sharp" package. Install sharp in your application.', { cause: error });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function skipSubBlocks(bytes, start) {
|
|
28
|
+
let offset = start;
|
|
29
|
+
while (offset < bytes.length) {
|
|
30
|
+
const size = bytes[offset] ?? 0;
|
|
31
|
+
offset += 1;
|
|
32
|
+
if (size === 0) return offset;
|
|
33
|
+
offset += size;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function colorTableBytes(flags) {
|
|
38
|
+
return (flags & 128) === 0 ? 0 : 3 * (1 << (flags & 7) + 1);
|
|
39
|
+
}
|
|
40
|
+
function hasCompleteGifStructure(bytes) {
|
|
41
|
+
let offset = 13 + colorTableBytes(bytes[10] ?? 0);
|
|
42
|
+
while (offset !== null && offset < bytes.length) {
|
|
43
|
+
const introducer = bytes[offset];
|
|
44
|
+
if (introducer === 59) return true;
|
|
45
|
+
if (introducer === 33) {
|
|
46
|
+
offset = skipSubBlocks(bytes, offset + 2);
|
|
47
|
+
} else if (introducer === 44) {
|
|
48
|
+
offset = skipSubBlocks(bytes, offset + 10 + colorTableBytes(bytes[offset + 9] ?? 0) + 1);
|
|
49
|
+
} else {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
async function verifyDecodedImage(contents, mimeType, aspectRatios = []) {
|
|
56
|
+
const invalid = { valid: false, aspectRatios: [] };
|
|
57
|
+
const matchesDecodedFormat = decodedFormatMatchers[mimeType];
|
|
58
|
+
if (!matchesDecodedFormat) return invalid;
|
|
59
|
+
if (mimeType === "image/gif" && !hasCompleteGifStructure(contents)) return invalid;
|
|
60
|
+
const decode = await loadSharp();
|
|
61
|
+
try {
|
|
62
|
+
const image = decode(contents, { animated: true, failOn: "warning", limitInputPixels: imageDecodingLimits.maximumDecodedPixels });
|
|
63
|
+
const metadata = await image.metadata();
|
|
64
|
+
if (!matchesDecodedFormat(metadata) || (metadata.pages ?? 1) > imageDecodingLimits.maximumFrames) return invalid;
|
|
65
|
+
await image.stats();
|
|
66
|
+
const width = metadata.width;
|
|
67
|
+
const height = metadata.pageHeight ?? metadata.height;
|
|
68
|
+
if (!width || !height) return invalid;
|
|
69
|
+
const precision = 1 / (Math.max((width + height) / 2, height) + 1);
|
|
70
|
+
return {
|
|
71
|
+
valid: true,
|
|
72
|
+
aspectRatios: aspectRatios.map((ratios) => ratios.some((ratio) => Math.abs(ratio - width / height) <= precision))
|
|
73
|
+
};
|
|
74
|
+
} catch {
|
|
75
|
+
return invalid;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
imageDecodingLimits,
|
|
80
|
+
verifyDecodedImage
|
|
81
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { HoloHttpErrorStatus, NormalizedHoloHttpError, isHoloHttpErrorStatus, normalizeHoloHttpError } from './http-errors.js';
|
|
2
|
+
export { BufferedRequestBody, DEFAULT_MAX_REQUEST_BODY_BYTES, DEFAULT_MAX_UPLOAD_BODY_BYTES, HoloRequestBodyTooLargeError, readBoundedBufferedBody, readBoundedCachedBody, readBoundedNodeRequestBody, readBoundedRequestBody, readBoundedRequestFormData, readBoundedRequestJson, readBoundedRequestText, requestBodyInternals, requestBodyLimitFor } from './request-body.js';
|
|
2
3
|
|
|
3
4
|
interface RuntimeLifecycleContext {
|
|
4
5
|
readonly projectRoot: string;
|
|
@@ -409,4 +410,78 @@ declare function resolveNormalizedRedisConnection(config: NormalizedHoloRedisCon
|
|
|
409
410
|
declare function normalizeRedisConfig(config?: HoloRedisConfig): NormalizedHoloRedisConfig;
|
|
410
411
|
declare function defineRedisConfig<TConfig extends HoloRedisConfig>(config: TConfig): Readonly<TConfig>;
|
|
411
412
|
|
|
412
|
-
|
|
413
|
+
type HoloRequestHeaders = Headers | ReadonlyArray<readonly [string, string]> | Record<string, string | readonly string[] | undefined> | {
|
|
414
|
+
readonly get?: (name: string) => string | null | undefined;
|
|
415
|
+
readonly forEach?: (callback: (value: string, key: string) => void) => void;
|
|
416
|
+
readonly entries?: () => Iterable<readonly [string, string]>;
|
|
417
|
+
};
|
|
418
|
+
type RequestUrlParts = {
|
|
419
|
+
readonly url?: string | URL;
|
|
420
|
+
readonly path?: string;
|
|
421
|
+
readonly headers?: HoloRequestHeaders;
|
|
422
|
+
};
|
|
423
|
+
type HoloRequestInput = RequestUrlParts & {
|
|
424
|
+
readonly request?: Request;
|
|
425
|
+
readonly req?: RequestUrlParts;
|
|
426
|
+
readonly node?: {
|
|
427
|
+
readonly req?: RequestUrlParts;
|
|
428
|
+
};
|
|
429
|
+
readonly web?: {
|
|
430
|
+
readonly request?: RequestUrlParts;
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
type HoloForwardedRequest = {
|
|
434
|
+
readonly address?: string;
|
|
435
|
+
readonly protocol?: string;
|
|
436
|
+
readonly host?: string;
|
|
437
|
+
readonly port?: string;
|
|
438
|
+
readonly prefix?: string;
|
|
439
|
+
};
|
|
440
|
+
type HoloForwardedRequestResolver = (readHeader: (name: string) => string | undefined, connectionAddress: string | undefined) => HoloForwardedRequest;
|
|
441
|
+
type HoloConnectionAddressProvider = () => string | undefined;
|
|
442
|
+
declare function configureForwardedRequestResolver(resolver: HoloForwardedRequestResolver | undefined): void;
|
|
443
|
+
declare function provideConnectionAddress(provider: HoloConnectionAddressProvider | undefined): void;
|
|
444
|
+
declare function currentConnectionAddress(): string | undefined;
|
|
445
|
+
declare class HoloRequestUrlError extends TypeError {
|
|
446
|
+
readonly status = 400;
|
|
447
|
+
constructor(message: string);
|
|
448
|
+
}
|
|
449
|
+
declare function resolveRequestUrl(input: HoloRequestInput): URL;
|
|
450
|
+
declare function resolveRoutedRequestUrl(input: HoloRequestInput): URL;
|
|
451
|
+
declare function resolveClientAddress(input: HoloRequestInput): string | undefined;
|
|
452
|
+
declare const requestUrlInternals: Readonly<{
|
|
453
|
+
configureForwardedRequestResolver: typeof configureForwardedRequestResolver;
|
|
454
|
+
currentConnectionAddress: typeof currentConnectionAddress;
|
|
455
|
+
provideConnectionAddress: typeof provideConnectionAddress;
|
|
456
|
+
resolveClientAddress: typeof resolveClientAddress;
|
|
457
|
+
resolveRoutedRequestUrl: typeof resolveRoutedRequestUrl;
|
|
458
|
+
}>;
|
|
459
|
+
declare function sameOriginRedirectPath(path: string | undefined): string;
|
|
460
|
+
|
|
461
|
+
declare function encryptCookieValue(name: string, value: string, appKey: string | undefined): string;
|
|
462
|
+
declare function decryptCookieValue(name: string, value: string, appKey: string | undefined): string | undefined;
|
|
463
|
+
declare const cookieEncryptionInternals: Readonly<{
|
|
464
|
+
decryptCookieValue: typeof decryptCookieValue;
|
|
465
|
+
encryptCookieValue: typeof encryptCookieValue;
|
|
466
|
+
}>;
|
|
467
|
+
|
|
468
|
+
declare function windowsStorageNameProblem(name: string): string | null;
|
|
469
|
+
declare function segmentProblem(segment: string): string | null;
|
|
470
|
+
declare function assertValidSegment(segment: string): void;
|
|
471
|
+
declare function splitLocalStoragePath(path: string): string[];
|
|
472
|
+
declare function isReservedLocalStorageName(name: string | undefined): boolean;
|
|
473
|
+
declare const localStoragePathInternals: {
|
|
474
|
+
assertValidSegment: typeof assertValidSegment;
|
|
475
|
+
isReservedLocalStorageName: typeof isReservedLocalStorageName;
|
|
476
|
+
segmentProblem: typeof segmentProblem;
|
|
477
|
+
splitLocalStoragePath: typeof splitLocalStoragePath;
|
|
478
|
+
windowsStorageNameProblem: typeof windowsStorageNameProblem;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
declare const COOKIE_WRITE_UNAVAILABLE = "cookie_write_unavailable";
|
|
482
|
+
declare function createCookieWriteUnavailableError(message: string, cause?: unknown): Error;
|
|
483
|
+
|
|
484
|
+
declare function resolveRetryBackoffSeconds(backoff: number | readonly number[], attempt: number): number;
|
|
485
|
+
|
|
486
|
+
export { COOKIE_WRITE_UNAVAILABLE, DEFAULT_HOLO_PROJECT_PATHS, DEFAULT_REDIS_CONNECTION, DEFAULT_REDIS_DB, DEFAULT_REDIS_HOST, DEFAULT_REDIS_PORT, HOLO_PROJECT_PREPARE_API_VERSION, HoloProjectPrepareError, HoloRequestUrlError, RuntimeLifecycle, cookieEncryptionInternals, createCookieWriteUnavailableError, createRuntimeLifecycle, defineHoloPlugin, defineHoloProject, defineHoloProjectPreparer, defineRedisConfig, holoRedisDefaults, loadHoloPluginBootModules, loadHoloPluginContributionModules, loadHoloPluginDefinitions, localStoragePathInternals, normalizeHoloPluginDefinition, normalizeHoloProjectConfig, normalizeRedisConfig, requestUrlInternals, resolveHoloPluginModulePath, resolveNormalizedRedisConnection, resolveRequestUrl, resolveRetryBackoffSeconds, sameOriginRedirectPath };
|
|
487
|
+
export type { ConcurrencyOptions, DatabaseLogger, HoloConnectionAddressProvider, HoloDatabaseDriver, HoloForwardedRequest, HoloForwardedRequestResolver, HoloGeneratedProjectArtifact, HoloManagedProjectArtifact, HoloPluginBroadcastContributions, HoloPluginCacheContributions, HoloPluginCliContributions, HoloPluginConfigContributions, HoloPluginContributions, HoloPluginDefinition, HoloPluginDependencyContributions, HoloPluginFrameworkContribution, HoloPluginFrameworkTsconfigKind, HoloPluginMailContributions, HoloPluginMigrationContributions, HoloPluginNotificationContributions, HoloPluginPackageManager, HoloPluginProjectContributions, HoloPluginQueueContributions, HoloPluginRuntimeContributions, HoloPluginRuntimeMapContributions, HoloPluginRuntimeModule, HoloProjectArtifactContents, HoloProjectConfig, HoloProjectConnectionConfig, HoloProjectDatabaseConfig, HoloProjectPaths, HoloProjectPrepareChange, HoloProjectPrepareChangeKind, HoloProjectPrepareCommand, HoloProjectPrepareContext, HoloProjectPrepareDiagnostic, HoloProjectPrepareFailure, HoloProjectPrepareFramework, HoloProjectPrepareFullRetry, HoloProjectPrepareLogger, HoloProjectPrepareModule, HoloProjectPreparePlugin, HoloProjectPrepareResult, HoloProjectPrepareRun, HoloProjectPrepareSource, HoloProjectPrepareWatch, HoloProjectPreparedResult, HoloProjectPreparer, HoloRedisClusterNodeConfig, HoloRedisConfig, HoloRedisConnectionConfig, HoloRequestHeaders, HoloRequestInput, LoadedHoloPluginDefinition, MigrationErrorLog, MigrationStartLog, MigrationSuccessLog, NormalizedHoloProjectConfig, NormalizedHoloRedisClusterNodeConfig, NormalizedHoloRedisConfig, NormalizedHoloRedisConnectionConfig, PluginLoadOptions, QueryErrorLog, QueryStartLog, QuerySuccessLog, RuntimeContribution, RuntimeLifecycleContext, SeederErrorLog, SeederStartLog, SeederSuccessLog, TransactionLog, TransactionScopeKind };
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,20 @@ import {
|
|
|
2
2
|
isHoloHttpErrorStatus,
|
|
3
3
|
normalizeHoloHttpError
|
|
4
4
|
} from "./chunk-UIGMROG3.mjs";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_MAX_REQUEST_BODY_BYTES,
|
|
7
|
+
DEFAULT_MAX_UPLOAD_BODY_BYTES,
|
|
8
|
+
HoloRequestBodyTooLargeError,
|
|
9
|
+
readBoundedBufferedBody,
|
|
10
|
+
readBoundedCachedBody,
|
|
11
|
+
readBoundedNodeRequestBody,
|
|
12
|
+
readBoundedRequestBody,
|
|
13
|
+
readBoundedRequestFormData,
|
|
14
|
+
readBoundedRequestJson,
|
|
15
|
+
readBoundedRequestText,
|
|
16
|
+
requestBodyInternals,
|
|
17
|
+
requestBodyLimitFor
|
|
18
|
+
} from "./chunk-CGVSGOEO.mjs";
|
|
5
19
|
|
|
6
20
|
// src/lifecycle.ts
|
|
7
21
|
function orderContributions(contributions) {
|
|
@@ -51,7 +65,7 @@ var RuntimeLifecycle = class {
|
|
|
51
65
|
try {
|
|
52
66
|
await this.dispose(context);
|
|
53
67
|
} catch (cleanupError) {
|
|
54
|
-
throw new AggregateError([error, cleanupError], "[Holo Runtime] Initialization and rollback both failed.");
|
|
68
|
+
throw new AggregateError([error, cleanupError], "[Holo Runtime] Initialization and rollback both failed.", { cause: cleanupError });
|
|
55
69
|
}
|
|
56
70
|
throw error;
|
|
57
71
|
}
|
|
@@ -168,7 +182,7 @@ async function loadHoloPluginDefinitions(projectRoot, packageNames, options = {}
|
|
|
168
182
|
try {
|
|
169
183
|
manifest = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
170
184
|
} catch (error) {
|
|
171
|
-
if (isRecord(error) && error.code === "ENOENT") throw new Error(`Cannot find module '${packageName}/package.json'
|
|
185
|
+
if (isRecord(error) && error.code === "ENOENT") throw new Error(`Cannot find module '${packageName}/package.json'`, { cause: error });
|
|
172
186
|
throw error;
|
|
173
187
|
}
|
|
174
188
|
if (!isRecord(manifest.holo) || !normalizeString(manifest.holo.plugin)) {
|
|
@@ -325,7 +339,7 @@ function normalizeUrl(value, label) {
|
|
|
325
339
|
if (error instanceof Error && error.message.startsWith("[Holo Redis]")) {
|
|
326
340
|
throw error;
|
|
327
341
|
}
|
|
328
|
-
throw new Error(`[Holo Redis] ${label} must be a valid redis:// or rediss:// URL
|
|
342
|
+
throw new Error(`[Holo Redis] ${label} must be a valid redis:// or rediss:// URL.`, { cause: error });
|
|
329
343
|
}
|
|
330
344
|
return normalized;
|
|
331
345
|
}
|
|
@@ -426,15 +440,264 @@ function normalizeRedisConfig(config = {}) {
|
|
|
426
440
|
function defineRedisConfig(config) {
|
|
427
441
|
return Object.freeze({ ...config });
|
|
428
442
|
}
|
|
443
|
+
|
|
444
|
+
// src/requestUrl.ts
|
|
445
|
+
function requestForwardingState() {
|
|
446
|
+
const runtime = globalThis;
|
|
447
|
+
runtime.__holoRequestForwarding__ ??= {};
|
|
448
|
+
return runtime.__holoRequestForwarding__;
|
|
449
|
+
}
|
|
450
|
+
function configureForwardedRequestResolver(resolver) {
|
|
451
|
+
requestForwardingState().resolveForwardedRequest = resolver;
|
|
452
|
+
}
|
|
453
|
+
function provideConnectionAddress(provider) {
|
|
454
|
+
requestForwardingState().connectionAddress = provider;
|
|
455
|
+
}
|
|
456
|
+
function currentConnectionAddress() {
|
|
457
|
+
try {
|
|
458
|
+
return requestForwardingState().connectionAddress?.();
|
|
459
|
+
} catch {
|
|
460
|
+
return void 0;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function isHeaderTuples(headers) {
|
|
464
|
+
return Array.isArray(headers);
|
|
465
|
+
}
|
|
466
|
+
function readHeader(headers, name) {
|
|
467
|
+
if (!headers) return void 0;
|
|
468
|
+
if (isHeaderTuples(headers)) {
|
|
469
|
+
return headers.find(([key]) => key.toLowerCase() === name)?.[1];
|
|
470
|
+
}
|
|
471
|
+
if ("get" in headers && typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
472
|
+
if ("entries" in headers && typeof headers.entries === "function") {
|
|
473
|
+
for (const [key, value] of headers.entries()) {
|
|
474
|
+
if (key.toLowerCase() === name) return value;
|
|
475
|
+
}
|
|
476
|
+
return void 0;
|
|
477
|
+
}
|
|
478
|
+
if ("forEach" in headers && typeof headers.forEach === "function") {
|
|
479
|
+
let result;
|
|
480
|
+
headers.forEach((value, key) => {
|
|
481
|
+
if (key.toLowerCase() === name) result = value;
|
|
482
|
+
});
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
486
|
+
if (key.toLowerCase() === name) {
|
|
487
|
+
return typeof value === "string" ? value : Array.isArray(value) ? value.join(",") : void 0;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
var HoloRequestUrlError = class extends TypeError {
|
|
493
|
+
status = 400;
|
|
494
|
+
constructor(message) {
|
|
495
|
+
super(`[@holo-js/kernel] ${message}`);
|
|
496
|
+
this.name = "HoloRequestUrlError";
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
var invalidRequestHostMessage = "Request hosts must be a valid hostname and optional port.";
|
|
500
|
+
var invalidRequestPortMessage = "Request ports must be a number from 0 to 65535.";
|
|
501
|
+
var invalidRequestPrefixMessage = 'Request prefixes must be a path starting with a single "/".';
|
|
502
|
+
function validForwardedPort(port) {
|
|
503
|
+
if (!/^\d{1,5}$/u.test(port) || Number(port) > 65535) throw new HoloRequestUrlError(invalidRequestPortMessage);
|
|
504
|
+
return port;
|
|
505
|
+
}
|
|
506
|
+
function validForwardedPrefix(prefix) {
|
|
507
|
+
const trimmed = prefix.replace(/\/+$/u, "");
|
|
508
|
+
if (trimmed && (!trimmed.startsWith("/") || trimmed.startsWith("//") || /[\s?#\\]/u.test(trimmed))) {
|
|
509
|
+
throw new HoloRequestUrlError(invalidRequestPrefixMessage);
|
|
510
|
+
}
|
|
511
|
+
return trimmed;
|
|
512
|
+
}
|
|
513
|
+
function parseRequestUrl(value, invalidMessage) {
|
|
514
|
+
if (!URL.canParse(String(value), "http://localhost")) throw new HoloRequestUrlError(invalidMessage);
|
|
515
|
+
return new URL(value, "http://localhost");
|
|
516
|
+
}
|
|
517
|
+
function nestedRequest(input) {
|
|
518
|
+
return input instanceof Request ? void 0 : input.request ?? input.web?.request ?? input.req ?? input.node?.req;
|
|
519
|
+
}
|
|
520
|
+
function requestHeaders(input) {
|
|
521
|
+
return input.headers ?? nestedRequest(input)?.headers;
|
|
522
|
+
}
|
|
523
|
+
function resolveForwardedRequest(headers) {
|
|
524
|
+
return requestForwardingState().resolveForwardedRequest?.((name) => readHeader(headers, name), currentConnectionAddress());
|
|
525
|
+
}
|
|
526
|
+
function resolveUrl(input, applyPrefix) {
|
|
527
|
+
const nested = nestedRequest(input);
|
|
528
|
+
const headers = requestHeaders(input);
|
|
529
|
+
const value = input.url ?? nested?.url ?? input.path ?? (nested && "path" in nested ? nested.path : void 0) ?? "/";
|
|
530
|
+
const invalidUrlMessage = "Request URLs must use http or https without credentials.";
|
|
531
|
+
const url = parseRequestUrl(value, invalidUrlMessage);
|
|
532
|
+
if (url.username || url.password || url.protocol !== "http:" && url.protocol !== "https:") {
|
|
533
|
+
throw new HoloRequestUrlError(invalidUrlMessage);
|
|
534
|
+
}
|
|
535
|
+
const forwarded = resolveForwardedRequest(headers);
|
|
536
|
+
const protocol = (forwarded?.protocol ?? url.protocol.slice(0, -1)).toLowerCase();
|
|
537
|
+
if (protocol !== "http" && protocol !== "https") {
|
|
538
|
+
throw new HoloRequestUrlError("Request protocols must be http or https.");
|
|
539
|
+
}
|
|
540
|
+
const host = forwarded?.host ?? readHeader(headers, "host") ?? url.host;
|
|
541
|
+
if (!host || /[\s,/@\\?#]/u.test(host)) {
|
|
542
|
+
throw new HoloRequestUrlError(invalidRequestHostMessage);
|
|
543
|
+
}
|
|
544
|
+
const origin = parseRequestUrl(`${protocol}://${host}`, invalidRequestHostMessage);
|
|
545
|
+
if (origin.pathname !== "/" || origin.username || origin.password || origin.search || origin.hash) {
|
|
546
|
+
throw new HoloRequestUrlError(invalidRequestHostMessage);
|
|
547
|
+
}
|
|
548
|
+
if (forwarded?.port !== void 0) origin.port = validForwardedPort(forwarded.port);
|
|
549
|
+
url.protocol = origin.protocol;
|
|
550
|
+
url.host = origin.host;
|
|
551
|
+
url.port = origin.port;
|
|
552
|
+
const prefix = forwarded?.prefix === void 0 ? "" : validForwardedPrefix(forwarded.prefix);
|
|
553
|
+
if (applyPrefix) url.pathname = `${prefix}${url.pathname}`;
|
|
554
|
+
return url;
|
|
555
|
+
}
|
|
556
|
+
function resolveRequestUrl(input) {
|
|
557
|
+
return resolveUrl(input, true);
|
|
558
|
+
}
|
|
559
|
+
function resolveRoutedRequestUrl(input) {
|
|
560
|
+
return resolveUrl(input, false);
|
|
561
|
+
}
|
|
562
|
+
function resolveClientAddress(input) {
|
|
563
|
+
if (!requestForwardingState().resolveForwardedRequest) return currentConnectionAddress();
|
|
564
|
+
return resolveForwardedRequest(requestHeaders(input))?.address;
|
|
565
|
+
}
|
|
566
|
+
var requestUrlInternals = Object.freeze({
|
|
567
|
+
configureForwardedRequestResolver,
|
|
568
|
+
currentConnectionAddress,
|
|
569
|
+
provideConnectionAddress,
|
|
570
|
+
resolveClientAddress,
|
|
571
|
+
resolveRoutedRequestUrl
|
|
572
|
+
});
|
|
573
|
+
function hasControlCharacter(value) {
|
|
574
|
+
for (const character of value) {
|
|
575
|
+
const code = character.codePointAt(0) ?? 0;
|
|
576
|
+
if (code < 32 || code === 127) return true;
|
|
577
|
+
}
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
function sameOriginRedirectPath(path) {
|
|
581
|
+
if (!path || !path.startsWith("/") || path.startsWith("//") || path.includes("\\") || hasControlCharacter(path)) {
|
|
582
|
+
return "/";
|
|
583
|
+
}
|
|
584
|
+
return path;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/cookieEncryption.ts
|
|
588
|
+
import { createCipheriv, createDecipheriv, createHmac, randomBytes } from "crypto";
|
|
589
|
+
var IV_BYTES = 12;
|
|
590
|
+
var TAG_BYTES = 16;
|
|
591
|
+
function cookieEncryptionKey(appKey) {
|
|
592
|
+
const key = appKey?.trim();
|
|
593
|
+
if (!key) {
|
|
594
|
+
throw new Error('[@holo-js/kernel] Holo cookies are encrypted with the application key. Set APP_KEY or run "holo key:generate".');
|
|
595
|
+
}
|
|
596
|
+
return createHmac("sha256", key).update("holo-js:cookie-encryption").digest();
|
|
597
|
+
}
|
|
598
|
+
function encryptCookieValue(name, value, appKey) {
|
|
599
|
+
const iv = randomBytes(IV_BYTES);
|
|
600
|
+
const cipher = createCipheriv("aes-256-gcm", cookieEncryptionKey(appKey), iv);
|
|
601
|
+
cipher.setAAD(Buffer.from(name, "utf8"));
|
|
602
|
+
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
603
|
+
return Buffer.concat([iv, cipher.getAuthTag(), encrypted]).toString("base64url");
|
|
604
|
+
}
|
|
605
|
+
function decryptCookieValue(name, value, appKey) {
|
|
606
|
+
const key = cookieEncryptionKey(appKey);
|
|
607
|
+
const payload = Buffer.from(value, "base64url");
|
|
608
|
+
if (payload.toString("base64url") !== value) return void 0;
|
|
609
|
+
if (payload.length < IV_BYTES + TAG_BYTES) return void 0;
|
|
610
|
+
try {
|
|
611
|
+
const decipher = createDecipheriv("aes-256-gcm", key, payload.subarray(0, IV_BYTES));
|
|
612
|
+
decipher.setAAD(Buffer.from(name, "utf8"));
|
|
613
|
+
decipher.setAuthTag(payload.subarray(IV_BYTES, IV_BYTES + TAG_BYTES));
|
|
614
|
+
return Buffer.concat([decipher.update(payload.subarray(IV_BYTES + TAG_BYTES)), decipher.final()]).toString("utf8");
|
|
615
|
+
} catch {
|
|
616
|
+
return void 0;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
var cookieEncryptionInternals = Object.freeze({ decryptCookieValue, encryptCookieValue });
|
|
620
|
+
|
|
621
|
+
// src/localStoragePath.ts
|
|
622
|
+
var WINDOWS_DEVICE_NAMES = /* @__PURE__ */ new Set([
|
|
623
|
+
"CON",
|
|
624
|
+
"PRN",
|
|
625
|
+
"AUX",
|
|
626
|
+
"NUL",
|
|
627
|
+
...["COM", "LPT"].flatMap((device) => ["1", "2", "3", "4", "5", "6", "7", "8", "9", "\xB9", "\xB2", "\xB3"].map((digit) => `${device}${digit}`))
|
|
628
|
+
]);
|
|
629
|
+
function windowsStorageNameProblem(name) {
|
|
630
|
+
const hasControlCharacter2 = Array.from(name).some((character) => character.charCodeAt(0) < 32);
|
|
631
|
+
if (hasControlCharacter2 || /[<>:"|?*]/u.test(name)) {
|
|
632
|
+
return 'Windows file names cannot contain < > : " | ? * or control characters';
|
|
633
|
+
}
|
|
634
|
+
if (/[. ]$/u.test(name)) {
|
|
635
|
+
return "Windows file names cannot end with a dot or a space";
|
|
636
|
+
}
|
|
637
|
+
const baseName = (name.split(".", 1)[0] ?? "").replace(/ +$/u, "").toUpperCase();
|
|
638
|
+
if (WINDOWS_DEVICE_NAMES.has(baseName)) {
|
|
639
|
+
return "Windows reserves this device name";
|
|
640
|
+
}
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
function segmentProblem(segment) {
|
|
644
|
+
if (segment === "..") {
|
|
645
|
+
return 'Storage paths must not contain ".." segments';
|
|
646
|
+
}
|
|
647
|
+
if (segment === "" || segment === "." || /[\\/]/u.test(segment) || segment.includes("\0")) {
|
|
648
|
+
return "Storage path segments must be file or folder names";
|
|
649
|
+
}
|
|
650
|
+
return process.platform === "win32" ? windowsStorageNameProblem(segment) : null;
|
|
651
|
+
}
|
|
652
|
+
function assertValidSegment(segment) {
|
|
653
|
+
const problem = segmentProblem(segment);
|
|
654
|
+
if (problem) throw new Error(`[Holo Storage] ${problem}.`);
|
|
655
|
+
}
|
|
656
|
+
function splitLocalStoragePath(path) {
|
|
657
|
+
return path.trimStart().replace(/\\/g, "/").split("/").filter(Boolean);
|
|
658
|
+
}
|
|
659
|
+
function isReservedLocalStorageName(name) {
|
|
660
|
+
return name?.endsWith("$") === true;
|
|
661
|
+
}
|
|
662
|
+
var localStoragePathInternals = {
|
|
663
|
+
assertValidSegment,
|
|
664
|
+
isReservedLocalStorageName,
|
|
665
|
+
segmentProblem,
|
|
666
|
+
splitLocalStoragePath,
|
|
667
|
+
windowsStorageNameProblem
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// src/cookieWriteUnavailable.ts
|
|
671
|
+
var COOKIE_WRITE_UNAVAILABLE = "cookie_write_unavailable";
|
|
672
|
+
function createCookieWriteUnavailableError(message, cause) {
|
|
673
|
+
return Object.assign(new Error(message, { cause }), {
|
|
674
|
+
code: COOKIE_WRITE_UNAVAILABLE
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/retryBackoff.ts
|
|
679
|
+
function resolveRetryBackoffSeconds(backoff, attempt) {
|
|
680
|
+
if (typeof backoff === "number") {
|
|
681
|
+
return backoff;
|
|
682
|
+
}
|
|
683
|
+
return backoff[Math.min(Math.max(attempt - 1, 0), backoff.length - 1)] ?? 0;
|
|
684
|
+
}
|
|
429
685
|
export {
|
|
686
|
+
COOKIE_WRITE_UNAVAILABLE,
|
|
430
687
|
DEFAULT_HOLO_PROJECT_PATHS,
|
|
688
|
+
DEFAULT_MAX_REQUEST_BODY_BYTES,
|
|
689
|
+
DEFAULT_MAX_UPLOAD_BODY_BYTES,
|
|
431
690
|
DEFAULT_REDIS_CONNECTION,
|
|
432
691
|
DEFAULT_REDIS_DB,
|
|
433
692
|
DEFAULT_REDIS_HOST,
|
|
434
693
|
DEFAULT_REDIS_PORT,
|
|
435
694
|
HOLO_PROJECT_PREPARE_API_VERSION,
|
|
436
695
|
HoloProjectPrepareError,
|
|
696
|
+
HoloRequestBodyTooLargeError,
|
|
697
|
+
HoloRequestUrlError,
|
|
437
698
|
RuntimeLifecycle,
|
|
699
|
+
cookieEncryptionInternals,
|
|
700
|
+
createCookieWriteUnavailableError,
|
|
438
701
|
createRuntimeLifecycle,
|
|
439
702
|
defineHoloPlugin,
|
|
440
703
|
defineHoloProject,
|
|
@@ -445,10 +708,24 @@ export {
|
|
|
445
708
|
loadHoloPluginBootModules,
|
|
446
709
|
loadHoloPluginContributionModules,
|
|
447
710
|
loadHoloPluginDefinitions,
|
|
711
|
+
localStoragePathInternals,
|
|
448
712
|
normalizeHoloHttpError,
|
|
449
713
|
normalizeHoloPluginDefinition,
|
|
450
714
|
normalizeHoloProjectConfig,
|
|
451
715
|
normalizeRedisConfig,
|
|
716
|
+
readBoundedBufferedBody,
|
|
717
|
+
readBoundedCachedBody,
|
|
718
|
+
readBoundedNodeRequestBody,
|
|
719
|
+
readBoundedRequestBody,
|
|
720
|
+
readBoundedRequestFormData,
|
|
721
|
+
readBoundedRequestJson,
|
|
722
|
+
readBoundedRequestText,
|
|
723
|
+
requestBodyInternals,
|
|
724
|
+
requestBodyLimitFor,
|
|
725
|
+
requestUrlInternals,
|
|
452
726
|
resolveHoloPluginModulePath,
|
|
453
|
-
resolveNormalizedRedisConnection
|
|
727
|
+
resolveNormalizedRedisConnection,
|
|
728
|
+
resolveRequestUrl,
|
|
729
|
+
resolveRetryBackoffSeconds,
|
|
730
|
+
sameOriginRedirectPath
|
|
454
731
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare function throwCapabilityFailures(failures: readonly unknown[]): void;
|
|
2
|
+
interface ReleasableResource {
|
|
3
|
+
close?(): void | Promise<void>;
|
|
4
|
+
disconnect?(): void | Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
interface HeldResource {
|
|
7
|
+
hold<TResult>(operation: () => TResult): TResult;
|
|
8
|
+
}
|
|
9
|
+
interface ResourceLease<TValue> extends HeldResource {
|
|
10
|
+
readonly value: TValue;
|
|
11
|
+
}
|
|
12
|
+
interface ManagedResources<TConfig, TValue extends ReleasableResource> {
|
|
13
|
+
acquire(config: TConfig, create: (config: TConfig) => TValue): Promise<ResourceLease<TValue>>;
|
|
14
|
+
commit(): Promise<void>;
|
|
15
|
+
discard(): Promise<void>;
|
|
16
|
+
dispose(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
declare function createManagedResources<TConfig, TValue extends ReleasableResource>(options?: {
|
|
19
|
+
readonly connect?: (value: TValue) => void | Promise<void>;
|
|
20
|
+
}): ManagedResources<TConfig, TValue>;
|
|
21
|
+
declare function holdOperations<TTarget extends object>(target: TTarget, lease: HeldResource): TTarget;
|
|
22
|
+
|
|
23
|
+
export { createManagedResources, holdOperations, throwCapabilityFailures };
|
|
24
|
+
export type { HeldResource, ManagedResources, ReleasableResource, ResourceLease };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
reportError
|
|
3
|
+
} from "./chunk-QQ5YD7HZ.mjs";
|
|
4
|
+
|
|
5
|
+
// src/managedResources.ts
|
|
6
|
+
import { isDeepStrictEqual } from "util";
|
|
7
|
+
function throwCapabilityFailures(failures) {
|
|
8
|
+
if (failures.length === 1) throw failures[0];
|
|
9
|
+
if (failures.length > 1) throw new AggregateError(failures, "Optional capability cleanup failed.");
|
|
10
|
+
}
|
|
11
|
+
async function releaseManagedResources(adapters) {
|
|
12
|
+
const failures = [];
|
|
13
|
+
for (const adapter of [...new Set(adapters)].reverse()) {
|
|
14
|
+
try {
|
|
15
|
+
if (adapter.close) await adapter.close();
|
|
16
|
+
else await adapter.disconnect?.();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
failures.push(error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
throwCapabilityFailures(failures);
|
|
22
|
+
}
|
|
23
|
+
function createManagedResources(options = {}) {
|
|
24
|
+
let active = [];
|
|
25
|
+
let retained = [];
|
|
26
|
+
let claimed = [];
|
|
27
|
+
let opened = [];
|
|
28
|
+
let disposing = false;
|
|
29
|
+
let disposalFailures = [];
|
|
30
|
+
const releasing = /* @__PURE__ */ new Set();
|
|
31
|
+
const findSameConfig = (entries, config) => entries.find((entry) => isDeepStrictEqual(entry.config, config));
|
|
32
|
+
const track = (promise) => {
|
|
33
|
+
releasing.add(promise);
|
|
34
|
+
promise.then(() => releasing.delete(promise));
|
|
35
|
+
};
|
|
36
|
+
const reportReplacedResourceFailure = (error) => reportError(error, {
|
|
37
|
+
capability: "runtime",
|
|
38
|
+
operation: "releaseReplacedResource"
|
|
39
|
+
});
|
|
40
|
+
const startClose = (entry, onFailure) => {
|
|
41
|
+
entry.state = "closing";
|
|
42
|
+
retained = retained.filter((candidate) => candidate !== entry);
|
|
43
|
+
return releaseManagedResources([entry.value]).catch(onFailure).finally(() => entry.onDrained?.());
|
|
44
|
+
};
|
|
45
|
+
const retireEntry = (entry, onFailure) => {
|
|
46
|
+
if (entry.pending === 0) return startClose(entry, onFailure);
|
|
47
|
+
entry.state = "draining";
|
|
48
|
+
entry.onFailure = onFailure;
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
entry.onDrained = resolve;
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const finishOperation = (entry) => {
|
|
54
|
+
entry.pending -= 1;
|
|
55
|
+
if (entry.pending !== 0 || entry.state === "closing") return;
|
|
56
|
+
const awaitingClose = entry.state === "draining" || retained.includes(entry) && !claimed.includes(entry);
|
|
57
|
+
if (awaitingClose) track(startClose(entry, entry.onFailure ?? reportReplacedResourceFailure));
|
|
58
|
+
};
|
|
59
|
+
const createEntry = (config, value) => {
|
|
60
|
+
const entry = {
|
|
61
|
+
config,
|
|
62
|
+
value,
|
|
63
|
+
pending: 0,
|
|
64
|
+
state: "open",
|
|
65
|
+
lease: {
|
|
66
|
+
value,
|
|
67
|
+
hold(operation) {
|
|
68
|
+
if (entry.state === "closing") throw new Error("[@holo-js/kernel] The managed resource is closed.");
|
|
69
|
+
entry.pending += 1;
|
|
70
|
+
let result;
|
|
71
|
+
try {
|
|
72
|
+
result = operation();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
finishOperation(entry);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
if (result instanceof Promise) {
|
|
78
|
+
result.finally(() => finishOperation(entry)).catch(() => void 0);
|
|
79
|
+
} else {
|
|
80
|
+
finishOperation(entry);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return entry;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
async acquire(config, create) {
|
|
90
|
+
if (disposing) throw new Error("[@holo-js/kernel] Managed resources are disposed; acquire a new registry instead.");
|
|
91
|
+
const reusable = findSameConfig(claimed, config) ?? findSameConfig(active, config) ?? findSameConfig(retained, config);
|
|
92
|
+
if (reusable) {
|
|
93
|
+
if (!claimed.includes(reusable)) claimed.push(reusable);
|
|
94
|
+
return reusable.lease;
|
|
95
|
+
}
|
|
96
|
+
const entry = createEntry(config, create(config));
|
|
97
|
+
opened.push(entry);
|
|
98
|
+
await options.connect?.(entry.value);
|
|
99
|
+
claimed.push(entry);
|
|
100
|
+
return entry.lease;
|
|
101
|
+
},
|
|
102
|
+
async commit() {
|
|
103
|
+
const previouslyOpen = [...active, ...retained];
|
|
104
|
+
active = claimed;
|
|
105
|
+
claimed = [];
|
|
106
|
+
opened = [];
|
|
107
|
+
retained = previouslyOpen.filter((entry) => !active.includes(entry));
|
|
108
|
+
const idle = retained.filter((entry) => entry.pending === 0);
|
|
109
|
+
retained = retained.filter((entry) => entry.pending > 0);
|
|
110
|
+
await Promise.all(idle.map((entry) => retireEntry(entry, reportReplacedResourceFailure)));
|
|
111
|
+
},
|
|
112
|
+
async discard() {
|
|
113
|
+
const revivedIdle = claimed.filter((entry) => retained.includes(entry) && entry.pending === 0);
|
|
114
|
+
retained = retained.filter((entry) => !revivedIdle.includes(entry));
|
|
115
|
+
const entries = [...opened, ...revivedIdle];
|
|
116
|
+
claimed = [];
|
|
117
|
+
opened = [];
|
|
118
|
+
const failures = [];
|
|
119
|
+
await Promise.all(entries.map((entry) => retireEntry(entry, (error) => {
|
|
120
|
+
failures.push(error);
|
|
121
|
+
})));
|
|
122
|
+
throwCapabilityFailures(failures);
|
|
123
|
+
},
|
|
124
|
+
async dispose() {
|
|
125
|
+
disposing = true;
|
|
126
|
+
disposalFailures = [];
|
|
127
|
+
const entries = /* @__PURE__ */ new Set([...active, ...retained, ...claimed, ...opened]);
|
|
128
|
+
active = [];
|
|
129
|
+
retained = [];
|
|
130
|
+
claimed = [];
|
|
131
|
+
opened = [];
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
track(retireEntry(entry, (error) => {
|
|
134
|
+
disposalFailures.push(error);
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
while (releasing.size > 0) await Promise.all([...releasing]);
|
|
138
|
+
throwCapabilityFailures(disposalFailures);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function holdOperations(target, lease) {
|
|
143
|
+
return new Proxy(target, {
|
|
144
|
+
get(source, property) {
|
|
145
|
+
const value = Reflect.get(source, property, source);
|
|
146
|
+
if (typeof value !== "function") return value;
|
|
147
|
+
return (...args) => lease.hold(() => Reflect.apply(value, source, args));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
createManagedResources,
|
|
153
|
+
holdOperations,
|
|
154
|
+
throwCapabilityFailures
|
|
155
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
declare const DEFAULT_MAX_REQUEST_BODY_BYTES: number;
|
|
2
|
+
declare const DEFAULT_MAX_UPLOAD_BODY_BYTES: number;
|
|
3
|
+
declare function requestBodyLimitFor(contentType: string | null | undefined): number;
|
|
4
|
+
declare class HoloRequestBodyTooLargeError extends Error {
|
|
5
|
+
readonly status = 413;
|
|
6
|
+
constructor(limit: number);
|
|
7
|
+
}
|
|
8
|
+
declare function readBoundedRequestBody(request: Request, maxBytes?: number): Promise<Uint8Array<ArrayBuffer>>;
|
|
9
|
+
type BufferedRequestBody = {
|
|
10
|
+
readonly bytes: Uint8Array<ArrayBuffer>;
|
|
11
|
+
readonly contentType?: string;
|
|
12
|
+
};
|
|
13
|
+
declare function readBoundedCachedBody(cached: unknown, maxBytes?: number): Promise<Uint8Array<ArrayBuffer> | undefined>;
|
|
14
|
+
declare function readBoundedBufferedBody(cached: unknown, maxBytes?: number): Promise<BufferedRequestBody | undefined>;
|
|
15
|
+
declare function readBoundedRequestText(request: Request, maxBytes?: number): Promise<string>;
|
|
16
|
+
declare function readBoundedRequestJson(request: Request, maxBytes?: number): Promise<unknown>;
|
|
17
|
+
declare function readBoundedRequestFormData(request: Request, maxBytes?: number): Promise<FormData>;
|
|
18
|
+
declare function readBoundedNodeRequestBody(stream: AsyncIterable<Uint8Array | string>, maxBytes?: number, declaredLength?: string | null): Promise<Uint8Array<ArrayBuffer>>;
|
|
19
|
+
declare const requestBodyInternals: Readonly<{
|
|
20
|
+
configureMaxRequestBodyBytes(bytes: number | undefined): void;
|
|
21
|
+
configureMaxUploadBodyBytes(bytes: number | undefined): void;
|
|
22
|
+
getMaxRequestBodyBytes(): number;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
export { DEFAULT_MAX_REQUEST_BODY_BYTES, DEFAULT_MAX_UPLOAD_BODY_BYTES, HoloRequestBodyTooLargeError, readBoundedBufferedBody, readBoundedCachedBody, readBoundedNodeRequestBody, readBoundedRequestBody, readBoundedRequestFormData, readBoundedRequestJson, readBoundedRequestText, requestBodyInternals, requestBodyLimitFor };
|
|
26
|
+
export type { BufferedRequestBody };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MAX_REQUEST_BODY_BYTES,
|
|
3
|
+
DEFAULT_MAX_UPLOAD_BODY_BYTES,
|
|
4
|
+
HoloRequestBodyTooLargeError,
|
|
5
|
+
readBoundedBufferedBody,
|
|
6
|
+
readBoundedCachedBody,
|
|
7
|
+
readBoundedNodeRequestBody,
|
|
8
|
+
readBoundedRequestBody,
|
|
9
|
+
readBoundedRequestFormData,
|
|
10
|
+
readBoundedRequestJson,
|
|
11
|
+
readBoundedRequestText,
|
|
12
|
+
requestBodyInternals,
|
|
13
|
+
requestBodyLimitFor
|
|
14
|
+
} from "./chunk-CGVSGOEO.mjs";
|
|
15
|
+
export {
|
|
16
|
+
DEFAULT_MAX_REQUEST_BODY_BYTES,
|
|
17
|
+
DEFAULT_MAX_UPLOAD_BODY_BYTES,
|
|
18
|
+
HoloRequestBodyTooLargeError,
|
|
19
|
+
readBoundedBufferedBody,
|
|
20
|
+
readBoundedCachedBody,
|
|
21
|
+
readBoundedNodeRequestBody,
|
|
22
|
+
readBoundedRequestBody,
|
|
23
|
+
readBoundedRequestFormData,
|
|
24
|
+
readBoundedRequestJson,
|
|
25
|
+
readBoundedRequestText,
|
|
26
|
+
requestBodyInternals,
|
|
27
|
+
requestBodyLimitFor
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,25 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/kernel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Holo-JS Framework -
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Holo-JS Framework - contracts, plugins, and runtime lifecycle",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/cobraprojects/holo-js.git",
|
|
10
|
+
"directory": "packages/kernel"
|
|
11
|
+
},
|
|
7
12
|
"exports": {
|
|
8
13
|
".": {
|
|
9
14
|
"types": "./dist/index.d.ts",
|
|
10
15
|
"import": "./dist/index.mjs",
|
|
11
16
|
"default": "./dist/index.mjs"
|
|
12
17
|
},
|
|
18
|
+
"./error-reporting": {
|
|
19
|
+
"types": "./dist/error-reporting.d.ts",
|
|
20
|
+
"import": "./dist/error-reporting.mjs",
|
|
21
|
+
"default": "./dist/error-reporting.mjs"
|
|
22
|
+
},
|
|
13
23
|
"./http-errors": {
|
|
14
24
|
"types": "./dist/http-errors.d.ts",
|
|
15
25
|
"import": "./dist/http-errors.mjs",
|
|
16
26
|
"default": "./dist/http-errors.mjs"
|
|
27
|
+
},
|
|
28
|
+
"./managed-resources": {
|
|
29
|
+
"types": "./dist/managed-resources.d.ts",
|
|
30
|
+
"import": "./dist/managed-resources.mjs",
|
|
31
|
+
"default": "./dist/managed-resources.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./file-content": {
|
|
34
|
+
"types": "./dist/file-content.d.ts",
|
|
35
|
+
"import": "./dist/file-content.mjs",
|
|
36
|
+
"default": "./dist/file-content.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./request-body": {
|
|
39
|
+
"types": "./dist/request-body.d.ts",
|
|
40
|
+
"import": "./dist/request-body.mjs",
|
|
41
|
+
"default": "./dist/request-body.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./image-decoding": {
|
|
44
|
+
"types": "./dist/image-decoding.d.ts",
|
|
45
|
+
"browser": "./dist/image-decoding.browser.mjs",
|
|
46
|
+
"node": "./dist/image-decoding.node.mjs",
|
|
47
|
+
"default": "./dist/image-decoding.mjs"
|
|
17
48
|
}
|
|
18
49
|
},
|
|
19
50
|
"main": "./dist/index.mjs",
|
|
20
51
|
"types": "./dist/index.d.ts",
|
|
21
52
|
"files": [
|
|
22
|
-
"dist"
|
|
53
|
+
"dist",
|
|
54
|
+
"LICENSE"
|
|
23
55
|
],
|
|
24
56
|
"scripts": {
|
|
25
57
|
"build": "tsup",
|
|
@@ -27,13 +59,25 @@
|
|
|
27
59
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
28
60
|
"test": "vitest --run"
|
|
29
61
|
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"file-type": "^22.2.0"
|
|
64
|
+
},
|
|
30
65
|
"devDependencies": {
|
|
31
|
-
"@types/node": "^22.
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
66
|
+
"@types/node": "^22.20.5",
|
|
67
|
+
"sharp": "^0.35.5",
|
|
68
|
+
"tsup": "^9.0.0",
|
|
69
|
+
"typescript": "^6.0.3",
|
|
70
|
+
"vitest": "^5.0.5"
|
|
35
71
|
},
|
|
36
72
|
"engines": {
|
|
37
|
-
"node": "^22.
|
|
73
|
+
"node": "^22.18.0 || ^24.0.0 || ^26.0.0"
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"sharp": "^0.35.5"
|
|
77
|
+
},
|
|
78
|
+
"peerDependenciesMeta": {
|
|
79
|
+
"sharp": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
38
82
|
}
|
|
39
83
|
}
|