vovk 3.4.1 → 3.5.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/HttpException.d.ts +7 -0
- package/dist/HttpException.js +15 -0
- package/dist/JSONLinesResponse.d.ts +14 -0
- package/dist/JSONLinesResponse.js +59 -0
- package/dist/VovkApp.d.ts +29 -0
- package/dist/VovkApp.js +189 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +7 -0
- package/dist/client/types.d.ts +102 -0
- package/dist/client/types.js +2 -0
- package/dist/createDecorator.d.ts +6 -0
- package/dist/createDecorator.js +43 -0
- package/dist/createVovkApp.d.ts +62 -0
- package/dist/createVovkApp.js +129 -0
- package/dist/openapi/openAPIToVovkSchema/index.d.ts +1 -1
- package/dist/openapi/openAPIToVovkSchema/index.js +29 -4
- package/dist/openapi/openAPIToVovkSchema/pruneComponentsSchemas.d.ts +7 -0
- package/dist/openapi/openAPIToVovkSchema/pruneComponentsSchemas.js +51 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/config.d.ts +11 -0
- package/dist/types.d.ts +239 -0
- package/dist/types.js +66 -0
- package/dist/utils/generateStaticAPI.d.ts +4 -0
- package/dist/utils/generateStaticAPI.js +30 -0
- package/dist/utils/getSchema.d.ts +20 -0
- package/dist/utils/getSchema.js +33 -0
- package/dist/utils/parseQuery.d.ts +25 -0
- package/dist/utils/parseQuery.js +156 -0
- package/dist/utils/reqForm.d.ts +2 -0
- package/dist/utils/reqForm.js +32 -0
- package/dist/utils/reqMeta.d.ts +2 -0
- package/dist/utils/reqMeta.js +13 -0
- package/dist/utils/reqQuery.d.ts +2 -0
- package/dist/utils/reqQuery.js +10 -0
- package/dist/utils/serializeQuery.d.ts +13 -0
- package/dist/utils/serializeQuery.js +65 -0
- package/dist/utils/setHandlerSchema.d.ts +4 -0
- package/dist/utils/setHandlerSchema.js +15 -0
- package/dist/utils/withValidation.d.ts +21 -0
- package/dist/utils/withValidation.js +88 -0
- package/package.json +1 -1
- package/dist/core/compose.d.ts +0 -38
- package/dist/core/compose.js +0 -31
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { KnownAny } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Serialize a nested object (including arrays, arrays of objects, etc.)
|
|
4
|
+
* into a bracket-based query string.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* serializeQuery({ x: 'xx', y: [1, 2], z: { f: 'x' } })
|
|
8
|
+
* => "x=xx&y[0]=1&y[1]=2&z[f]=x"
|
|
9
|
+
*
|
|
10
|
+
* @param obj - The input object to be serialized
|
|
11
|
+
* @returns - A bracket-based query string (without leading "?")
|
|
12
|
+
*/
|
|
13
|
+
export default function serializeQuery(obj: Record<string, KnownAny>): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = serializeQuery;
|
|
4
|
+
/**
|
|
5
|
+
* Recursively build query parameters from an object.
|
|
6
|
+
*
|
|
7
|
+
* @param key - The query key so far (e.g. 'user', 'user[0]', 'user[0][name]')
|
|
8
|
+
* @param value - The current value to serialize
|
|
9
|
+
* @returns - An array of `key=value` strings
|
|
10
|
+
*/
|
|
11
|
+
function buildParams(key, value) {
|
|
12
|
+
if (value === null || value === undefined) {
|
|
13
|
+
return []; // skip null/undefined values entirely
|
|
14
|
+
}
|
|
15
|
+
// If value is an object or array, we need to recurse
|
|
16
|
+
if (typeof value === 'object') {
|
|
17
|
+
// Array case
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
/**
|
|
20
|
+
* We use index-based bracket notation here:
|
|
21
|
+
* e.g. for value = ['aa', 'bb'] and key = 'foo'
|
|
22
|
+
* => "foo[0]=aa&foo[1]=bb"
|
|
23
|
+
*
|
|
24
|
+
* If you prefer "foo[]=aa&foo[]=bb" style, replace:
|
|
25
|
+
* `${key}[${i}]`
|
|
26
|
+
* with:
|
|
27
|
+
* `${key}[]`
|
|
28
|
+
*/
|
|
29
|
+
return value.flatMap((v, i) => {
|
|
30
|
+
const newKey = `${key}[${i}]`;
|
|
31
|
+
return buildParams(newKey, v);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// Plain object case
|
|
35
|
+
return Object.keys(value).flatMap((k) => {
|
|
36
|
+
const newKey = `${key}[${k}]`;
|
|
37
|
+
return buildParams(newKey, value[k]);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return [`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Serialize a nested object (including arrays, arrays of objects, etc.)
|
|
44
|
+
* into a bracket-based query string.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* serializeQuery({ x: 'xx', y: [1, 2], z: { f: 'x' } })
|
|
48
|
+
* => "x=xx&y[0]=1&y[1]=2&z[f]=x"
|
|
49
|
+
*
|
|
50
|
+
* @param obj - The input object to be serialized
|
|
51
|
+
* @returns - A bracket-based query string (without leading "?")
|
|
52
|
+
*/
|
|
53
|
+
function serializeQuery(obj) {
|
|
54
|
+
if (!obj || typeof obj !== 'object')
|
|
55
|
+
return '';
|
|
56
|
+
// Collect query segments
|
|
57
|
+
const segments = [];
|
|
58
|
+
for (const key in obj) {
|
|
59
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
60
|
+
const value = obj[key];
|
|
61
|
+
segments.push(...buildParams(key, value));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return segments.join('&');
|
|
65
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { KnownAny, VovkController, VovkHandlerSchema } from '../types';
|
|
2
|
+
export declare function setHandlerSchema(h: ((...args: KnownAny[]) => KnownAny) & {
|
|
3
|
+
_getSchema?: (controller: VovkController) => Omit<VovkHandlerSchema, 'httpMethod' | 'path'>;
|
|
4
|
+
}, schema: Omit<VovkHandlerSchema, 'httpMethod' | 'path'>): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setHandlerSchema = setHandlerSchema;
|
|
4
|
+
async function setHandlerSchema(h, schema) {
|
|
5
|
+
h._getSchema = (controller) => {
|
|
6
|
+
if (!controller) {
|
|
7
|
+
throw new Error('Error setting client validators. Controller not found. Did you forget to use an HTTP decorator?');
|
|
8
|
+
}
|
|
9
|
+
const handlerName = Object.getOwnPropertyNames(controller).find((key) => controller[key]._sourceMethod === h);
|
|
10
|
+
if (!handlerName) {
|
|
11
|
+
throw new Error('Error setting client validators. Handler not found.');
|
|
12
|
+
}
|
|
13
|
+
return schema;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { VovkValidationType, type KnownAny, type VovkRequest } from '../types';
|
|
2
|
+
export declare function withValidation<T extends (req: KnownAny, params: KnownAny) => KnownAny, BODY_MODEL, QUERY_MODEL, PARAMS_MODEL, OUTPUT_MODEL, ITERATION_MODEL>({ disableServerSideValidation, skipSchemaEmission, validateEachIteration, body, query, params, output, iteration, handle, getJSONSchemaFromModel, validate, }: {
|
|
3
|
+
disableServerSideValidation?: boolean | VovkValidationType[];
|
|
4
|
+
skipSchemaEmission?: boolean | VovkValidationType[];
|
|
5
|
+
validateEachIteration?: boolean;
|
|
6
|
+
body?: BODY_MODEL;
|
|
7
|
+
query?: QUERY_MODEL;
|
|
8
|
+
params?: PARAMS_MODEL;
|
|
9
|
+
output?: OUTPUT_MODEL;
|
|
10
|
+
iteration?: ITERATION_MODEL;
|
|
11
|
+
handle: T;
|
|
12
|
+
getJSONSchemaFromModel?: (model: NonNullable<BODY_MODEL | QUERY_MODEL | PARAMS_MODEL | OUTPUT_MODEL | ITERATION_MODEL>, meta: {
|
|
13
|
+
type: VovkValidationType;
|
|
14
|
+
}) => KnownAny;
|
|
15
|
+
validate: (data: KnownAny, model: NonNullable<BODY_MODEL | QUERY_MODEL | PARAMS_MODEL | OUTPUT_MODEL | ITERATION_MODEL>, meta: {
|
|
16
|
+
type: VovkValidationType;
|
|
17
|
+
req: VovkRequest<KnownAny, KnownAny>;
|
|
18
|
+
status?: number;
|
|
19
|
+
i?: number;
|
|
20
|
+
}) => KnownAny;
|
|
21
|
+
}): T;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withValidation = withValidation;
|
|
4
|
+
const HttpException_1 = require("../HttpException");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const setHandlerSchema_1 = require("./setHandlerSchema");
|
|
7
|
+
const validationTypes = ['body', 'query', 'params', 'output', 'iteration'];
|
|
8
|
+
function withValidation({ disableServerSideValidation, skipSchemaEmission, validateEachIteration, body, query, params, output, iteration, handle, getJSONSchemaFromModel, validate, }) {
|
|
9
|
+
const disableServerSideValidationKeys = disableServerSideValidation === false
|
|
10
|
+
? []
|
|
11
|
+
: disableServerSideValidation === true
|
|
12
|
+
? validationTypes
|
|
13
|
+
: (disableServerSideValidation ?? []);
|
|
14
|
+
const skipSchemaEmissionKeys = skipSchemaEmission === false ? [] : skipSchemaEmission === true ? validationTypes : (skipSchemaEmission ?? []);
|
|
15
|
+
const outputHandler = async (req, handlerParams) => {
|
|
16
|
+
const data = await handle(req, handlerParams);
|
|
17
|
+
if (output && iteration) {
|
|
18
|
+
throw new HttpException_1.HttpException(types_1.HttpStatus.INTERNAL_SERVER_ERROR, "Output and iteration are mutually exclusive. You can't use them together.");
|
|
19
|
+
}
|
|
20
|
+
if (output && !disableServerSideValidationKeys.includes('output')) {
|
|
21
|
+
if (!data) {
|
|
22
|
+
throw new HttpException_1.HttpException(types_1.HttpStatus.INTERNAL_SERVER_ERROR, 'Output is required. You probably forgot to return something from your handler.');
|
|
23
|
+
}
|
|
24
|
+
await validate(data, output, { type: 'output', req });
|
|
25
|
+
}
|
|
26
|
+
if (iteration && !disableServerSideValidationKeys.includes('iteration')) {
|
|
27
|
+
// We assume `data` is an async iterable here; you might want to check that:
|
|
28
|
+
if (!data || typeof data[Symbol.asyncIterator] !== 'function') {
|
|
29
|
+
throw new HttpException_1.HttpException(types_1.HttpStatus.INTERNAL_SERVER_ERROR, 'Data is not an async iterable but iteration validation is defined.');
|
|
30
|
+
}
|
|
31
|
+
// Return a brand-new async generator that yields validated items
|
|
32
|
+
return (async function* () {
|
|
33
|
+
let i = 0;
|
|
34
|
+
for await (const item of data) {
|
|
35
|
+
if (validateEachIteration || i === 0) {
|
|
36
|
+
await validate(item, iteration, { type: 'iteration', req, status: 200, i });
|
|
37
|
+
}
|
|
38
|
+
i++;
|
|
39
|
+
yield item;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
}
|
|
43
|
+
else if (validateEachIteration) {
|
|
44
|
+
throw new HttpException_1.HttpException(types_1.HttpStatus.INTERNAL_SERVER_ERROR, 'validateEachIteration is set but iteration is not defined.');
|
|
45
|
+
}
|
|
46
|
+
return data;
|
|
47
|
+
};
|
|
48
|
+
const resultHandler = async (req, handlerParams) => {
|
|
49
|
+
if (body && !disableServerSideValidationKeys.includes('body')) {
|
|
50
|
+
const data = await req.json();
|
|
51
|
+
const instance = (await validate(data, body, { type: 'body', req })) ?? data;
|
|
52
|
+
// redeclare to add ability to call req.json() again
|
|
53
|
+
req.json = () => Promise.resolve(data);
|
|
54
|
+
req.vovk.body = () => Promise.resolve(instance);
|
|
55
|
+
}
|
|
56
|
+
if (query && !disableServerSideValidationKeys.includes('query')) {
|
|
57
|
+
const data = req.vovk.query();
|
|
58
|
+
const instance = (await validate(data, query, { type: 'query', req })) ?? data;
|
|
59
|
+
req.vovk.query = () => instance;
|
|
60
|
+
}
|
|
61
|
+
if (params && !disableServerSideValidationKeys.includes('params')) {
|
|
62
|
+
const data = req.vovk.params();
|
|
63
|
+
const instance = (await validate(data, params, { type: 'params', req })) ?? data;
|
|
64
|
+
req.vovk.params = () => instance;
|
|
65
|
+
}
|
|
66
|
+
return outputHandler(req, handlerParams);
|
|
67
|
+
};
|
|
68
|
+
if (getJSONSchemaFromModel) {
|
|
69
|
+
const validation = {};
|
|
70
|
+
if (body && !skipSchemaEmissionKeys.includes('body')) {
|
|
71
|
+
validation.body = getJSONSchemaFromModel(body, { type: 'body' });
|
|
72
|
+
}
|
|
73
|
+
if (query && !skipSchemaEmissionKeys.includes('query')) {
|
|
74
|
+
validation.query = getJSONSchemaFromModel(query, { type: 'query' });
|
|
75
|
+
}
|
|
76
|
+
if (params && !skipSchemaEmissionKeys.includes('params')) {
|
|
77
|
+
validation.params = getJSONSchemaFromModel(params, { type: 'params' });
|
|
78
|
+
}
|
|
79
|
+
if (output && !skipSchemaEmissionKeys.includes('output')) {
|
|
80
|
+
validation.output = getJSONSchemaFromModel(output, { type: 'output' });
|
|
81
|
+
}
|
|
82
|
+
if (iteration && !skipSchemaEmissionKeys.includes('iteration')) {
|
|
83
|
+
validation.iteration = getJSONSchemaFromModel(iteration, { type: 'iteration' });
|
|
84
|
+
}
|
|
85
|
+
(0, setHandlerSchema_1.setHandlerSchema)(resultHandler, { validation });
|
|
86
|
+
}
|
|
87
|
+
return resultHandler;
|
|
88
|
+
}
|
package/package.json
CHANGED
package/dist/core/compose.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { KnownAny } from '../types/utils.js';
|
|
2
|
-
/**
|
|
3
|
-
* Metadata stored on a handler by HTTP decorators and custom decorators when used outside decorator context (via compose).
|
|
4
|
-
*/
|
|
5
|
-
export type ComposeMetadata = {
|
|
6
|
-
httpMethod?: string;
|
|
7
|
-
path?: string;
|
|
8
|
-
options?: KnownAny;
|
|
9
|
-
decoratorAppliers?: ((controller: KnownAny, propertyKey: string) => void)[];
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Composes decorators and a handler/class into a single value.
|
|
13
|
-
*
|
|
14
|
-
* For method-level composition, decorators are stored and applied later by initSegment.
|
|
15
|
-
* For class-level composition, decorators like prefix() and cloneControllerMetadata()
|
|
16
|
-
* are applied immediately to the class in reverse order (matching stacked decorator semantics).
|
|
17
|
-
*
|
|
18
|
-
* @example Method-level
|
|
19
|
-
* ```ts
|
|
20
|
-
* static handleParams = compose(
|
|
21
|
-
* put('x/{foo}/{bar}/y'),
|
|
22
|
-
* authGuard(null),
|
|
23
|
-
* procedure({ params: z.object({ foo: z.string(), bar: z.string() }) })
|
|
24
|
-
* .handle(async (req) => req.vovk.params())
|
|
25
|
-
* );
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* @example Class-level
|
|
29
|
-
* ```ts
|
|
30
|
-
* const MyController = compose(
|
|
31
|
-
* prefix('users'),
|
|
32
|
-
* cloneControllerMetadata(),
|
|
33
|
-
* class extends ParentController {}
|
|
34
|
-
* );
|
|
35
|
-
* export default MyController;
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export declare function compose<T>(...args: [...unknown[], T]): T;
|
package/dist/core/compose.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export function compose(...args) {
|
|
2
|
-
if (args.length === 0)
|
|
3
|
-
throw new Error('compose() requires at least one argument');
|
|
4
|
-
const last = args[args.length - 1];
|
|
5
|
-
const decoratorFns = args.slice(0, -1);
|
|
6
|
-
if (typeof last !== 'function') {
|
|
7
|
-
throw new Error('The last argument to compose() must be a function, handler, or class');
|
|
8
|
-
}
|
|
9
|
-
for (const decoratorFn of decoratorFns) {
|
|
10
|
-
if (typeof decoratorFn !== 'function') {
|
|
11
|
-
throw new Error('All arguments to compose() except the last must be decorator functions');
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
// Detect class: native ES class constructors' toString() starts with "class"
|
|
15
|
-
if (last.toString().startsWith('class ')) {
|
|
16
|
-
// Apply class decorators in reverse order (bottom-up, matching stacked decorator semantics).
|
|
17
|
-
// Decorators mutate and return the same class, so .name is preserved.
|
|
18
|
-
for (let i = decoratorFns.length - 1; i >= 0; i--) {
|
|
19
|
-
decoratorFns[i](last);
|
|
20
|
-
}
|
|
21
|
-
return last;
|
|
22
|
-
}
|
|
23
|
-
// Method-level compose: store decorator appliers for deferred execution by initSegment
|
|
24
|
-
const handler = last;
|
|
25
|
-
handler._composeMetadata = handler._composeMetadata ?? {};
|
|
26
|
-
handler._composeMetadata.decoratorAppliers = handler._composeMetadata.decoratorAppliers ?? [];
|
|
27
|
-
for (const decoratorFn of decoratorFns) {
|
|
28
|
-
handler._composeMetadata.decoratorAppliers.push(decoratorFn);
|
|
29
|
-
}
|
|
30
|
-
return handler;
|
|
31
|
-
}
|