@hypequery/serve 0.23.1 → 0.24.1
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/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +40 -6
- package/dist/semantic/datasets/index.d.ts +1 -1
- package/dist/semantic/datasets/index.d.ts.map +1 -1
- package/dist/semantic/datasets/index.js +1 -1
- package/dist/utils/node-request.d.ts +34 -0
- package/dist/utils/node-request.d.ts.map +1 -0
- package/dist/utils/node-request.js +97 -0
- package/package.json +5 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,MAAM,CAAC;AAG/E,OAAO,KAAK,EAEV,YAAY,EAGZ,kBAAkB,EACnB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,MAAM,CAAC;AAG/E,OAAO,KAAK,EAEV,YAAY,EAGZ,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAmLrB,eAAO,MAAM,iBAAiB,GAC5B,SAAS,YAAY,EACrB,UAAS,kBAAuB,MAKlB,KAAK,eAAe,EAAE,KAAK,cAAc,kBAwCxD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,EACrB,UAAS,kBAAuB;;gBA0BP,OAAO,CAAC,IAAI,CAAC;EAiEvC,CAAC"}
|
package/dist/adapters/node.js
CHANGED
|
@@ -2,9 +2,15 @@ import { createServer } from "http";
|
|
|
2
2
|
import { once } from "node:events";
|
|
3
3
|
import { normalizeHeaders, parseQueryParams, parseRequestBody, serializeResponseBody, } from "./utils.js";
|
|
4
4
|
import { adapterErrorHeaders } from "../utils/error-headers.js";
|
|
5
|
+
import { declaresRequestBody, isNodeBodyConsumed, readPreParsedBody, resolveNodeRequestUrl, } from "../utils/node-request.js";
|
|
5
6
|
const DEFAULT_REQUEST_TIMEOUT = 30_000; // 30 seconds
|
|
6
7
|
const DEFAULT_BODY_LIMIT = 1_048_576; // 1 MB
|
|
7
8
|
const DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT = 10_000; // 10 seconds
|
|
9
|
+
const payloadTooLarge = () => {
|
|
10
|
+
const error = new Error("Request body too large");
|
|
11
|
+
error.code = "PAYLOAD_TOO_LARGE";
|
|
12
|
+
return error;
|
|
13
|
+
};
|
|
8
14
|
const readRequestBody = async (req, bodyLimit) => {
|
|
9
15
|
const chunks = [];
|
|
10
16
|
let totalLength = 0;
|
|
@@ -12,21 +18,49 @@ const readRequestBody = async (req, bodyLimit) => {
|
|
|
12
18
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
13
19
|
totalLength += buf.length;
|
|
14
20
|
if (bodyLimit > 0 && totalLength > bodyLimit) {
|
|
15
|
-
|
|
16
|
-
error.code = "PAYLOAD_TOO_LARGE";
|
|
17
|
-
throw error;
|
|
21
|
+
throw payloadTooLarge();
|
|
18
22
|
}
|
|
19
23
|
chunks.push(buf);
|
|
20
24
|
}
|
|
21
25
|
return Buffer.concat(chunks);
|
|
22
26
|
};
|
|
27
|
+
const resolveRequestBody = async (req, bodyLimit, contentType) => {
|
|
28
|
+
if (!isNodeBodyConsumed(req)) {
|
|
29
|
+
return parseRequestBody(await readRequestBody(req, bodyLimit), contentType);
|
|
30
|
+
}
|
|
31
|
+
// A body parser (express.json(), NestJS, koa-bodyparser, ...) already read
|
|
32
|
+
// the stream. Use what it left on req.body; it has applied its own limit.
|
|
33
|
+
const preParsed = readPreParsedBody(req, bodyLimit);
|
|
34
|
+
if (preParsed.kind === "too-large")
|
|
35
|
+
throw payloadTooLarge();
|
|
36
|
+
if (preParsed.kind === "parsed")
|
|
37
|
+
return preParsed.value;
|
|
38
|
+
if (preParsed.kind === "raw") {
|
|
39
|
+
return parseRequestBody(preParsed.buffer, contentType);
|
|
40
|
+
}
|
|
41
|
+
if (declaresRequestBody(req)) {
|
|
42
|
+
// The body is gone and nothing exposed it. Fail loudly rather than
|
|
43
|
+
// validating an empty body and returning a misleading 400.
|
|
44
|
+
throw {
|
|
45
|
+
status: 500,
|
|
46
|
+
body: {
|
|
47
|
+
error: {
|
|
48
|
+
type: "INTERNAL_SERVER_ERROR",
|
|
49
|
+
message: "The request body was consumed before the hypequery handler ran. " +
|
|
50
|
+
"Register the handler before body-parsing middleware, or set " +
|
|
51
|
+
"req.body to the parsed body (for Fastify: request.raw.body = request.body).",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
};
|
|
23
58
|
const buildServeRequest = async (req, bodyLimit) => {
|
|
24
59
|
const method = (req.method ?? "GET").toUpperCase();
|
|
25
|
-
const url = new URL(req
|
|
26
|
-
const bodyBuffer = await readRequestBody(req, bodyLimit);
|
|
60
|
+
const url = new URL(resolveNodeRequestUrl(req), "http://localhost");
|
|
27
61
|
const headers = normalizeHeaders(req.headers);
|
|
28
62
|
const contentType = headers["content-type"] ?? headers["Content-Type"];
|
|
29
|
-
const body = await
|
|
63
|
+
const body = await resolveRequestBody(req, bodyLimit, contentType);
|
|
30
64
|
return {
|
|
31
65
|
method,
|
|
32
66
|
path: url.pathname,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { dataset, dimension, measure, belongsTo, hasMany, hasOne, sum, count, countDistinct, avg, min, max, divide, multiply, subtract, add, nullIfZero, coalesce, round, floor, ceil, eq, neq, gt, gte, lt, lte, inList, notInList, between, like, asc, desc, filter, order, createDatasetRegistry, publishDatasets, } from '@hypequery/datasets';
|
|
1
|
+
export { dataset, dimension, measure, belongsTo, hasMany, hasOne, sum, count, countDistinct, avg, min, max, divide, multiply, subtract, add, nullIfZero, coalesce, round, floor, ceil, eq, neq, gt, gte, lt, lte, inList, notInList, between, like, isNull, isNotNull, asc, desc, filter, order, createDatasetRegistry, publishDatasets, } from '@hypequery/datasets';
|
|
2
2
|
export type { FieldType, DimensionType, DimensionOptions, DimensionDefinition, MeasureOptions, MeasureDefinition, InferDimensionType, RelationshipKind, RelationshipDefinition, AggregationType, MeasureAggregation, AggregationSpec, FormulaExpr, DerivedMetricSpec, TimeGrain, MetricRef, BaseMetricRef, DerivedMetricRef, GrainedMetricRef, MetricContract, MetricFilter, MetricOrderBy, MetricQuery, DatasetQuery, MetricResultMeta, MetricResult, DatasetQueryResult, MetricHandle, ExecutionContext, SemanticExecutionRuntime, SemanticTenantRuntime, SemanticFilterDefinition, SemanticFiltersDefinition, DatasetConfig, DatasetLimits, DatasetInstance, BaseMetricConfig, DerivedMetricConfig, DatasetRegistryInstance, DatasetFieldNames, DatasetPublisher, PublishableMetric, PublishedMetricHandle, PublishedMetricMap, PublishedMetrics, PublishedDataset, PublishedDatasetRegistry, PublishDatasetOptions, } from '@hypequery/datasets';
|
|
3
3
|
export { createMetricEndpoint } from './metric-endpoint.js';
|
|
4
4
|
export { createDatasetEndpoint } from './dataset-endpoint.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/semantic/datasets/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EACP,SAAS,EACT,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,GAAG,EACH,KAAK,EACL,aAAa,EACb,GAAG,EACH,GAAG,EACH,GAAG,EACH,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,UAAU,EACV,QAAQ,EACR,KAAK,EACL,KAAK,EACL,IAAI,EACJ,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,MAAM,EACN,SAAS,EACT,OAAO,EACP,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,MAAM,EACN,KAAK,EACL,qBAAqB,EACrB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACb,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/semantic/datasets/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EACP,SAAS,EACT,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,GAAG,EACH,KAAK,EACL,aAAa,EACb,GAAG,EACH,GAAG,EACH,GAAG,EACH,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,UAAU,EACV,QAAQ,EACR,KAAK,EACL,KAAK,EACL,IAAI,EACJ,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,MAAM,EACN,SAAS,EACT,OAAO,EACP,IAAI,EACJ,MAAM,EACN,SAAS,EACT,GAAG,EACH,IAAI,EACJ,MAAM,EACN,KAAK,EACL,qBAAqB,EACrB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACb,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Re-export public datasets APIs for convenience
|
|
2
|
-
export { dataset, dimension, measure, belongsTo, hasMany, hasOne, sum, count, countDistinct, avg, min, max, divide, multiply, subtract, add, nullIfZero, coalesce, round, floor, ceil, eq, neq, gt, gte, lt, lte, inList, notInList, between, like, asc, desc, filter, order, createDatasetRegistry, publishDatasets, } from '@hypequery/datasets';
|
|
2
|
+
export { dataset, dimension, measure, belongsTo, hasMany, hasOne, sum, count, countDistinct, avg, min, max, divide, multiply, subtract, add, nullIfZero, coalesce, round, floor, ceil, eq, neq, gt, gte, lt, lte, inList, notInList, between, like, isNull, isNotNull, asc, desc, filter, order, createDatasetRegistry, publishDatasets, } from '@hypequery/datasets';
|
|
3
3
|
// Serve-specific endpoint integration
|
|
4
4
|
export { createMetricEndpoint } from './metric-endpoint.js';
|
|
5
5
|
export { createDatasetEndpoint } from './dataset-endpoint.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IncomingMessage } from "http";
|
|
2
|
+
/**
|
|
3
|
+
* The URL the client actually requested. Express and Connect strip the mount
|
|
4
|
+
* prefix from `req.url` inside `app.use('/prefix', handler)` but keep the full
|
|
5
|
+
* URL in `req.originalUrl`; routing needs the full path to match `basePath`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const resolveNodeRequestUrl: (req: IncomingMessage) => string;
|
|
8
|
+
/**
|
|
9
|
+
* True when something upstream (a body parser) has already read the request
|
|
10
|
+
* stream, so reading it again would yield nothing.
|
|
11
|
+
*/
|
|
12
|
+
export declare const isNodeBodyConsumed: (req: IncomingMessage) => boolean;
|
|
13
|
+
/** True when the request headers announce a body. */
|
|
14
|
+
export declare const declaresRequestBody: (req: IncomingMessage) => boolean;
|
|
15
|
+
export type PreParsedBody = {
|
|
16
|
+
kind: "none";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "too-large";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "raw";
|
|
21
|
+
buffer: Buffer;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "parsed";
|
|
24
|
+
value: unknown;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The body an upstream parser left on `req.body`, held to *bodyLimit* bytes
|
|
28
|
+
* (0 for none) before any of it is parsed. Buffers and strings
|
|
29
|
+
* (express.raw(), express.text()) are raw bytes for the adapter's own
|
|
30
|
+
* content-type parsing, except a string on a JSON request that a JSON parser
|
|
31
|
+
* already decoded (`readJsonString`); anything else is used as-is.
|
|
32
|
+
*/
|
|
33
|
+
export declare const readPreParsedBody: (req: IncomingMessage, bodyLimit?: number) => PreParsedBody;
|
|
34
|
+
//# sourceMappingURL=node-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-request.d.ts","sourceRoot":"","sources":["../../src/utils/node-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAc5C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,eAAe,KAAG,MAM5D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,eAAe,KAAG,OACK,CAAC;AAEhE,qDAAqD;AACrD,eAAO,MAAM,mBAAmB,GAAI,KAAK,eAAe,KAAG,OAI1D,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAqDvC;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,eAAe,EAAE,kBAAa,KAAG,aAgBvE,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The URL the client actually requested. Express and Connect strip the mount
|
|
3
|
+
* prefix from `req.url` inside `app.use('/prefix', handler)` but keep the full
|
|
4
|
+
* URL in `req.originalUrl`; routing needs the full path to match `basePath`.
|
|
5
|
+
*/
|
|
6
|
+
export const resolveNodeRequestUrl = (req) => {
|
|
7
|
+
const { originalUrl } = req;
|
|
8
|
+
if (typeof originalUrl === "string" && originalUrl.length > 0) {
|
|
9
|
+
return originalUrl;
|
|
10
|
+
}
|
|
11
|
+
return req.url ?? "/";
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* True when something upstream (a body parser) has already read the request
|
|
15
|
+
* stream, so reading it again would yield nothing.
|
|
16
|
+
*/
|
|
17
|
+
export const isNodeBodyConsumed = (req) => req.readableEnded || req._body === true;
|
|
18
|
+
/** True when the request headers announce a body. */
|
|
19
|
+
export const declaresRequestBody = (req) => {
|
|
20
|
+
if (req.headers["transfer-encoding"] !== undefined)
|
|
21
|
+
return true;
|
|
22
|
+
const length = Number(req.headers["content-length"]);
|
|
23
|
+
return Number.isFinite(length) && length > 0;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* How to read a string `req.body` on a JSON request: as raw text, or as a JSON
|
|
27
|
+
* value a parser already decoded (`express.json({ strict: false })` turns
|
|
28
|
+
* `"123"` into the string `123`) and must not be parsed again.
|
|
29
|
+
*
|
|
30
|
+
* Raw text has exactly the byte length the client declared, and a decoded
|
|
31
|
+
* JSON string is at least two bytes shorter because its quotes are gone, so
|
|
32
|
+
* the lengths decide whenever they can be compared. A chunked body declares no
|
|
33
|
+
* length, and a compressed one declares the compressed length; then the text
|
|
34
|
+
* decides: raw text that is a JSON object or array is what an endpoint takes
|
|
35
|
+
* (and is returned already parsed), while a decoded string is kept as a
|
|
36
|
+
* string, so `"123"` never becomes `123`.
|
|
37
|
+
*
|
|
38
|
+
* Supported limitation: without a comparable length, raw text that is a bare
|
|
39
|
+
* JSON primitive (`express.text()` given `123`, `true` or `null`) cannot be
|
|
40
|
+
* told from a decoded string and stays a string. An endpoint taking a
|
|
41
|
+
* primitive behind `express.text()` should receive a declared-length body, or
|
|
42
|
+
* sit behind `express.json()` instead.
|
|
43
|
+
*/
|
|
44
|
+
const readJsonString = (req, body, byteLength) => {
|
|
45
|
+
const encoding = req.headers["content-encoding"];
|
|
46
|
+
const declared = req.headers["content-length"];
|
|
47
|
+
const comparable = (encoding === undefined || encoding.trim().toLowerCase() === "identity") &&
|
|
48
|
+
declared !== undefined &&
|
|
49
|
+
/^\d+$/.test(declared);
|
|
50
|
+
if (comparable) {
|
|
51
|
+
return Number(declared) === byteLength
|
|
52
|
+
? { kind: "raw", buffer: Buffer.from(body, "utf8") }
|
|
53
|
+
: { kind: "parsed", value: body };
|
|
54
|
+
}
|
|
55
|
+
const value = parseJsonObjectText(body);
|
|
56
|
+
return { kind: "parsed", value: value === undefined ? body : value };
|
|
57
|
+
};
|
|
58
|
+
/** The object or array *text* encodes as JSON, or undefined for anything else. */
|
|
59
|
+
const parseJsonObjectText = (text) => {
|
|
60
|
+
const first = text.trimStart()[0];
|
|
61
|
+
if (first !== "{" && first !== "[")
|
|
62
|
+
return undefined;
|
|
63
|
+
try {
|
|
64
|
+
const value = JSON.parse(text);
|
|
65
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The body an upstream parser left on `req.body`, held to *bodyLimit* bytes
|
|
73
|
+
* (0 for none) before any of it is parsed. Buffers and strings
|
|
74
|
+
* (express.raw(), express.text()) are raw bytes for the adapter's own
|
|
75
|
+
* content-type parsing, except a string on a JSON request that a JSON parser
|
|
76
|
+
* already decoded (`readJsonString`); anything else is used as-is.
|
|
77
|
+
*/
|
|
78
|
+
export const readPreParsedBody = (req, bodyLimit = 0) => {
|
|
79
|
+
const { body } = req;
|
|
80
|
+
if (body === undefined)
|
|
81
|
+
return { kind: "none" };
|
|
82
|
+
if (Buffer.isBuffer(body)) {
|
|
83
|
+
return bodyLimit > 0 && body.length > bodyLimit
|
|
84
|
+
? { kind: "too-large" }
|
|
85
|
+
: { kind: "raw", buffer: body };
|
|
86
|
+
}
|
|
87
|
+
if (typeof body !== "string")
|
|
88
|
+
return { kind: "parsed", value: body };
|
|
89
|
+
const byteLength = Buffer.byteLength(body, "utf8");
|
|
90
|
+
if (bodyLimit > 0 && byteLength > bodyLimit)
|
|
91
|
+
return { kind: "too-large" };
|
|
92
|
+
const contentType = req.headers["content-type"];
|
|
93
|
+
if (typeof contentType === "string" && contentType.includes("application/json")) {
|
|
94
|
+
return readJsonString(req, body, byteLength);
|
|
95
|
+
}
|
|
96
|
+
return { kind: "raw", buffer: Buffer.from(body, "utf8") };
|
|
97
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/serve",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Code-first TypeScript runtime for validated ClickHouse analytics APIs, OpenAPI, auth, and tenancy",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"clickhouse",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"openapi-typescript": "^7.13.0",
|
|
39
39
|
"zod": "^4.6.6",
|
|
40
40
|
"zod-to-json-schema": "^3.25.2",
|
|
41
|
-
"@hypequery/datasets": "^0.
|
|
41
|
+
"@hypequery/datasets": "^0.27.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"tsx": "^4.0.0",
|
|
@@ -53,8 +53,11 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
+
"@types/express": "^5.0.6",
|
|
56
57
|
"@types/node": "^20.11.30",
|
|
57
58
|
"@vitest/coverage-v8": "^5.0.5",
|
|
59
|
+
"express": "^5.2.1",
|
|
60
|
+
"fastify": "^5.12.7",
|
|
58
61
|
"tsx": "^4.23.15",
|
|
59
62
|
"typescript": "^6.0.3",
|
|
60
63
|
"vitest": "^5.0.5"
|