vovk 0.0.0 → 0.0.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/HttpException.d.ts +6 -0
- package/HttpException.js +13 -0
- package/README.md +844 -1
- package/Segment.d.ts +31 -0
- package/Segment.js +132 -0
- package/StreamResponse.d.ts +16 -0
- package/StreamResponse.js +41 -0
- package/client/clientizeController.d.ts +3 -0
- package/client/clientizeController.js +67 -0
- package/client/defaultFetcher.d.ts +7 -0
- package/client/defaultFetcher.js +55 -0
- package/client/defaultStreamFetcher.d.ts +4 -0
- package/client/defaultStreamFetcher.js +103 -0
- package/client/index.d.ts +5 -0
- package/client/index.js +7 -0
- package/client/types.d.ts +69 -0
- package/client/types.js +2 -0
- package/createDecorator.d.ts +9 -0
- package/createDecorator.js +41 -0
- package/createSegment.d.ts +67 -0
- package/createSegment.js +171 -0
- package/index.d.ts +62 -0
- package/index.js +16 -0
- package/package.json +66 -6
- package/tsconfig.tsbuildinfo +1 -0
- package/types.d.ts +138 -0
- package/types.js +65 -0
- package/worker/index.d.ts +3 -0
- package/worker/index.js +7 -0
- package/worker/promisifyWorker.d.ts +2 -0
- package/worker/promisifyWorker.js +45 -0
- package/worker/types.d.ts +17 -0
- package/worker/types.js +2 -0
- package/worker/worker.d.ts +1 -0
- package/worker/worker.js +30 -0
package/Segment.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NextRequest } from 'next/server';
|
|
2
|
+
import { _HttpMethod as HttpMethod, type _RouteHandler as RouteHandler } from './types';
|
|
3
|
+
export declare class _Segment {
|
|
4
|
+
#private;
|
|
5
|
+
_routes: Record<HttpMethod, Map<{
|
|
6
|
+
name?: string;
|
|
7
|
+
_prefix?: string;
|
|
8
|
+
_activated?: true;
|
|
9
|
+
}, Record<string, RouteHandler>>>;
|
|
10
|
+
GET: (req: NextRequest, data: {
|
|
11
|
+
params: Record<string, string[]>;
|
|
12
|
+
}) => Promise<Response | undefined>;
|
|
13
|
+
POST: (req: NextRequest, data: {
|
|
14
|
+
params: Record<string, string[]>;
|
|
15
|
+
}) => Promise<Response | undefined>;
|
|
16
|
+
PUT: (req: NextRequest, data: {
|
|
17
|
+
params: Record<string, string[]>;
|
|
18
|
+
}) => Promise<Response | undefined>;
|
|
19
|
+
PATCH: (req: NextRequest, data: {
|
|
20
|
+
params: Record<string, string[]>;
|
|
21
|
+
}) => Promise<Response | undefined>;
|
|
22
|
+
DELETE: (req: NextRequest, data: {
|
|
23
|
+
params: Record<string, string[]>;
|
|
24
|
+
}) => Promise<Response | undefined>;
|
|
25
|
+
HEAD: (req: NextRequest, data: {
|
|
26
|
+
params: Record<string, string[]>;
|
|
27
|
+
}) => Promise<Response | undefined>;
|
|
28
|
+
OPTIONS: (req: NextRequest, data: {
|
|
29
|
+
params: Record<string, string[]>;
|
|
30
|
+
}) => Promise<Response | undefined>;
|
|
31
|
+
}
|
package/Segment.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._Segment = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
const HttpException_1 = require("./HttpException");
|
|
6
|
+
class _Segment {
|
|
7
|
+
_routes = {
|
|
8
|
+
GET: new Map(),
|
|
9
|
+
POST: new Map(),
|
|
10
|
+
PUT: new Map(),
|
|
11
|
+
PATCH: new Map(),
|
|
12
|
+
DELETE: new Map(),
|
|
13
|
+
HEAD: new Map(),
|
|
14
|
+
OPTIONS: new Map(),
|
|
15
|
+
};
|
|
16
|
+
GET = (req, data) => this.#callMethod(types_1._HttpMethod.GET, req, data.params);
|
|
17
|
+
POST = (req, data) => this.#callMethod(types_1._HttpMethod.POST, req, data.params);
|
|
18
|
+
PUT = (req, data) => this.#callMethod(types_1._HttpMethod.PUT, req, data.params);
|
|
19
|
+
PATCH = (req, data) => this.#callMethod(types_1._HttpMethod.PATCH, req, data.params);
|
|
20
|
+
DELETE = (req, data) => this.#callMethod(types_1._HttpMethod.DELETE, req, data.params);
|
|
21
|
+
HEAD = (req, data) => this.#callMethod(types_1._HttpMethod.HEAD, req, data.params);
|
|
22
|
+
OPTIONS = (req, data) => this.#callMethod(types_1._HttpMethod.OPTIONS, req, data.params);
|
|
23
|
+
#respond = (status, body) => {
|
|
24
|
+
return new Response(JSON.stringify(body), {
|
|
25
|
+
status,
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
#respondWithError = (statusCode, message) => {
|
|
32
|
+
return this.#respond(statusCode, {
|
|
33
|
+
statusCode,
|
|
34
|
+
message,
|
|
35
|
+
isError: true,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
#callMethod = async (httpMethod, req, params) => {
|
|
39
|
+
const controllers = this._routes[httpMethod];
|
|
40
|
+
const methodParams = {};
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
42
|
+
const handlers = Object.fromEntries([...controllers.entries()]
|
|
43
|
+
.map(([controller, staticMethods]) => {
|
|
44
|
+
const prefix = controller._prefix ?? '';
|
|
45
|
+
if (!controller._activated) {
|
|
46
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Controller "${controller.name}" found but not activated`);
|
|
47
|
+
}
|
|
48
|
+
return Object.entries(staticMethods).map(([path, staticMethod]) => {
|
|
49
|
+
const fullPath = [prefix, path].filter(Boolean).join('/');
|
|
50
|
+
return [fullPath, { staticMethod, controller }];
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
.flat());
|
|
54
|
+
const getHandler = () => {
|
|
55
|
+
if (Object.keys(params).length === 0) {
|
|
56
|
+
return handlers[''];
|
|
57
|
+
}
|
|
58
|
+
const path = params[Object.keys(params)[0]];
|
|
59
|
+
const allMethodKeys = Object.keys(handlers);
|
|
60
|
+
let methodKeys = [];
|
|
61
|
+
methodKeys = allMethodKeys
|
|
62
|
+
// First, try to match literal routes exactly.
|
|
63
|
+
.filter((p) => {
|
|
64
|
+
if (p.includes(':'))
|
|
65
|
+
return false; // Skip parameterized paths
|
|
66
|
+
return p === path.join('/');
|
|
67
|
+
});
|
|
68
|
+
if (!methodKeys.length) {
|
|
69
|
+
methodKeys = allMethodKeys.filter((p) => {
|
|
70
|
+
const routeSegments = p.split('/');
|
|
71
|
+
if (routeSegments.length !== path.length)
|
|
72
|
+
return false;
|
|
73
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
|
74
|
+
const routeSegment = routeSegments[i];
|
|
75
|
+
const pathSegment = path[i];
|
|
76
|
+
if (routeSegment.startsWith(':')) {
|
|
77
|
+
const parameter = routeSegment.slice(1);
|
|
78
|
+
if (parameter in methodParams) {
|
|
79
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Duplicate parameter "${parameter}"`);
|
|
80
|
+
}
|
|
81
|
+
// If it's a parameterized segment, capture the parameter value.
|
|
82
|
+
methodParams[parameter] = pathSegment;
|
|
83
|
+
}
|
|
84
|
+
else if (routeSegment !== pathSegment) {
|
|
85
|
+
// If it's a literal segment and it does not match the corresponding path segment, return false.
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (methodKeys.length > 1) {
|
|
93
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Conflicting routes found: ${methodKeys.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
const [methodKey] = methodKeys;
|
|
96
|
+
if (methodKey) {
|
|
97
|
+
return handlers[methodKey];
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
101
|
+
const handler = getHandler();
|
|
102
|
+
if (!handler) {
|
|
103
|
+
return this.#respondWithError(types_1._HttpStatus.NOT_FOUND, 'Route is not found');
|
|
104
|
+
}
|
|
105
|
+
const { staticMethod, controller } = handler;
|
|
106
|
+
try {
|
|
107
|
+
const result = await staticMethod.call(this, req, methodParams);
|
|
108
|
+
if (result instanceof Response) {
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
if (typeof result !== 'undefined') {
|
|
112
|
+
return this.#respond(200, result);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
const err = e;
|
|
117
|
+
try {
|
|
118
|
+
controller._onError?.(err);
|
|
119
|
+
}
|
|
120
|
+
catch (onErrorError) {
|
|
121
|
+
// eslint-disable-next-line no-console
|
|
122
|
+
console.error(onErrorError);
|
|
123
|
+
}
|
|
124
|
+
if (err.message !== 'NEXT_REDIRECT') {
|
|
125
|
+
const statusCode = err.statusCode ?? types_1._HttpStatus.INTERNAL_SERVER_ERROR;
|
|
126
|
+
return this.#respondWithError(statusCode, err.message);
|
|
127
|
+
}
|
|
128
|
+
throw e; // if NEXT_REDIRECT rethrow it
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
exports._Segment = _Segment;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { _KnownAny as KnownAny, _StreamAbortMessage as StreamAbortMessage } from './types';
|
|
2
|
+
export declare class _StreamResponse<T> extends Response {
|
|
3
|
+
static readonly JSON_DIVIDER = "__##DIV123##__";
|
|
4
|
+
static defaultHeaders: {
|
|
5
|
+
'Content-Type': string;
|
|
6
|
+
Connection: string;
|
|
7
|
+
'Content-Encoding': string;
|
|
8
|
+
'Cache-Control': string;
|
|
9
|
+
};
|
|
10
|
+
readonly writer: WritableStreamDefaultWriter;
|
|
11
|
+
readonly encoder: TextEncoder;
|
|
12
|
+
constructor(init?: ResponseInit);
|
|
13
|
+
send(data: T | StreamAbortMessage): Promise<void>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
throw(e: KnownAny): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._StreamResponse = void 0;
|
|
4
|
+
class _StreamResponse extends Response {
|
|
5
|
+
static JSON_DIVIDER = '__##DIV123##__'; // protects collisions with JSON data
|
|
6
|
+
static defaultHeaders = {
|
|
7
|
+
'Content-Type': 'text/event-stream',
|
|
8
|
+
Connection: 'keep-alive',
|
|
9
|
+
'Content-Encoding': 'none',
|
|
10
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
11
|
+
};
|
|
12
|
+
writer;
|
|
13
|
+
encoder;
|
|
14
|
+
constructor(init) {
|
|
15
|
+
const responseStream = new TransformStream();
|
|
16
|
+
const writer = responseStream.writable.getWriter();
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
super(responseStream.readable, {
|
|
19
|
+
...init,
|
|
20
|
+
headers: init?.headers ?? _StreamResponse.defaultHeaders,
|
|
21
|
+
});
|
|
22
|
+
this.writer = writer;
|
|
23
|
+
this.encoder = encoder;
|
|
24
|
+
}
|
|
25
|
+
send(data) {
|
|
26
|
+
const { writer, encoder } = this;
|
|
27
|
+
return writer.write(encoder.encode(JSON.stringify(data) + _StreamResponse.JSON_DIVIDER));
|
|
28
|
+
}
|
|
29
|
+
close() {
|
|
30
|
+
const { writer } = this;
|
|
31
|
+
return writer.close();
|
|
32
|
+
}
|
|
33
|
+
async throw(e) {
|
|
34
|
+
const { writer } = this;
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
36
|
+
await this.send({ isError: true, reason: e instanceof Error ? e.message : e });
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
38
|
+
return writer.abort(e);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports._StreamResponse = _StreamResponse;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type _VovkControllerMetadataJson as VovkControllerMetadataJson } from '../types';
|
|
2
|
+
import { type _VovkClientOptions as VovkClientOptions, type _VovkClient as VovkClient } from './types';
|
|
3
|
+
export declare const _clientizeController: <T, OPTS extends Record<string, any> = Record<string, never>>(givenController: VovkControllerMetadataJson, options?: VovkClientOptions<OPTS> | undefined) => VovkClient<T, OPTS>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._clientizeController = void 0;
|
|
4
|
+
const defaultStreamFetcher_1 = require("./defaultStreamFetcher");
|
|
5
|
+
const _1 = require(".");
|
|
6
|
+
const trimPath = (path) => path.trim().replace(/^\/|\/$/g, '');
|
|
7
|
+
const getHandlerPath = (endpoint, params, query) => {
|
|
8
|
+
let result = endpoint;
|
|
9
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
10
|
+
result = result.replace(`:${key}`, value);
|
|
11
|
+
}
|
|
12
|
+
const searchParams = new URLSearchParams();
|
|
13
|
+
let hasQuery = false;
|
|
14
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
15
|
+
searchParams.set(key, value);
|
|
16
|
+
hasQuery = true;
|
|
17
|
+
}
|
|
18
|
+
return `${result}${hasQuery ? '?' : ''}${searchParams.toString()}`;
|
|
19
|
+
};
|
|
20
|
+
const _clientizeController = (givenController, options) => {
|
|
21
|
+
const controller = givenController;
|
|
22
|
+
const client = {};
|
|
23
|
+
if (!controller)
|
|
24
|
+
throw new Error(`Unable to clientize. Controller metadata is not provided`);
|
|
25
|
+
const metadata = controller._handlers;
|
|
26
|
+
if (!metadata)
|
|
27
|
+
throw new Error(`Unable to clientize. No metadata for controller ${String(controller?.controllerName)}`);
|
|
28
|
+
const prefix = trimPath(controller._prefix ?? '');
|
|
29
|
+
const { fetcher = _1.defaultFetcher, streamFetcher = defaultStreamFetcher_1._defaultStreamFetcher } = options ?? {};
|
|
30
|
+
for (const [staticMethodName, { path, httpMethod, clientValidators }] of Object.entries(metadata)) {
|
|
31
|
+
const getPath = (params, query) => getHandlerPath([prefix, path].filter(Boolean).join('/'), params, query);
|
|
32
|
+
const validate = ({ body, query }) => {
|
|
33
|
+
if (options?.disableClientValidation)
|
|
34
|
+
return;
|
|
35
|
+
return options?.validateOnClient?.({ body, query }, clientValidators ?? {});
|
|
36
|
+
};
|
|
37
|
+
const handler = (input = {}) => {
|
|
38
|
+
const internalOptions = {
|
|
39
|
+
name: staticMethodName,
|
|
40
|
+
httpMethod,
|
|
41
|
+
getPath,
|
|
42
|
+
validate,
|
|
43
|
+
};
|
|
44
|
+
const internalInput = {
|
|
45
|
+
...input,
|
|
46
|
+
body: input.body ?? null,
|
|
47
|
+
query: input.query ?? {},
|
|
48
|
+
params: input.params ?? {},
|
|
49
|
+
...options?.defaultOptions,
|
|
50
|
+
};
|
|
51
|
+
if (input.isStream) {
|
|
52
|
+
if (!streamFetcher)
|
|
53
|
+
throw new Error('Stream fetcher is not provided');
|
|
54
|
+
const fetcherPromise = streamFetcher(internalOptions, internalInput);
|
|
55
|
+
return fetcherPromise;
|
|
56
|
+
}
|
|
57
|
+
const fetcherPromise = fetcher(internalOptions, internalInput);
|
|
58
|
+
if (!(fetcherPromise instanceof Promise))
|
|
59
|
+
throw new Error('Fetcher must return a promise');
|
|
60
|
+
return fetcherPromise;
|
|
61
|
+
};
|
|
62
|
+
// @ts-expect-error TODO: Fix this
|
|
63
|
+
client[staticMethodName] = handler;
|
|
64
|
+
}
|
|
65
|
+
return client;
|
|
66
|
+
};
|
|
67
|
+
exports._clientizeController = _clientizeController;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type _VovkClientFetcher as VovkClientFetcher } from './types';
|
|
2
|
+
export interface _DefaultFetcherOptions extends Omit<RequestInit, 'body' | 'method'> {
|
|
3
|
+
prefix?: string;
|
|
4
|
+
isStream?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultFetcher";
|
|
7
|
+
export declare const _defaultFetcher: VovkClientFetcher<_DefaultFetcherOptions>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._defaultFetcher = 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 ErrorResponseBody type
|
|
9
|
+
const _defaultFetcher = async ({ httpMethod, getPath, validate }, { params, query, body, prefix = '', ...options }) => {
|
|
10
|
+
const endpoint = (prefix.startsWith('http://') || prefix.startsWith('https://') || prefix.startsWith('/') ? '' : '/') +
|
|
11
|
+
(prefix.endsWith('/') ? prefix : `${prefix}/`) +
|
|
12
|
+
getPath(params, query);
|
|
13
|
+
try {
|
|
14
|
+
validate({ body, query });
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
// if HttpException is thrown, rethrow it
|
|
18
|
+
if (e instanceof HttpException_1._HttpException)
|
|
19
|
+
throw e;
|
|
20
|
+
// otherwise, throw HttpException with status 0
|
|
21
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
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 result;
|
|
34
|
+
let response;
|
|
35
|
+
try {
|
|
36
|
+
response = await fetch(endpoint, init);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// handle network errors
|
|
40
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e?.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
result = await response.json();
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
// handle parsing errors
|
|
47
|
+
throw new HttpException_1._HttpException(response.status, e?.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
48
|
+
}
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
// handle server errors
|
|
51
|
+
throw new HttpException_1._HttpException(response.status, result?.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
exports._defaultFetcher = _defaultFetcher;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { _DefaultFetcherOptions as DefaultFetcherOptions } from './defaultFetcher';
|
|
2
|
+
import type { _VovkClientFetcher as VovkClientFetcher } from './types';
|
|
3
|
+
export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultStreamFetcher";
|
|
4
|
+
export declare const _defaultStreamFetcher: VovkClientFetcher<DefaultFetcherOptions>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._defaultStreamFetcher = exports.DEFAULT_ERROR_MESSAGE = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const HttpException_1 = require("../HttpException");
|
|
6
|
+
const StreamResponse_1 = require("../StreamResponse");
|
|
7
|
+
exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultStreamFetcher';
|
|
8
|
+
const _defaultStreamFetcher = async ({ httpMethod, getPath, validate }, { params, query, body, prefix = '', ...options }) => {
|
|
9
|
+
const endpoint = (prefix.startsWith('http://') || prefix.startsWith('https://') || prefix.startsWith('/') ? '' : '/') +
|
|
10
|
+
(prefix.endsWith('/') ? prefix : `${prefix}/`) +
|
|
11
|
+
getPath(params, query);
|
|
12
|
+
try {
|
|
13
|
+
validate({ body, query });
|
|
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
|
+
const init = {
|
|
23
|
+
method: httpMethod,
|
|
24
|
+
...options,
|
|
25
|
+
};
|
|
26
|
+
if (body instanceof FormData) {
|
|
27
|
+
init.body = body;
|
|
28
|
+
}
|
|
29
|
+
else if (body) {
|
|
30
|
+
init.body = JSON.stringify(body);
|
|
31
|
+
}
|
|
32
|
+
let response;
|
|
33
|
+
try {
|
|
34
|
+
response = await fetch(endpoint, init);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
// handle network errors
|
|
38
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
39
|
+
}
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = await response.json();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// ignore parsing errors
|
|
47
|
+
}
|
|
48
|
+
// handle server errors
|
|
49
|
+
throw new HttpException_1._HttpException(response.status, result.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
50
|
+
}
|
|
51
|
+
if (!response.body)
|
|
52
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, 'Stream body is falsy. Check your controller code.');
|
|
53
|
+
const reader = response.body.getReader();
|
|
54
|
+
async function* iterator() {
|
|
55
|
+
let prepend = '';
|
|
56
|
+
// Remove the Promise wrapper, as we will use async generator
|
|
57
|
+
while (true) {
|
|
58
|
+
let value;
|
|
59
|
+
let done = false;
|
|
60
|
+
try {
|
|
61
|
+
({ value, done } = await reader.read());
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
await reader.cancel();
|
|
65
|
+
throw new Error('Stream error. ' + String(error));
|
|
66
|
+
}
|
|
67
|
+
if (done) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const string = new TextDecoder().decode(value);
|
|
71
|
+
const lines = (prepend + string).split(StreamResponse_1._StreamResponse.JSON_DIVIDER).filter(Boolean);
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
let data;
|
|
74
|
+
try {
|
|
75
|
+
data = JSON.parse(line);
|
|
76
|
+
prepend = '';
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
prepend += string;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
if (data) {
|
|
83
|
+
if ('isError' in data && 'reason' in data) {
|
|
84
|
+
const upcomingError = data.reason;
|
|
85
|
+
await reader.cancel();
|
|
86
|
+
if (typeof upcomingError === 'string') {
|
|
87
|
+
throw new Error(upcomingError);
|
|
88
|
+
}
|
|
89
|
+
throw upcomingError;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
yield data;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
[Symbol.asyncIterator]: iterator,
|
|
100
|
+
cancel: () => reader.cancel(),
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
exports._defaultStreamFetcher = _defaultStreamFetcher;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { _clientizeController as clientizeController } from './clientizeController';
|
|
2
|
+
import type { _VovkClientFetcher as VovkClientFetcher, _VovkClientOptions as VovkClientOptions } from './types';
|
|
3
|
+
import { type _DefaultFetcherOptions as DefaultFetcherOptions, _defaultFetcher as defaultFetcher } from './defaultFetcher';
|
|
4
|
+
export { clientizeController, defaultFetcher };
|
|
5
|
+
export type { VovkClientFetcher, VovkClientOptions, DefaultFetcherOptions };
|
package/client/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultFetcher = exports.clientizeController = void 0;
|
|
4
|
+
const clientizeController_1 = require("./clientizeController");
|
|
5
|
+
Object.defineProperty(exports, "clientizeController", { enumerable: true, get: function () { return clientizeController_1._clientizeController; } });
|
|
6
|
+
const defaultFetcher_1 = require("./defaultFetcher");
|
|
7
|
+
Object.defineProperty(exports, "defaultFetcher", { enumerable: true, get: function () { return defaultFetcher_1._defaultFetcher; } });
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { _KnownAny as KnownAny, _HttpMethod as HttpMethod, _ControllerStaticMethod, _VovkBody, _VovkQuery, _VovkParams } from '../types';
|
|
2
|
+
import { _StreamResponse as StreamResponse } from '../StreamResponse';
|
|
3
|
+
export type _StaticMethodInput<T extends _ControllerStaticMethod> = (_VovkBody<T> extends undefined | void ? {
|
|
4
|
+
body?: undefined;
|
|
5
|
+
} : _VovkBody<T> extends null ? {
|
|
6
|
+
body?: null;
|
|
7
|
+
} : {
|
|
8
|
+
body: _VovkBody<T>;
|
|
9
|
+
}) & (_VovkQuery<T> extends undefined | void ? {
|
|
10
|
+
query?: undefined;
|
|
11
|
+
} : {
|
|
12
|
+
query: _VovkQuery<T>;
|
|
13
|
+
}) & (_VovkParams<T> extends undefined | void ? {
|
|
14
|
+
params?: undefined;
|
|
15
|
+
} : {
|
|
16
|
+
params: _VovkParams<T>;
|
|
17
|
+
});
|
|
18
|
+
type ToPromise<T> = T extends PromiseLike<unknown> ? T : Promise<T>;
|
|
19
|
+
export type _StreamAsyncIterator<T> = {
|
|
20
|
+
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
21
|
+
cancel: () => Promise<void> | void;
|
|
22
|
+
};
|
|
23
|
+
type ClientMethod<T extends (...args: KnownAny[]) => void | object | StreamResponse<STREAM> | Promise<StreamResponse<STREAM>>, OPTS extends Record<string, KnownAny>, STREAM extends KnownAny = unknown> = <R>(options: (_StaticMethodInput<T> extends {
|
|
24
|
+
body?: undefined | null;
|
|
25
|
+
query?: undefined;
|
|
26
|
+
params?: undefined;
|
|
27
|
+
} ? unknown : Parameters<T>[0] extends void ? _StaticMethodInput<T>['params'] extends object ? {
|
|
28
|
+
params: _StaticMethodInput<T>['params'];
|
|
29
|
+
} : unknown : _StaticMethodInput<T>) & (Partial<OPTS> | void)) => ReturnType<T> extends Promise<StreamResponse<infer U>> | StreamResponse<infer U> ? Promise<_StreamAsyncIterator<U>> : R extends object ? Promise<R> : ToPromise<ReturnType<T>>;
|
|
30
|
+
export type _VovkClient<T, OPTS extends {
|
|
31
|
+
[key: string]: KnownAny;
|
|
32
|
+
}> = {
|
|
33
|
+
[K in keyof T]: T[K] extends (...args: KnownAny) => KnownAny ? ClientMethod<T[K], OPTS> : never;
|
|
34
|
+
};
|
|
35
|
+
export type _VovkClientFetcher<OPTS extends Record<string, KnownAny> = Record<string, never>, T = KnownAny> = (options: {
|
|
36
|
+
name: keyof T;
|
|
37
|
+
httpMethod: HttpMethod;
|
|
38
|
+
getPath: (params: {
|
|
39
|
+
[key: string]: string;
|
|
40
|
+
}, query: {
|
|
41
|
+
[key: string]: string;
|
|
42
|
+
}) => string;
|
|
43
|
+
validate: (input: {
|
|
44
|
+
body?: unknown;
|
|
45
|
+
query?: unknown;
|
|
46
|
+
}) => void;
|
|
47
|
+
}, input: {
|
|
48
|
+
body: unknown;
|
|
49
|
+
query: {
|
|
50
|
+
[key: string]: string;
|
|
51
|
+
};
|
|
52
|
+
params: {
|
|
53
|
+
[key: string]: string;
|
|
54
|
+
};
|
|
55
|
+
} & OPTS) => KnownAny;
|
|
56
|
+
export type _VovkClientOptions<OPTS extends Record<string, KnownAny> = Record<string, never>> = {
|
|
57
|
+
disableClientValidation?: boolean;
|
|
58
|
+
fetcher?: _VovkClientFetcher<OPTS>;
|
|
59
|
+
streamFetcher?: _VovkClientFetcher<OPTS> | null;
|
|
60
|
+
validateOnClient?: (input: {
|
|
61
|
+
body?: unknown;
|
|
62
|
+
query?: unknown;
|
|
63
|
+
}, validators: {
|
|
64
|
+
body?: unknown;
|
|
65
|
+
query?: unknown;
|
|
66
|
+
}) => unknown;
|
|
67
|
+
defaultOptions?: Partial<OPTS>;
|
|
68
|
+
};
|
|
69
|
+
export {};
|
package/client/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { _KnownAny as KnownAny, _VovkController as VovkController, _VovkRequest as VovkRequest } from './types';
|
|
2
|
+
type Next = () => Promise<unknown>;
|
|
3
|
+
export declare function _createDecorator<ARGS extends unknown[], REQUEST = VovkRequest<unknown>>(handler: (this: VovkController, req: REQUEST, next: Next, ...args: ARGS) => unknown, initHandler?: (this: VovkController, ...args: ARGS) => {
|
|
4
|
+
clientValidators?: {
|
|
5
|
+
body?: KnownAny;
|
|
6
|
+
query?: KnownAny;
|
|
7
|
+
};
|
|
8
|
+
} | null | undefined): (...args: ARGS) => (target: KnownAny, propertyKey: string) => void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports._createDecorator = void 0;
|
|
4
|
+
function _createDecorator(handler, initHandler) {
|
|
5
|
+
return function decoratorCreator(...args) {
|
|
6
|
+
return function decorator(target, propertyKey) {
|
|
7
|
+
const controller = target;
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
9
|
+
const originalMethod = controller[propertyKey];
|
|
10
|
+
if (typeof originalMethod === 'function') {
|
|
11
|
+
const initResult = initHandler?.call(controller, ...args);
|
|
12
|
+
if (initResult?.clientValidators) {
|
|
13
|
+
controller._handlers = {
|
|
14
|
+
...controller._handlers,
|
|
15
|
+
[propertyKey]: {
|
|
16
|
+
...controller._handlers?.[propertyKey],
|
|
17
|
+
clientValidators: initResult.clientValidators,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
initResult.clientValidators;
|
|
21
|
+
}
|
|
22
|
+
const method = function method(req, params) {
|
|
23
|
+
const next = async () => {
|
|
24
|
+
return (await originalMethod.call(controller, req, params));
|
|
25
|
+
};
|
|
26
|
+
return handler.call(controller, req, next, ...args);
|
|
27
|
+
};
|
|
28
|
+
// method._name = (originalMethod as { _name?: string })._name ?? originalMethod.name;
|
|
29
|
+
method._controller = controller;
|
|
30
|
+
// TODO define internal method type
|
|
31
|
+
originalMethod._controller = controller;
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
33
|
+
controller[propertyKey] = method;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
throw new Error(`Unable to decorate: ${propertyKey} is not a function`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
exports._createDecorator = _createDecorator;
|