vovk 3.0.0-draft.13 → 3.0.0-draft.130
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 +1 -1
- package/README.md +8 -96
- package/{HttpException.d.ts → dist/HttpException.d.ts} +2 -2
- package/{HttpException.js → dist/HttpException.js} +3 -3
- package/dist/JSONLinesResponse.d.ts +14 -0
- package/{StreamResponse.js → dist/JSONLinesResponse.js} +15 -9
- package/{Segment.d.ts → dist/VovkApp.d.ts} +11 -10
- package/dist/VovkApp.js +189 -0
- package/dist/client/createRPC.d.ts +3 -0
- package/dist/client/createRPC.js +86 -0
- package/{client → dist/client}/defaultHandler.d.ts +1 -1
- package/dist/client/defaultHandler.js +22 -0
- package/dist/client/defaultStreamHandler.d.ts +4 -0
- package/{client → dist/client}/defaultStreamHandler.js +8 -8
- package/dist/client/fetcher.d.ts +8 -0
- package/dist/client/fetcher.js +88 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +8 -0
- package/dist/client/types.d.ts +113 -0
- package/dist/createDecorator.d.ts +6 -0
- package/{createDecorator.js → dist/createDecorator.js} +21 -16
- package/{createSegment.d.ts → dist/createVovkApp.d.ts} +10 -10
- package/dist/createVovkApp.js +129 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +25 -0
- package/dist/types.d.ts +276 -0
- package/dist/types.js +72 -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 +35 -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/{utils → dist/utils}/reqMeta.d.ts +1 -2
- package/{utils → dist/utils}/reqQuery.d.ts +1 -2
- 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 +10 -4
- package/.npmignore +0 -2
- package/Segment.js +0 -182
- package/StreamResponse.d.ts +0 -16
- package/client/clientizeController.d.ts +0 -4
- package/client/clientizeController.js +0 -93
- package/client/defaultFetcher.d.ts +0 -4
- package/client/defaultFetcher.js +0 -49
- package/client/defaultHandler.js +0 -21
- package/client/defaultStreamHandler.d.ts +0 -4
- package/client/index.d.ts +0 -4
- package/client/index.js +0 -5
- package/client/types.d.ts +0 -100
- package/createDecorator.d.ts +0 -4
- package/createSegment.js +0 -118
- package/generateStaticAPI.d.ts +0 -4
- package/generateStaticAPI.js +0 -18
- package/index.d.ts +0 -60
- package/index.js +0 -20
- package/types.d.ts +0 -186
- package/types.js +0 -65
- package/utils/getSchema.d.ts +0 -8
- package/utils/getSchema.js +0 -38
- package/utils/reqQuery.js +0 -25
- package/utils/setClientValidatorsForHandler.d.ts +0 -5
- package/utils/setClientValidatorsForHandler.js +0 -28
- package/worker/index.d.ts +0 -3
- package/worker/index.js +0 -7
- package/worker/promisifyWorker.d.ts +0 -2
- package/worker/promisifyWorker.js +0 -143
- package/worker/types.d.ts +0 -31
- package/worker/types.js +0 -2
- package/worker/worker.d.ts +0 -1
- package/worker/worker.js +0 -44
- /package/{client → dist/client}/types.js +0 -0
- /package/{utils → dist/utils}/reqMeta.js +0 -0
- /package/{utils → dist/utils}/shim.d.ts +0 -0
- /package/{utils → dist/utils}/shim.js +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = reqQuery;
|
|
7
|
+
const parseQuery_js_1 = __importDefault(require("./parseQuery.js"));
|
|
8
|
+
function reqQuery(req) {
|
|
9
|
+
return (0, parseQuery_js_1.default)(req.nextUrl.search);
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { KnownAny } from '../types.js';
|
|
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.js';
|
|
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.js';
|
|
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_js_1 = require("../HttpException.js");
|
|
5
|
+
const types_js_1 = require("../types.js");
|
|
6
|
+
const setHandlerSchema_js_1 = require("./setHandlerSchema.js");
|
|
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_js_1.HttpException(types_js_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_js_1.HttpException(types_js_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_js_1.HttpException(types_js_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_js_1.HttpException(types_js_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_js_1.setHandlerSchema)(resultHandler, { validation });
|
|
86
|
+
}
|
|
87
|
+
return resultHandler;
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vovk",
|
|
3
|
-
"version": "3.0.0-draft.
|
|
4
|
-
"
|
|
3
|
+
"version": "3.0.0-draft.130",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"description": "RESTful RPC for Next.js",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "git+https://github.com/finom/vovk.git"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
|
-
"build": "
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"rm-dist": "shx rm -rf dist",
|
|
11
13
|
"lint": "eslint . --fix",
|
|
12
14
|
"tsc": "tsc --noEmit",
|
|
13
15
|
"npm-publish": "if [ -z \"$NPM_TAG\" ]; then echo 'Error: NPM_TAG is not set'; exit 1; fi; cd ./dist && npm publish --tag=$NPM_TAG && cd ..",
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
"nextjs",
|
|
18
20
|
"router"
|
|
19
21
|
],
|
|
20
|
-
"author": "
|
|
22
|
+
"author": "Andrey Gubanov",
|
|
21
23
|
"license": "MIT",
|
|
22
24
|
"bugs": {
|
|
23
25
|
"url": "https://github.com/finom/vovk/issues"
|
|
@@ -25,5 +27,9 @@
|
|
|
25
27
|
"homepage": "https://vovk.dev",
|
|
26
28
|
"peerDependencies": {
|
|
27
29
|
"next": "*"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"openapi3-ts": "*",
|
|
33
|
+
"type-fest": "*"
|
|
28
34
|
}
|
|
29
35
|
}
|
package/.npmignore
DELETED
package/Segment.js
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
var _a;
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports._Segment = void 0;
|
|
8
|
-
const types_1 = require("./types");
|
|
9
|
-
const HttpException_1 = require("./HttpException");
|
|
10
|
-
const StreamResponse_1 = require("./StreamResponse");
|
|
11
|
-
const reqQuery_1 = __importDefault(require("./utils/reqQuery"));
|
|
12
|
-
const reqMeta_1 = __importDefault(require("./utils/reqMeta"));
|
|
13
|
-
class _Segment {
|
|
14
|
-
static getHeadersFromOptions(options) {
|
|
15
|
-
if (!options)
|
|
16
|
-
return {};
|
|
17
|
-
const corsHeaders = {
|
|
18
|
-
'Access-Control-Allow-Origin': '*',
|
|
19
|
-
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS, HEAD',
|
|
20
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
21
|
-
};
|
|
22
|
-
const headers = {
|
|
23
|
-
...(options.cors ? corsHeaders : {}),
|
|
24
|
-
...(options.headers ?? {}),
|
|
25
|
-
};
|
|
26
|
-
return headers;
|
|
27
|
-
}
|
|
28
|
-
_routes = {
|
|
29
|
-
GET: new Map(),
|
|
30
|
-
POST: new Map(),
|
|
31
|
-
PUT: new Map(),
|
|
32
|
-
PATCH: new Map(),
|
|
33
|
-
DELETE: new Map(),
|
|
34
|
-
HEAD: new Map(),
|
|
35
|
-
OPTIONS: new Map(),
|
|
36
|
-
};
|
|
37
|
-
GET = async (req, data) => this.#callMethod(types_1._HttpMethod.GET, req, await data.params);
|
|
38
|
-
POST = async (req, data) => this.#callMethod(types_1._HttpMethod.POST, req, await data.params);
|
|
39
|
-
PUT = async (req, data) => this.#callMethod(types_1._HttpMethod.PUT, req, await data.params);
|
|
40
|
-
PATCH = async (req, data) => this.#callMethod(types_1._HttpMethod.PATCH, req, await data.params);
|
|
41
|
-
DELETE = async (req, data) => this.#callMethod(types_1._HttpMethod.DELETE, req, await data.params);
|
|
42
|
-
HEAD = async (req, data) => this.#callMethod(types_1._HttpMethod.HEAD, req, await data.params);
|
|
43
|
-
OPTIONS = async (req, data) => this.#callMethod(types_1._HttpMethod.OPTIONS, req, await data.params);
|
|
44
|
-
respond = (status, body, options) => {
|
|
45
|
-
return new Response(JSON.stringify(body), {
|
|
46
|
-
status,
|
|
47
|
-
headers: {
|
|
48
|
-
'Content-Type': 'application/json',
|
|
49
|
-
..._a.getHeadersFromOptions(options),
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
#respondWithError = (statusCode, message, options) => {
|
|
54
|
-
return this.respond(statusCode, {
|
|
55
|
-
statusCode,
|
|
56
|
-
message,
|
|
57
|
-
isError: true,
|
|
58
|
-
}, options);
|
|
59
|
-
};
|
|
60
|
-
#callMethod = async (httpMethod, req, params) => {
|
|
61
|
-
const controllers = this._routes[httpMethod];
|
|
62
|
-
const methodParams = {};
|
|
63
|
-
const path = params[Object.keys(params)[0]];
|
|
64
|
-
const handlers = {};
|
|
65
|
-
controllers.forEach((staticMethods, controller) => {
|
|
66
|
-
const prefix = controller._prefix ?? '';
|
|
67
|
-
if (!controller._activated) {
|
|
68
|
-
throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Controller "${controller.name}" found but not activated`);
|
|
69
|
-
}
|
|
70
|
-
Object.entries(staticMethods).forEach(([path, staticMethod]) => {
|
|
71
|
-
const fullPath = [prefix, path].filter(Boolean).join('/');
|
|
72
|
-
handlers[fullPath] = { staticMethod, controller };
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
const getHandler = () => {
|
|
76
|
-
if (Object.keys(params).length === 0) {
|
|
77
|
-
return handlers[''];
|
|
78
|
-
}
|
|
79
|
-
const allMethodKeys = Object.keys(handlers);
|
|
80
|
-
let methodKeys = [];
|
|
81
|
-
methodKeys = allMethodKeys
|
|
82
|
-
// First, try to match literal routes exactly.
|
|
83
|
-
.filter((p) => {
|
|
84
|
-
if (p.includes(':'))
|
|
85
|
-
return false; // Skip parameterized paths
|
|
86
|
-
return p === path.join('/');
|
|
87
|
-
});
|
|
88
|
-
if (!methodKeys.length) {
|
|
89
|
-
methodKeys = allMethodKeys.filter((p) => {
|
|
90
|
-
const routeSegments = p.split('/');
|
|
91
|
-
if (routeSegments.length !== path.length)
|
|
92
|
-
return false;
|
|
93
|
-
for (let i = 0; i < routeSegments.length; i++) {
|
|
94
|
-
const routeSegment = routeSegments[i];
|
|
95
|
-
const pathSegment = path[i];
|
|
96
|
-
if (routeSegment.startsWith(':')) {
|
|
97
|
-
const parameter = routeSegment.slice(1);
|
|
98
|
-
if (parameter in methodParams) {
|
|
99
|
-
throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Duplicate parameter "${parameter}"`);
|
|
100
|
-
}
|
|
101
|
-
// If it's a parameterized segment, capture the parameter value.
|
|
102
|
-
methodParams[parameter] = pathSegment;
|
|
103
|
-
}
|
|
104
|
-
else if (routeSegment !== pathSegment) {
|
|
105
|
-
// If it's a literal segment and it does not match the corresponding path segment, return false.
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return true;
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
if (methodKeys.length > 1) {
|
|
113
|
-
throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Conflicting routes found: ${methodKeys.join(', ')}`);
|
|
114
|
-
}
|
|
115
|
-
const [methodKey] = methodKeys;
|
|
116
|
-
if (methodKey) {
|
|
117
|
-
return handlers[methodKey];
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
};
|
|
121
|
-
const handler = getHandler();
|
|
122
|
-
if (!handler) {
|
|
123
|
-
return this.#respondWithError(types_1._HttpStatus.NOT_FOUND, `Route ${path.join('/')} is not found`);
|
|
124
|
-
}
|
|
125
|
-
const { staticMethod, controller } = handler;
|
|
126
|
-
req.vovk = {
|
|
127
|
-
body: () => req.json(),
|
|
128
|
-
query: () => (0, reqQuery_1.default)(req),
|
|
129
|
-
meta: (meta) => (0, reqMeta_1.default)(req, meta),
|
|
130
|
-
};
|
|
131
|
-
try {
|
|
132
|
-
const result = await staticMethod.call(controller, req, methodParams);
|
|
133
|
-
const isIterator = typeof result === 'object' &&
|
|
134
|
-
!!result &&
|
|
135
|
-
((Reflect.has(result, Symbol.iterator) &&
|
|
136
|
-
typeof result[Symbol.iterator] === 'function') ||
|
|
137
|
-
(Reflect.has(result, Symbol.asyncIterator) &&
|
|
138
|
-
typeof result[Symbol.asyncIterator] === 'function'));
|
|
139
|
-
if (isIterator && !(result instanceof Array)) {
|
|
140
|
-
const streamResponse = new StreamResponse_1._StreamResponse({
|
|
141
|
-
headers: {
|
|
142
|
-
...StreamResponse_1._StreamResponse.defaultHeaders,
|
|
143
|
-
..._a.getHeadersFromOptions(staticMethod._options),
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
void (async () => {
|
|
147
|
-
try {
|
|
148
|
-
for await (const chunk of result) {
|
|
149
|
-
streamResponse.send(chunk);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch (e) {
|
|
153
|
-
return streamResponse.throw(e);
|
|
154
|
-
}
|
|
155
|
-
return streamResponse.close();
|
|
156
|
-
})();
|
|
157
|
-
return streamResponse;
|
|
158
|
-
}
|
|
159
|
-
if (result instanceof Response) {
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
return this.respond(200, result ?? null, staticMethod._options);
|
|
163
|
-
}
|
|
164
|
-
catch (e) {
|
|
165
|
-
const err = e;
|
|
166
|
-
try {
|
|
167
|
-
await controller._onError?.(err, req);
|
|
168
|
-
}
|
|
169
|
-
catch (onErrorError) {
|
|
170
|
-
// eslint-disable-next-line no-console
|
|
171
|
-
console.error(onErrorError);
|
|
172
|
-
}
|
|
173
|
-
if (err.message !== 'NEXT_REDIRECT' && err.message !== 'NEXT_NOT_FOUND') {
|
|
174
|
-
const statusCode = err.statusCode ?? types_1._HttpStatus.INTERNAL_SERVER_ERROR;
|
|
175
|
-
return this.#respondWithError(statusCode, err.message, staticMethod._options);
|
|
176
|
-
}
|
|
177
|
-
throw e; // if NEXT_REDIRECT or NEXT_NOT_FOUND, rethrow it
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
exports._Segment = _Segment;
|
|
182
|
-
_a = _Segment;
|
package/StreamResponse.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { _KnownAny as KnownAny, _StreamAbortMessage as StreamAbortMessage } from './types';
|
|
2
|
-
import './utils/shim';
|
|
3
|
-
export declare class _StreamResponse<T> extends Response {
|
|
4
|
-
static defaultHeaders: {
|
|
5
|
-
'Content-Type': string;
|
|
6
|
-
};
|
|
7
|
-
isClosed: boolean;
|
|
8
|
-
controller?: ReadableStreamDefaultController;
|
|
9
|
-
readonly encoder: TextEncoder;
|
|
10
|
-
readonly readableStream: ReadableStream;
|
|
11
|
-
constructor(init?: ResponseInit);
|
|
12
|
-
send(data: T | StreamAbortMessage): void;
|
|
13
|
-
close(): void;
|
|
14
|
-
throw(e: KnownAny): void;
|
|
15
|
-
[Symbol.dispose](): void;
|
|
16
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { type _VovkControllerSchema as VovkControllerSchema, type _KnownAny as KnownAny } from '../types';
|
|
2
|
-
import { type _VovkClientOptions as VovkClientOptions, type _VovkClient as VovkClient, type _VovkDefaultFetcherOptions as VovkDefaultFetcherOptions } from './types';
|
|
3
|
-
export declare const ARRAY_QUERY_KEY = "_vovkarr";
|
|
4
|
-
export declare const _clientizeController: <T, OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions>(givenController: VovkControllerSchema, segmentName?: string, options?: VovkClientOptions<OPTS>) => VovkClient<T, OPTS>;
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports._clientizeController = exports.ARRAY_QUERY_KEY = void 0;
|
|
7
|
-
const defaultFetcher_1 = __importDefault(require("./defaultFetcher"));
|
|
8
|
-
const defaultHandler_1 = require("./defaultHandler");
|
|
9
|
-
const defaultStreamHandler_1 = require("./defaultStreamHandler");
|
|
10
|
-
exports.ARRAY_QUERY_KEY = '_vovkarr';
|
|
11
|
-
const trimPath = (path) => path.trim().replace(/^\/|\/$/g, '');
|
|
12
|
-
const getHandlerPath = (endpoint, params, query) => {
|
|
13
|
-
let result = endpoint;
|
|
14
|
-
for (const [key, value] of Object.entries(params ?? {})) {
|
|
15
|
-
result = result.replace(`:${key}`, value);
|
|
16
|
-
}
|
|
17
|
-
const searchParams = new URLSearchParams();
|
|
18
|
-
let hasQuery = false;
|
|
19
|
-
const arrayKeys = [];
|
|
20
|
-
for (const [key, value] of Object.entries(query ?? {})) {
|
|
21
|
-
if (typeof value === 'undefined')
|
|
22
|
-
continue;
|
|
23
|
-
if (value instanceof Array) {
|
|
24
|
-
arrayKeys.push(key);
|
|
25
|
-
for (const val of value) {
|
|
26
|
-
searchParams.append(key, val);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
searchParams.set(key, value);
|
|
31
|
-
}
|
|
32
|
-
hasQuery = true;
|
|
33
|
-
}
|
|
34
|
-
if (arrayKeys.length) {
|
|
35
|
-
searchParams.set(exports.ARRAY_QUERY_KEY, arrayKeys.join(','));
|
|
36
|
-
}
|
|
37
|
-
return `${result}${hasQuery ? '?' : ''}${searchParams.toString()}`;
|
|
38
|
-
};
|
|
39
|
-
const _clientizeController = (givenController, segmentName, options) => {
|
|
40
|
-
const controller = givenController;
|
|
41
|
-
const client = {};
|
|
42
|
-
if (!controller)
|
|
43
|
-
throw new Error(`Unable to clientize. Controller schema is not provided`);
|
|
44
|
-
const schema = controller._handlers;
|
|
45
|
-
if (!schema)
|
|
46
|
-
throw new Error(`Unable to clientize. No schema for controller ${String(controller?._controllerName)}`);
|
|
47
|
-
const controllerPrefix = trimPath(controller._prefix ?? '');
|
|
48
|
-
const { fetcher: settingsFetcher = defaultFetcher_1.default } = options ?? {};
|
|
49
|
-
for (const [staticMethodName, { path, httpMethod, clientValidators }] of Object.entries(schema)) {
|
|
50
|
-
const getEndpoint = ({ prefix, params, query, }) => {
|
|
51
|
-
const mainPrefix = (prefix.startsWith('http://') || prefix.startsWith('https://') || prefix.startsWith('/') ? '' : '/') +
|
|
52
|
-
(prefix.endsWith('/') ? prefix : `${prefix}/`) +
|
|
53
|
-
(segmentName ? `${segmentName}/` : '');
|
|
54
|
-
return mainPrefix + getHandlerPath([controllerPrefix, path].filter(Boolean).join('/'), params, query);
|
|
55
|
-
};
|
|
56
|
-
const handler = (input = {}) => {
|
|
57
|
-
const fetcher = input.fetcher ?? settingsFetcher;
|
|
58
|
-
const validate = async ({ body, query, endpoint }) => {
|
|
59
|
-
await (input.validateOnClient ?? options?.validateOnClient)?.({ body, query, endpoint }, clientValidators ?? {});
|
|
60
|
-
};
|
|
61
|
-
const internalOptions = {
|
|
62
|
-
name: staticMethodName,
|
|
63
|
-
httpMethod,
|
|
64
|
-
getEndpoint,
|
|
65
|
-
validate,
|
|
66
|
-
defaultHandler: defaultHandler_1._defaultHandler,
|
|
67
|
-
defaultStreamHandler: defaultStreamHandler_1._defaultStreamHandler,
|
|
68
|
-
};
|
|
69
|
-
const internalInput = {
|
|
70
|
-
...options?.defaultOptions,
|
|
71
|
-
...input,
|
|
72
|
-
body: input.body ?? null,
|
|
73
|
-
query: input.query ?? {},
|
|
74
|
-
params: input.params ?? {},
|
|
75
|
-
// TS workaround
|
|
76
|
-
fetcher: undefined,
|
|
77
|
-
validateOnClient: undefined,
|
|
78
|
-
};
|
|
79
|
-
delete internalInput.fetcher;
|
|
80
|
-
delete internalInput.validateOnClient;
|
|
81
|
-
if (!fetcher)
|
|
82
|
-
throw new Error('Fetcher is not provided');
|
|
83
|
-
const fetcherPromise = fetcher(internalOptions, internalInput);
|
|
84
|
-
if (!(fetcherPromise instanceof Promise))
|
|
85
|
-
return Promise.resolve(fetcherPromise);
|
|
86
|
-
return input.transform ? fetcherPromise.then(input.transform) : fetcherPromise;
|
|
87
|
-
};
|
|
88
|
-
// @ts-expect-error TODO: Fix this
|
|
89
|
-
client[staticMethodName] = handler;
|
|
90
|
-
}
|
|
91
|
-
return client;
|
|
92
|
-
};
|
|
93
|
-
exports._clientizeController = _clientizeController;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { _VovkDefaultFetcherOptions as VovkDefaultFetcherOptions, _VovkClientFetcher as VovkClientFetcher } from './types';
|
|
2
|
-
export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultFetcher";
|
|
3
|
-
declare const defaultFetcher: VovkClientFetcher<VovkDefaultFetcherOptions>;
|
|
4
|
-
export default defaultFetcher;
|
package/client/defaultFetcher.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_ERROR_MESSAGE = void 0;
|
|
4
|
-
const types_1 = require("../types");
|
|
5
|
-
const HttpException_1 = require("../HttpException");
|
|
6
|
-
exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultFetcher';
|
|
7
|
-
// defaultFetcher uses HttpException class to throw errors of fake HTTP status 0 if client-side error occurs
|
|
8
|
-
// For normal HTTP errors, it uses message and status code from the response of VovkErrorResponse type
|
|
9
|
-
const defaultFetcher = async ({ httpMethod, getEndpoint, validate, defaultHandler, defaultStreamHandler }, { params, query, body, prefix = '/api', ...options }) => {
|
|
10
|
-
const endpoint = getEndpoint({ prefix, params, query });
|
|
11
|
-
if (!options.disableClientValidation) {
|
|
12
|
-
try {
|
|
13
|
-
await validate({ body, query, endpoint });
|
|
14
|
-
}
|
|
15
|
-
catch (e) {
|
|
16
|
-
// if HttpException is thrown, rethrow it
|
|
17
|
-
if (e instanceof HttpException_1._HttpException)
|
|
18
|
-
throw e;
|
|
19
|
-
// otherwise, throw HttpException with status 0
|
|
20
|
-
throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
const init = {
|
|
24
|
-
method: httpMethod,
|
|
25
|
-
...options,
|
|
26
|
-
};
|
|
27
|
-
if (body instanceof FormData) {
|
|
28
|
-
init.body = body;
|
|
29
|
-
}
|
|
30
|
-
else if (body) {
|
|
31
|
-
init.body = JSON.stringify(body);
|
|
32
|
-
}
|
|
33
|
-
let response;
|
|
34
|
-
try {
|
|
35
|
-
response = await fetch(endpoint, init);
|
|
36
|
-
}
|
|
37
|
-
catch (e) {
|
|
38
|
-
// handle network errors
|
|
39
|
-
throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e?.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
40
|
-
}
|
|
41
|
-
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
42
|
-
return defaultHandler(response);
|
|
43
|
-
}
|
|
44
|
-
if (response.headers.get('content-type')?.includes('text/plain; charset=utf-8')) {
|
|
45
|
-
return defaultStreamHandler(response);
|
|
46
|
-
}
|
|
47
|
-
return response;
|
|
48
|
-
};
|
|
49
|
-
exports.default = defaultFetcher;
|
package/client/defaultHandler.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports._defaultHandler = exports.DEFAULT_ERROR_MESSAGE = void 0;
|
|
4
|
-
const HttpException_1 = require("../HttpException");
|
|
5
|
-
exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultHandler';
|
|
6
|
-
const _defaultHandler = async (response) => {
|
|
7
|
-
let result;
|
|
8
|
-
try {
|
|
9
|
-
result = await response.json();
|
|
10
|
-
}
|
|
11
|
-
catch (e) {
|
|
12
|
-
// handle parsing errors
|
|
13
|
-
throw new HttpException_1._HttpException(response.status, e?.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
14
|
-
}
|
|
15
|
-
if (!response.ok) {
|
|
16
|
-
// handle server errors
|
|
17
|
-
throw new HttpException_1._HttpException(response.status, result?.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
18
|
-
}
|
|
19
|
-
return result;
|
|
20
|
-
};
|
|
21
|
-
exports._defaultHandler = _defaultHandler;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { _StreamAsyncIterator as StreamAsyncIterator } from './types';
|
|
2
|
-
import '../utils/shim';
|
|
3
|
-
export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultStreamHandler";
|
|
4
|
-
export declare const _defaultStreamHandler: (response: Response) => Promise<StreamAsyncIterator<unknown>>;
|
package/client/index.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { _clientizeController as clientizeController } from './clientizeController';
|
|
2
|
-
import type { _VovkClientFetcher as VovkClientFetcher, _VovkClientOptions as VovkClientOptions, _VovkDefaultFetcherOptions as VovkDefaultFetcherOptions } from './types';
|
|
3
|
-
export { clientizeController };
|
|
4
|
-
export type { VovkClientFetcher, VovkClientOptions, VovkDefaultFetcherOptions };
|
package/client/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.clientizeController = void 0;
|
|
4
|
-
const clientizeController_1 = require("./clientizeController");
|
|
5
|
-
Object.defineProperty(exports, "clientizeController", { enumerable: true, get: function () { return clientizeController_1._clientizeController; } });
|