vovk 3.5.1 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/create-rpc.d.ts +13 -0
- package/dist/client/create-rpc.js +147 -0
- package/dist/client/default-handler.d.ts +6 -0
- package/dist/client/default-handler.js +25 -0
- package/dist/client/default-stream-handler.d.ts +16 -0
- package/dist/client/default-stream-handler.js +282 -0
- package/dist/client/fetcher.d.ts +1 -1
- package/dist/client/fetcher.js +2 -2
- package/dist/client/serialize-query.d.ts +13 -0
- package/dist/client/serialize-query.js +62 -0
- package/dist/core/apply-decorator-adapter.d.ts +7 -0
- package/dist/core/apply-decorator-adapter.js +50 -0
- package/dist/core/controllers-to-static-params.d.ts +13 -0
- package/dist/core/controllers-to-static-params.js +32 -0
- package/dist/core/create-decorator.d.ts +12 -0
- package/dist/core/create-decorator.js +52 -0
- package/dist/core/decorators.js +4 -4
- package/dist/core/get-schema.d.ts +21 -0
- package/dist/core/get-schema.js +31 -0
- package/dist/core/http-exception.d.ts +16 -0
- package/dist/core/http-exception.js +26 -0
- package/dist/core/init-segment.d.ts +33 -0
- package/dist/core/init-segment.js +62 -0
- package/dist/core/json-lines-responder.d.ts +42 -0
- package/dist/core/json-lines-responder.js +94 -0
- package/dist/core/resolve-generator-config-values.d.ts +19 -0
- package/dist/core/resolve-generator-config-values.js +59 -0
- package/dist/core/set-handler-schema.d.ts +4 -0
- package/dist/core/set-handler-schema.js +12 -0
- package/dist/core/to-download-response.d.ts +11 -0
- package/dist/core/to-download-response.js +25 -0
- package/dist/core/vovk-app.d.ts +36 -0
- package/dist/core/vovk-app.js +318 -0
- package/dist/index.d.ts +10 -10
- package/dist/index.js +10 -10
- package/dist/internal.d.ts +10 -10
- package/dist/internal.js +9 -9
- package/dist/openapi/error.js +1 -1
- package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.d.ts +23 -0
- package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.js +90 -0
- package/dist/openapi/openapi-to-vovk-schema/index.d.ts +5 -0
- package/dist/openapi/openapi-to-vovk-schema/index.js +179 -0
- package/dist/openapi/openapi-to-vovk-schema/inline-refs.d.ts +9 -0
- package/dist/openapi/openapi-to-vovk-schema/inline-refs.js +99 -0
- package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.d.ts +7 -0
- package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.js +51 -0
- package/dist/openapi/operation.js +1 -1
- package/dist/openapi/tool.js +1 -1
- package/dist/openapi/vovk-schema-to-openapi.d.ts +21 -0
- package/dist/openapi/vovk-schema-to-openapi.js +250 -0
- package/dist/req/buffer-body.d.ts +1 -0
- package/dist/req/buffer-body.js +30 -0
- package/dist/req/parse-body.d.ts +4 -0
- package/dist/req/parse-body.js +49 -0
- package/dist/req/parse-form.d.ts +1 -0
- package/dist/req/parse-form.js +24 -0
- package/dist/req/parse-query.d.ts +24 -0
- package/dist/req/parse-query.js +156 -0
- package/dist/req/req-meta.d.ts +2 -0
- package/dist/req/req-meta.js +10 -0
- package/dist/req/req-query.d.ts +2 -0
- package/dist/req/req-query.js +4 -0
- package/dist/req/validate-content-type.d.ts +1 -0
- package/dist/req/validate-content-type.js +32 -0
- package/dist/samples/create-code-samples.d.ts +20 -0
- package/dist/samples/create-code-samples.js +293 -0
- package/dist/samples/object-to-code.d.ts +8 -0
- package/dist/samples/object-to-code.js +38 -0
- package/dist/samples/schema-to-code.d.ts +11 -0
- package/dist/samples/schema-to-code.js +264 -0
- package/dist/samples/schema-to-object.d.ts +2 -0
- package/dist/samples/schema-to-object.js +164 -0
- package/dist/samples/schema-to-ts-type.d.ts +2 -0
- package/dist/samples/schema-to-ts-type.js +114 -0
- package/dist/tools/create-tool-factory.d.ts +135 -0
- package/dist/tools/create-tool-factory.js +62 -0
- package/dist/tools/create-tool.d.ts +126 -0
- package/dist/tools/create-tool.js +6 -0
- package/dist/tools/derive-tools.d.ts +46 -0
- package/dist/tools/derive-tools.js +131 -0
- package/dist/tools/to-model-output-default.d.ts +7 -0
- package/dist/tools/to-model-output-default.js +7 -0
- package/dist/tools/to-model-output-mcp.d.ts +30 -0
- package/dist/tools/to-model-output-mcp.js +54 -0
- package/dist/tools/to-model-output.d.ts +8 -0
- package/dist/tools/to-model-output.js +10 -0
- package/dist/types/client.d.ts +3 -3
- package/dist/types/core.d.ts +1 -1
- package/dist/types/inference.d.ts +1 -1
- package/dist/types/validation.d.ts +1 -1
- package/dist/utils/camel-case.d.ts +6 -0
- package/dist/utils/camel-case.js +34 -0
- package/dist/utils/deep-extend.d.ts +54 -0
- package/dist/utils/deep-extend.js +127 -0
- package/dist/utils/file-name-to-disposition.d.ts +1 -0
- package/dist/utils/file-name-to-disposition.js +3 -0
- package/dist/utils/to-kebab-case.d.ts +1 -0
- package/dist/utils/to-kebab-case.js +5 -0
- package/dist/utils/trim-path.d.ts +1 -0
- package/dist/utils/trim-path.js +1 -0
- package/dist/utils/upper-first.d.ts +1 -0
- package/dist/utils/upper-first.js +3 -0
- package/dist/validation/create-standard-validation.d.ts +268 -0
- package/dist/validation/create-standard-validation.js +45 -0
- package/dist/validation/create-validate-on-client.d.ts +14 -0
- package/dist/validation/create-validate-on-client.js +23 -0
- package/dist/validation/procedure.d.ts +24 -24
- package/dist/validation/procedure.js +1 -1
- package/dist/validation/validation-schemas-object-to-single-validation-schema.d.ts +17 -0
- package/dist/validation/validation-schemas-object-to-single-validation-schema.js +92 -0
- package/dist/validation/with-validation-library.d.ts +119 -0
- package/dist/validation/with-validation-library.js +184 -0
- package/package.json +13 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates static API of the given controllers for a static segment.
|
|
3
|
+
* @see https://vovk.dev/segment
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* export function generateStaticParams() {
|
|
7
|
+
* return controllersToStaticParams(controllers);
|
|
8
|
+
* }
|
|
9
|
+
*/
|
|
10
|
+
export function controllersToStaticParams(c, slug = 'vovk') {
|
|
11
|
+
const controllers = c;
|
|
12
|
+
return [
|
|
13
|
+
{ [slug]: ['_schema_'] },
|
|
14
|
+
...Object.values(controllers).flatMap((controller) => {
|
|
15
|
+
const handlers = controller._handlers;
|
|
16
|
+
const splitPrefix = controller.prefix?.split('/') ?? [];
|
|
17
|
+
return Object.entries(handlers ?? {}).flatMap(([name, handler]) => {
|
|
18
|
+
const staticParams = controller._handlersMetadata?.[name]?.staticParams;
|
|
19
|
+
if (staticParams?.length) {
|
|
20
|
+
return staticParams.map((paramsItem) => {
|
|
21
|
+
let path = handler.path;
|
|
22
|
+
for (const [key, value] of Object.entries(paramsItem ?? {})) {
|
|
23
|
+
path = path.replace(`{${key}}`, value);
|
|
24
|
+
}
|
|
25
|
+
return { [slug]: [...splitPrefix, ...path.split('/')].filter(Boolean) };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return [{ [slug]: [...splitPrefix, ...handler.path.split('/')].filter(Boolean) }];
|
|
29
|
+
});
|
|
30
|
+
}),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VovkHandlerSchema, VovkController } from '../types/core.js';
|
|
2
|
+
import type { VovkRequest } from '../types/request.js';
|
|
3
|
+
import type { KnownAny } from '../types/utils.js';
|
|
4
|
+
type Next = () => Promise<unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Creates a custom decorator for Vovk controllers.
|
|
7
|
+
* @see https://vovk.dev/decorator
|
|
8
|
+
*/
|
|
9
|
+
export declare function createDecorator<TArgs extends unknown[], TRequest = VovkRequest>(handler: null | ((this: VovkController, req: TRequest, next: Next, ...args: TArgs) => unknown), initHandler?: (this: VovkController, ...args: TArgs) => Omit<VovkHandlerSchema, 'path' | 'httpMethod'> | ((handlerSchema: VovkHandlerSchema | null, options: {
|
|
10
|
+
handlerName: string;
|
|
11
|
+
}) => Omit<Partial<VovkHandlerSchema>, 'path' | 'httpMethod'>) | null | undefined): (...args: TArgs) => (target: KnownAny, propertyKeyOrContext?: unknown) => KnownAny;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { applyDecoratorAdapter } from './apply-decorator-adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a custom decorator for Vovk controllers.
|
|
4
|
+
* @see https://vovk.dev/decorator
|
|
5
|
+
*/
|
|
6
|
+
export function createDecorator(handler, initHandler) {
|
|
7
|
+
return function decoratorCreator(...args) {
|
|
8
|
+
return function decorator(target, propertyKeyOrContext) {
|
|
9
|
+
return applyDecoratorAdapter(target, propertyKeyOrContext, applyDecorator);
|
|
10
|
+
function applyDecorator(controller, propertyKey) {
|
|
11
|
+
const originalMethod = controller[propertyKey];
|
|
12
|
+
if (typeof originalMethod !== 'function') {
|
|
13
|
+
throw new Error(`Unable to decorate: ${propertyKey} is not a function`);
|
|
14
|
+
}
|
|
15
|
+
const sourceMethod = originalMethod._sourceMethod ?? originalMethod;
|
|
16
|
+
const method = function method(req, params) {
|
|
17
|
+
const next = async () => {
|
|
18
|
+
return await originalMethod.call(controller, req, params);
|
|
19
|
+
};
|
|
20
|
+
return handler ? handler.call(controller, req, next, ...args) : next();
|
|
21
|
+
};
|
|
22
|
+
controller[propertyKey] = method;
|
|
23
|
+
method._controller = controller;
|
|
24
|
+
method._sourceMethod = sourceMethod;
|
|
25
|
+
method.fn = originalMethod.fn;
|
|
26
|
+
method.definition = originalMethod.definition;
|
|
27
|
+
sourceMethod.wrapper = method;
|
|
28
|
+
// TODO define internal method type
|
|
29
|
+
originalMethod._controller = controller;
|
|
30
|
+
const handlerSchema = controller._handlers?.[propertyKey] ?? null;
|
|
31
|
+
const initResultReturn = initHandler?.call(controller, ...args);
|
|
32
|
+
const initResult = typeof initResultReturn === 'function'
|
|
33
|
+
? initResultReturn(handlerSchema, {
|
|
34
|
+
handlerName: propertyKey,
|
|
35
|
+
})
|
|
36
|
+
: initResultReturn;
|
|
37
|
+
const methodSchema = {
|
|
38
|
+
...handlerSchema,
|
|
39
|
+
// avoid override of path and httpMethod
|
|
40
|
+
...(initResult?.validation ? { validation: initResult.validation } : {}),
|
|
41
|
+
...(initResult?.operationObject ? { operationObject: initResult.operationObject } : {}),
|
|
42
|
+
...(initResult?.misc ? { misc: initResult.misc } : {}),
|
|
43
|
+
};
|
|
44
|
+
method.schema = methodSchema;
|
|
45
|
+
controller._handlers = {
|
|
46
|
+
...controller._handlers,
|
|
47
|
+
[propertyKey]: methodSchema,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
}
|
package/dist/core/decorators.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { vovkApp } from './
|
|
2
|
-
import { trimPath } from '../utils/
|
|
3
|
-
import { toKebabCase } from '../utils/
|
|
4
|
-
import { applyDecoratorAdapter } from './
|
|
1
|
+
import { vovkApp } from './vovk-app.js';
|
|
2
|
+
import { trimPath } from '../utils/trim-path.js';
|
|
3
|
+
import { toKebabCase } from '../utils/to-kebab-case.js';
|
|
4
|
+
import { applyDecoratorAdapter } from './apply-decorator-adapter.js';
|
|
5
5
|
import { HttpMethod } from '../types/enums.js';
|
|
6
6
|
const isClass = (func) => typeof func === 'function' && /class/.test(func.toString());
|
|
7
7
|
const assignSchema = ({ controller, propertyKey, path, options, httpMethod, }) => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { VovkSegmentSchema, VovkController } from '../types/core.js';
|
|
2
|
+
import type { StaticClass } from '../types/utils.js';
|
|
3
|
+
export declare function getControllerSchema(controller: VovkController, rpcModuleName: string, exposeValidation: boolean): Promise<{
|
|
4
|
+
rpcModuleName: string;
|
|
5
|
+
originalControllerName: string;
|
|
6
|
+
prefix: string;
|
|
7
|
+
handlers: {
|
|
8
|
+
[k: string]: {
|
|
9
|
+
path: string;
|
|
10
|
+
httpMethod: string;
|
|
11
|
+
operationObject?: import("../internal.js").VovkOperationObject;
|
|
12
|
+
misc?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
}>;
|
|
16
|
+
export declare function getSchema(options: {
|
|
17
|
+
emitSchema?: boolean;
|
|
18
|
+
segmentName?: string;
|
|
19
|
+
controllers: Record<string, StaticClass>;
|
|
20
|
+
exposeValidation?: boolean;
|
|
21
|
+
}): Promise<VovkSegmentSchema>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { VovkSchemaIdEnum } from '../types/enums.js';
|
|
2
|
+
export async function getControllerSchema(controller, rpcModuleName, exposeValidation) {
|
|
3
|
+
const handlers = exposeValidation
|
|
4
|
+
? (controller._handlers ?? {})
|
|
5
|
+
: Object.fromEntries(
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
|
+
Object.entries(controller._handlers ?? {}).map(([key, { validation: _v, ...value }]) => [key, value]));
|
|
8
|
+
return {
|
|
9
|
+
rpcModuleName,
|
|
10
|
+
originalControllerName: controller.name,
|
|
11
|
+
prefix: controller.prefix ?? '',
|
|
12
|
+
handlers,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export async function getSchema(options) {
|
|
16
|
+
const exposeValidation = options?.exposeValidation ?? true;
|
|
17
|
+
const emitSchema = options.emitSchema ?? true;
|
|
18
|
+
const schema = {
|
|
19
|
+
$schema: VovkSchemaIdEnum.SEGMENT,
|
|
20
|
+
emitSchema,
|
|
21
|
+
segmentName: options.segmentName ?? '',
|
|
22
|
+
segmentType: 'segment',
|
|
23
|
+
controllers: {},
|
|
24
|
+
};
|
|
25
|
+
if (!emitSchema)
|
|
26
|
+
return schema;
|
|
27
|
+
for (const [rpcModuleName, controller] of Object.entries(options.controllers ?? {})) {
|
|
28
|
+
schema.controllers[rpcModuleName] = await getControllerSchema(controller, rpcModuleName, exposeValidation);
|
|
29
|
+
}
|
|
30
|
+
return schema;
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VovkErrorResponse } from '../types/core.js';
|
|
2
|
+
import type { HttpStatus } from '../types/enums.js';
|
|
3
|
+
/**
|
|
4
|
+
* Represents an HTTP exception with a status code and message.
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* throw new HttpException(HttpStatus.BAD_REQUEST, 'Invalid request data');
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export declare class HttpException extends Error {
|
|
11
|
+
statusCode: HttpStatus;
|
|
12
|
+
message: string;
|
|
13
|
+
cause?: unknown;
|
|
14
|
+
constructor(statusCode: HttpStatus, message: string, cause?: unknown);
|
|
15
|
+
toJSON(): VovkErrorResponse;
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents an HTTP exception with a status code and message.
|
|
3
|
+
* @example
|
|
4
|
+
* ```ts
|
|
5
|
+
* throw new HttpException(HttpStatus.BAD_REQUEST, 'Invalid request data');
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
export class HttpException extends Error {
|
|
9
|
+
statusCode;
|
|
10
|
+
message;
|
|
11
|
+
cause;
|
|
12
|
+
constructor(statusCode, message, cause) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.message = message;
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
}
|
|
18
|
+
toJSON() {
|
|
19
|
+
return {
|
|
20
|
+
isError: true,
|
|
21
|
+
statusCode: this.statusCode,
|
|
22
|
+
message: this.message,
|
|
23
|
+
...(this.cause ? { cause: this.cause } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { VovkRequest } from '../types/request.js';
|
|
2
|
+
import type { StaticClass } from '../types/utils.js';
|
|
3
|
+
export declare const initSegment: (options: {
|
|
4
|
+
segmentName?: string;
|
|
5
|
+
controllers: Record<string, StaticClass>;
|
|
6
|
+
exposeValidation?: boolean;
|
|
7
|
+
emitSchema?: boolean;
|
|
8
|
+
onError?: (err: Error, req: VovkRequest) => void | Promise<void>;
|
|
9
|
+
onSuccess?: (resp: unknown, req: VovkRequest) => void | Promise<void>;
|
|
10
|
+
onBefore?: (req: VovkRequest) => void | Promise<void>;
|
|
11
|
+
}) => {
|
|
12
|
+
GET: (req: Request, data: {
|
|
13
|
+
params: Promise<Record<string, string[]>>;
|
|
14
|
+
}) => Promise<Response>;
|
|
15
|
+
POST: (req: Request, data: {
|
|
16
|
+
params: Promise<Record<string, string[]>>;
|
|
17
|
+
}) => Promise<Response>;
|
|
18
|
+
PUT: (req: Request, data: {
|
|
19
|
+
params: Promise<Record<string, string[]>>;
|
|
20
|
+
}) => Promise<Response>;
|
|
21
|
+
PATCH: (req: Request, data: {
|
|
22
|
+
params: Promise<Record<string, string[]>>;
|
|
23
|
+
}) => Promise<Response>;
|
|
24
|
+
DELETE: (req: Request, data: {
|
|
25
|
+
params: Promise<Record<string, string[]>>;
|
|
26
|
+
}) => Promise<Response>;
|
|
27
|
+
HEAD: (req: Request, data: {
|
|
28
|
+
params: Promise<Record<string, string[]>>;
|
|
29
|
+
}) => Promise<Response>;
|
|
30
|
+
OPTIONS: (req: Request, data: {
|
|
31
|
+
params: Promise<Record<string, string[]>>;
|
|
32
|
+
}) => Promise<Response>;
|
|
33
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { vovkApp } from './vovk-app.js';
|
|
2
|
+
import { trimPath } from '../utils/trim-path.js';
|
|
3
|
+
import { getSchema } from './get-schema.js';
|
|
4
|
+
export const initSegment = (options) => {
|
|
5
|
+
const segmentName = trimPath(options.segmentName ?? '');
|
|
6
|
+
options.segmentName = segmentName;
|
|
7
|
+
const controllerEntries = Object.entries(options.controllers ?? {});
|
|
8
|
+
const controllerSet = new Set(controllerEntries.map(([, c]) => c));
|
|
9
|
+
// Sort so parent controllers are initialized before their children
|
|
10
|
+
controllerEntries.sort(([, a], [, b]) => {
|
|
11
|
+
return (Number(controllerSet.has(Object.getPrototypeOf(a))) -
|
|
12
|
+
Number(controllerSet.has(Object.getPrototypeOf(b))));
|
|
13
|
+
});
|
|
14
|
+
for (const [rpcModuleName, controller] of controllerEntries) {
|
|
15
|
+
controller._segmentName = segmentName;
|
|
16
|
+
controller._rpcModuleName = rpcModuleName;
|
|
17
|
+
controller._onError = options?.onError;
|
|
18
|
+
controller._onSuccess = options?.onSuccess;
|
|
19
|
+
controller._onBefore = options?.onBefore;
|
|
20
|
+
// Apply deferred decorate() decorator appliers in reverse order (bottom-up, matching stacked decorator semantics)
|
|
21
|
+
for (const key of Object.getOwnPropertyNames(controller)) {
|
|
22
|
+
const appliers = controller[key]?._decorateMetadata
|
|
23
|
+
?.decoratorAppliers;
|
|
24
|
+
if (appliers) {
|
|
25
|
+
for (let i = appliers.length - 1; i >= 0; i--) {
|
|
26
|
+
appliers[i](controller, key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Re-clone metadata if this controller extends another registered controller
|
|
31
|
+
// (cloneControllerMetadata() runs at class-definition time, before decorate() metadata is applied)
|
|
32
|
+
const parent = Object.getPrototypeOf(controller);
|
|
33
|
+
if (controllerSet.has(parent) && parent._handlers) {
|
|
34
|
+
controller._handlers = { ...parent._handlers, ...controller._handlers };
|
|
35
|
+
controller._handlersMetadata = { ...parent._handlersMetadata, ...controller._handlersMetadata };
|
|
36
|
+
for (const methods of Object.values(vovkApp.routes)) {
|
|
37
|
+
methods.set(controller, { ...(methods.get(parent) ?? {}), ...methods.get(controller) });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function GET_DEV(req, data) {
|
|
42
|
+
const params = await data.params;
|
|
43
|
+
if (params[Object.keys(params)[0]]?.[0] === '_schema_') {
|
|
44
|
+
const schema = await getSchema(options);
|
|
45
|
+
return vovkApp.respond({
|
|
46
|
+
req,
|
|
47
|
+
statusCode: 200,
|
|
48
|
+
responseBody: { schema },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return vovkApp.GET(req, data, segmentName);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
GET: process.env.NODE_ENV === 'development' ? GET_DEV : (req, data) => vovkApp.GET(req, data, segmentName),
|
|
55
|
+
POST: (req, data) => vovkApp.POST(req, data, segmentName),
|
|
56
|
+
PUT: (req, data) => vovkApp.PUT(req, data, segmentName),
|
|
57
|
+
PATCH: (req, data) => vovkApp.PATCH(req, data, segmentName),
|
|
58
|
+
DELETE: (req, data) => vovkApp.DELETE(req, data, segmentName),
|
|
59
|
+
HEAD: (req, data) => vovkApp.HEAD(req, data, segmentName),
|
|
60
|
+
OPTIONS: (req, data) => vovkApp.OPTIONS(req, data, segmentName),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StreamAbortMessage } from '../types/core.js';
|
|
2
|
+
import '../utils/shim.js';
|
|
3
|
+
export declare abstract class Responder {
|
|
4
|
+
response: Response;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A Responder subclass for streaming JSON Lines (JSONL) data.
|
|
8
|
+
* @see https://vovk.dev/jsonlines
|
|
9
|
+
* @param request The incoming Request object.
|
|
10
|
+
* @param getResponse Optional function to create a custom Response object.
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { JSONLinesResponder } from 'vovk';
|
|
14
|
+
*
|
|
15
|
+
* const responder = new JSONLinesResponder<MyItemType>(request, (responder) => {
|
|
16
|
+
* return new Response(responder.readableStream, { headers: responder.headers });
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Send items
|
|
20
|
+
* responder.send({ ... });
|
|
21
|
+
* // Close the stream when done
|
|
22
|
+
* responder.close();
|
|
23
|
+
* // Or throw an error
|
|
24
|
+
* responder.throw(new Error('Something went wrong'));
|
|
25
|
+
* // get the Response object, headers, etc.
|
|
26
|
+
* const { response, headers } = responder;
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare class JSONLinesResponder<T> extends Responder {
|
|
30
|
+
private isClosed;
|
|
31
|
+
private i;
|
|
32
|
+
private controller?;
|
|
33
|
+
private readonly encoder;
|
|
34
|
+
readonly readableStream: ReadableStream | null;
|
|
35
|
+
readonly headers: Record<string, string>;
|
|
36
|
+
onBeforeSend: (item: T, i: number) => T | Promise<T>;
|
|
37
|
+
constructor(request?: Request | null, getResponse?: (responder: JSONLinesResponder<T>) => Response);
|
|
38
|
+
readonly send: (item: T) => Promise<void>;
|
|
39
|
+
sendLineOrError: (data: T | StreamAbortMessage) => void;
|
|
40
|
+
readonly close: () => void;
|
|
41
|
+
readonly throw: (e: unknown) => void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import '../utils/shim.js';
|
|
2
|
+
export class Responder {
|
|
3
|
+
response;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A Responder subclass for streaming JSON Lines (JSONL) data.
|
|
7
|
+
* @see https://vovk.dev/jsonlines
|
|
8
|
+
* @param request The incoming Request object.
|
|
9
|
+
* @param getResponse Optional function to create a custom Response object.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { JSONLinesResponder } from 'vovk';
|
|
13
|
+
*
|
|
14
|
+
* const responder = new JSONLinesResponder<MyItemType>(request, (responder) => {
|
|
15
|
+
* return new Response(responder.readableStream, { headers: responder.headers });
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Send items
|
|
19
|
+
* responder.send({ ... });
|
|
20
|
+
* // Close the stream when done
|
|
21
|
+
* responder.close();
|
|
22
|
+
* // Or throw an error
|
|
23
|
+
* responder.throw(new Error('Something went wrong'));
|
|
24
|
+
* // get the Response object, headers, etc.
|
|
25
|
+
* const { response, headers } = responder;
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class JSONLinesResponder extends Responder {
|
|
29
|
+
isClosed = false;
|
|
30
|
+
i = 0;
|
|
31
|
+
controller;
|
|
32
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: biome bug
|
|
33
|
+
encoder;
|
|
34
|
+
readableStream;
|
|
35
|
+
headers;
|
|
36
|
+
onBeforeSend = (item) => item;
|
|
37
|
+
constructor(request, getResponse) {
|
|
38
|
+
super();
|
|
39
|
+
const encoder = new TextEncoder();
|
|
40
|
+
let readableController;
|
|
41
|
+
const readableStream = new ReadableStream({
|
|
42
|
+
cancel: () => {
|
|
43
|
+
this.isClosed = true;
|
|
44
|
+
},
|
|
45
|
+
start: (controller) => {
|
|
46
|
+
readableController = controller;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const accept = request?.headers?.get('accept');
|
|
50
|
+
const headers = {
|
|
51
|
+
'content-type': accept?.includes('application/jsonl')
|
|
52
|
+
? 'application/jsonl; charset=utf-8'
|
|
53
|
+
: 'text/plain; charset=utf-8',
|
|
54
|
+
};
|
|
55
|
+
this.headers = headers;
|
|
56
|
+
this.readableStream = readableStream;
|
|
57
|
+
this.encoder = encoder;
|
|
58
|
+
// biome-ignore lint/style/noNonNullAssertion: assigned at readableStream start
|
|
59
|
+
this.controller = readableController;
|
|
60
|
+
this.response = getResponse?.(this) ?? new Response(readableStream, { headers });
|
|
61
|
+
request?.signal?.addEventListener('abort', this.close, { once: true });
|
|
62
|
+
// this will make promise on the client-side to resolve immediately, before sending the first JSON line
|
|
63
|
+
this.controller?.enqueue(encoder?.encode(''));
|
|
64
|
+
}
|
|
65
|
+
send = async (item) => {
|
|
66
|
+
try {
|
|
67
|
+
// onBeforeSend is set by withValidationLibrary if iteration validation is provided
|
|
68
|
+
// in case if data is streamed immediately in a controller/service, we're going to lose the first iteration validation
|
|
69
|
+
// the await with zero timeout ensures onBeforeSend is set before the first send
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
71
|
+
this.sendLineOrError(await this.onBeforeSend(item, this.i++));
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
this.throw(e);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
sendLineOrError = (data) => {
|
|
78
|
+
const { controller, encoder } = this;
|
|
79
|
+
if (this.isClosed)
|
|
80
|
+
return;
|
|
81
|
+
controller?.enqueue(encoder?.encode(`${JSON.stringify(data)}\n`));
|
|
82
|
+
};
|
|
83
|
+
close = () => {
|
|
84
|
+
const { controller } = this;
|
|
85
|
+
if (this.isClosed)
|
|
86
|
+
return;
|
|
87
|
+
this.isClosed = true;
|
|
88
|
+
controller?.close();
|
|
89
|
+
};
|
|
90
|
+
throw = (e) => {
|
|
91
|
+
this.sendLineOrError({ isError: true, reason: e instanceof Error ? e.message : e });
|
|
92
|
+
return this.close();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
2
|
+
import type { VovkConfig, VovkOutputConfig, VovkPackageJson, VovkReadmeConfig, VovkSamplesConfig } from '../types/config.js';
|
|
3
|
+
import type { PackageJson } from '../types/package.js';
|
|
4
|
+
export declare function resolveGeneratorConfigValues({ config, outputConfigs, forceOutputConfigs, segmentName, isBundle, projectPackageJson, }: {
|
|
5
|
+
config: VovkConfig | undefined;
|
|
6
|
+
outputConfigs: VovkOutputConfig[];
|
|
7
|
+
forceOutputConfigs?: VovkOutputConfig[];
|
|
8
|
+
segmentName: string | null;
|
|
9
|
+
isBundle: boolean;
|
|
10
|
+
projectPackageJson: PackageJson | undefined;
|
|
11
|
+
}): {
|
|
12
|
+
readme: VovkReadmeConfig;
|
|
13
|
+
openAPIObject: OpenAPIObject;
|
|
14
|
+
samples: VovkSamplesConfig;
|
|
15
|
+
origin: string;
|
|
16
|
+
package: VovkPackageJson;
|
|
17
|
+
imports: VovkOutputConfig['imports'];
|
|
18
|
+
reExports: VovkOutputConfig['reExports'];
|
|
19
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { deepExtend } from '../utils/deep-extend.js';
|
|
2
|
+
export function resolveGeneratorConfigValues({ config, outputConfigs, forceOutputConfigs, segmentName, isBundle, projectPackageJson, }) {
|
|
3
|
+
const packageJson = deepExtend(Object.fromEntries(Object.entries(projectPackageJson ?? {}).filter(([key]) => [
|
|
4
|
+
'name',
|
|
5
|
+
'version',
|
|
6
|
+
'description',
|
|
7
|
+
'license',
|
|
8
|
+
'author',
|
|
9
|
+
'contributors',
|
|
10
|
+
'repository',
|
|
11
|
+
'homepage',
|
|
12
|
+
'bugs',
|
|
13
|
+
'keywords',
|
|
14
|
+
].includes(key))), config?.outputConfig?.package, typeof segmentName === 'string' ? config?.outputConfig?.segments?.[segmentName]?.package : undefined, outputConfigs?.reduce((acc, config) => deepExtend(acc, config.package), {}), isBundle ? config?.bundle?.outputConfig?.package : undefined);
|
|
15
|
+
const openAPIObject = deepExtend({
|
|
16
|
+
openapi: '3.1.0',
|
|
17
|
+
info: {
|
|
18
|
+
title: packageJson.name,
|
|
19
|
+
version: packageJson.version,
|
|
20
|
+
description: packageJson.description,
|
|
21
|
+
},
|
|
22
|
+
}, config?.outputConfig?.openAPIObject, typeof segmentName === 'string' ? config?.outputConfig?.segments?.[segmentName]?.openAPIObject : undefined, outputConfigs?.reduce((acc, config) => deepExtend(acc, config.openAPIObject), {}), isBundle ? config?.bundle?.outputConfig?.openAPIObject : undefined, forceOutputConfigs?.reduce((acc, config) => deepExtend(acc, config.openAPIObject), {}));
|
|
23
|
+
const samples = deepExtend({}, config?.outputConfig?.samples, typeof segmentName === 'string' ? config?.outputConfig?.segments?.[segmentName]?.samples : undefined, outputConfigs?.reduce((acc, config) => deepExtend(acc, config.samples), {}), isBundle ? config?.bundle?.outputConfig?.samples : undefined, forceOutputConfigs?.reduce((acc, config) => deepExtend(acc, config.samples), {}));
|
|
24
|
+
const readme = deepExtend({}, config?.outputConfig?.readme, typeof segmentName === 'string' ? config?.outputConfig?.segments?.[segmentName]?.readme : undefined, outputConfigs?.reduce((acc, config) => deepExtend(acc, config.readme), {}), isBundle ? config?.bundle?.outputConfig?.readme : undefined, forceOutputConfigs?.reduce((acc, config) => deepExtend(acc, config.readme), {}));
|
|
25
|
+
const origin = [
|
|
26
|
+
config?.outputConfig?.origin,
|
|
27
|
+
typeof segmentName === 'string' ? config?.outputConfig?.segments?.[segmentName]?.origin : undefined,
|
|
28
|
+
...(outputConfigs?.map((config) => config.origin) ?? []),
|
|
29
|
+
isBundle ? config?.bundle?.outputConfig?.origin : undefined,
|
|
30
|
+
...(forceOutputConfigs?.map((config) => config.origin) ?? []),
|
|
31
|
+
]
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.at(-1)
|
|
34
|
+
// remove trailing slash if any
|
|
35
|
+
?.replace(/\/$/, '') ?? '';
|
|
36
|
+
const imports = deepExtend({
|
|
37
|
+
fetcher: 'vovk/fetcher',
|
|
38
|
+
validateOnClient: null,
|
|
39
|
+
createRPC: 'vovk/create-rpc',
|
|
40
|
+
}, config?.outputConfig?.imports, typeof segmentName === 'string' ? config?.outputConfig?.segments?.[segmentName]?.imports : undefined, outputConfigs?.reduce((acc, config) => deepExtend(acc, config.imports), {}), isBundle ? config?.bundle?.outputConfig?.imports : undefined, forceOutputConfigs?.reduce((acc, config) => deepExtend(acc, config.imports), {}));
|
|
41
|
+
const reExports = deepExtend(
|
|
42
|
+
// segmentName can be an empty string (for the root segment) and null (for composed clients)
|
|
43
|
+
// therefore, !segmentName indicates that this either a composed client or a root segment of a segmented client
|
|
44
|
+
{}, !segmentName && config?.outputConfig?.reExports,
|
|
45
|
+
// for segmented client, apply all reExports from all segments
|
|
46
|
+
typeof segmentName !== 'string' &&
|
|
47
|
+
Object.values(config?.outputConfig?.segments ?? {}).reduce((acc, segmentConfig) => deepExtend(acc, segmentConfig.reExports ?? {}), {}),
|
|
48
|
+
// for a specific segment, apply reExports from that segment
|
|
49
|
+
typeof segmentName === 'string' ? config?.outputConfig?.segments?.[segmentName]?.reExports : undefined, outputConfigs?.reduce((acc, config) => deepExtend(acc, config.reExports), {}), isBundle ? config?.bundle?.outputConfig?.reExports : undefined, forceOutputConfigs?.reduce((acc, config) => deepExtend(acc, config.reExports), {}));
|
|
50
|
+
return {
|
|
51
|
+
package: packageJson,
|
|
52
|
+
openAPIObject,
|
|
53
|
+
samples,
|
|
54
|
+
readme,
|
|
55
|
+
origin,
|
|
56
|
+
imports,
|
|
57
|
+
reExports,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { VovkController, VovkHandlerSchema } from '../types/core.js';
|
|
2
|
+
export declare function setHandlerSchema(h: ((...args: unknown[]) => unknown) & {
|
|
3
|
+
_getSchema?: (controller: VovkController) => Omit<VovkHandlerSchema, 'httpMethod' | 'path'>;
|
|
4
|
+
}, schema: Omit<VovkHandlerSchema, 'httpMethod' | 'path'>): Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function setHandlerSchema(h, schema) {
|
|
2
|
+
h._getSchema = (controller) => {
|
|
3
|
+
if (!controller) {
|
|
4
|
+
throw new Error('Error setting client validators. Controller not found. Did you forget to use an HTTP decorator?');
|
|
5
|
+
}
|
|
6
|
+
const handlerName = Object.getOwnPropertyNames(controller).find((key) => controller[key]._sourceMethod === h);
|
|
7
|
+
if (!handlerName) {
|
|
8
|
+
throw new Error('Error setting client validators. Handler not found.');
|
|
9
|
+
}
|
|
10
|
+
return schema;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type BinaryData = Blob | File | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array> | string;
|
|
2
|
+
/**
|
|
3
|
+
* Creates a Response object for downloading binary data with appropriate headers.
|
|
4
|
+
* @see https://vovk.dev/response
|
|
5
|
+
*/
|
|
6
|
+
export declare function toDownloadResponse(data: BinaryData, { filename, type, headers }?: {
|
|
7
|
+
filename?: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
}): Response;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fileNameToDisposition } from '../utils/file-name-to-disposition.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a Response object for downloading binary data with appropriate headers.
|
|
4
|
+
* @see https://vovk.dev/response
|
|
5
|
+
*/
|
|
6
|
+
export function toDownloadResponse(data, { filename, type, headers } = {}) {
|
|
7
|
+
const body = data instanceof Blob
|
|
8
|
+
? data
|
|
9
|
+
: data instanceof ReadableStream
|
|
10
|
+
? data
|
|
11
|
+
: new Blob([data], { type: type ?? 'application/octet-stream' });
|
|
12
|
+
const resolvedName = filename ?? (data instanceof File ? data.name : undefined);
|
|
13
|
+
const resolvedType = type ?? (body instanceof Blob ? body.type : undefined);
|
|
14
|
+
return new Response(body, {
|
|
15
|
+
headers: {
|
|
16
|
+
...(resolvedType ? { 'Content-Type': resolvedType } : {}),
|
|
17
|
+
...(resolvedName
|
|
18
|
+
? {
|
|
19
|
+
'Content-Disposition': fileNameToDisposition(resolvedName),
|
|
20
|
+
}
|
|
21
|
+
: {}),
|
|
22
|
+
...headers,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { HttpMethod, HttpStatus } from '../types/enums.js';
|
|
2
|
+
import type { RouteHandler, VovkController, DecoratorOptions } from '../types/core.js';
|
|
3
|
+
declare class VovkApp {
|
|
4
|
+
#private;
|
|
5
|
+
private static getHeadersFromDecoratorOptions;
|
|
6
|
+
routes: Record<HttpMethod, Map<VovkController, Record<string, RouteHandler>>>;
|
|
7
|
+
GET: (req: Request, data: {
|
|
8
|
+
params: Promise<Record<string, string[]>>;
|
|
9
|
+
}, segmentName: string) => Promise<Response>;
|
|
10
|
+
POST: (req: Request, data: {
|
|
11
|
+
params: Promise<Record<string, string[]>>;
|
|
12
|
+
}, segmentName: string) => Promise<Response>;
|
|
13
|
+
PUT: (req: Request, data: {
|
|
14
|
+
params: Promise<Record<string, string[]>>;
|
|
15
|
+
}, segmentName: string) => Promise<Response>;
|
|
16
|
+
PATCH: (req: Request, data: {
|
|
17
|
+
params: Promise<Record<string, string[]>>;
|
|
18
|
+
}, segmentName: string) => Promise<Response>;
|
|
19
|
+
DELETE: (req: Request, data: {
|
|
20
|
+
params: Promise<Record<string, string[]>>;
|
|
21
|
+
}, segmentName: string) => Promise<Response>;
|
|
22
|
+
HEAD: (req: Request, data: {
|
|
23
|
+
params: Promise<Record<string, string[]>>;
|
|
24
|
+
}, segmentName: string) => Promise<Response>;
|
|
25
|
+
OPTIONS: (req: Request, data: {
|
|
26
|
+
params: Promise<Record<string, string[]>>;
|
|
27
|
+
}, segmentName: string) => Promise<Response>;
|
|
28
|
+
respond: ({ statusCode, responseBody, options, }: {
|
|
29
|
+
req: Request;
|
|
30
|
+
statusCode: HttpStatus;
|
|
31
|
+
responseBody: unknown;
|
|
32
|
+
options?: DecoratorOptions;
|
|
33
|
+
}) => Promise<Response>;
|
|
34
|
+
}
|
|
35
|
+
declare const vovkApp: VovkApp;
|
|
36
|
+
export { vovkApp };
|