vovk 3.0.0-draft.17 → 3.0.0-draft.170

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.
Files changed (152) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -96
  3. package/cjs/HttpException.d.ts +7 -0
  4. package/cjs/HttpException.js +15 -0
  5. package/cjs/JSONLinesResponse.d.ts +14 -0
  6. package/cjs/JSONLinesResponse.js +57 -0
  7. package/cjs/VovkApp.d.ts +50 -0
  8. package/cjs/VovkApp.js +199 -0
  9. package/cjs/client/createRPC.d.ts +11 -0
  10. package/cjs/client/createRPC.js +92 -0
  11. package/cjs/client/defaultHandler.d.ts +2 -0
  12. package/cjs/client/defaultHandler.js +25 -0
  13. package/cjs/client/defaultStreamHandler.d.ts +4 -0
  14. package/cjs/client/defaultStreamHandler.js +79 -0
  15. package/cjs/client/fetcher.d.ts +14 -0
  16. package/cjs/client/fetcher.js +92 -0
  17. package/cjs/client/index.d.ts +10 -0
  18. package/cjs/client/index.js +23 -0
  19. package/cjs/client/types.d.ts +194 -0
  20. package/cjs/client/types.js +2 -0
  21. package/cjs/createDecorator.d.ts +19 -0
  22. package/cjs/createDecorator.js +44 -0
  23. package/cjs/createVovkApp.d.ts +118 -0
  24. package/cjs/createVovkApp.js +138 -0
  25. package/cjs/index.d.ts +61 -0
  26. package/cjs/index.js +29 -0
  27. package/cjs/types.d.ts +345 -0
  28. package/cjs/types.js +72 -0
  29. package/cjs/utils/createDecorator.d.ts +19 -0
  30. package/cjs/utils/createDecorator.js +46 -0
  31. package/cjs/utils/createLLMFunctions.d.ts +57 -0
  32. package/cjs/utils/createLLMFunctions.js +95 -0
  33. package/cjs/utils/generateStaticAPI.d.ts +7 -0
  34. package/cjs/utils/generateStaticAPI.js +30 -0
  35. package/cjs/utils/getSchema.d.ts +24 -0
  36. package/cjs/utils/getSchema.js +41 -0
  37. package/cjs/utils/multitenant.d.ts +26 -0
  38. package/cjs/utils/multitenant.js +170 -0
  39. package/cjs/utils/parseQuery.d.ts +25 -0
  40. package/cjs/utils/parseQuery.js +147 -0
  41. package/cjs/utils/reqForm.d.ts +2 -0
  42. package/cjs/utils/reqForm.js +29 -0
  43. package/cjs/utils/reqMeta.d.ts +5 -0
  44. package/cjs/utils/reqMeta.js +12 -0
  45. package/{utils → cjs/utils}/reqQuery.d.ts +1 -2
  46. package/cjs/utils/reqQuery.js +12 -0
  47. package/cjs/utils/serializeQuery.d.ts +13 -0
  48. package/cjs/utils/serializeQuery.js +64 -0
  49. package/cjs/utils/setHandlerSchema.d.ts +7 -0
  50. package/cjs/utils/setHandlerSchema.js +17 -0
  51. package/cjs/utils/shim.js +17 -0
  52. package/cjs/utils/withValidation.d.ts +47 -0
  53. package/cjs/utils/withValidation.js +122 -0
  54. package/mjs/HttpException.d.ts +7 -0
  55. package/mjs/HttpException.js +15 -0
  56. package/mjs/JSONLinesResponse.d.ts +14 -0
  57. package/mjs/JSONLinesResponse.js +57 -0
  58. package/mjs/VovkApp.d.ts +50 -0
  59. package/mjs/VovkApp.js +199 -0
  60. package/mjs/client/createRPC.d.ts +11 -0
  61. package/mjs/client/createRPC.js +92 -0
  62. package/mjs/client/defaultHandler.d.ts +2 -0
  63. package/mjs/client/defaultHandler.js +25 -0
  64. package/mjs/client/defaultStreamHandler.d.ts +4 -0
  65. package/mjs/client/defaultStreamHandler.js +79 -0
  66. package/mjs/client/fetcher.d.ts +14 -0
  67. package/mjs/client/fetcher.js +92 -0
  68. package/mjs/client/index.d.ts +10 -0
  69. package/mjs/client/index.js +23 -0
  70. package/mjs/client/types.d.ts +194 -0
  71. package/mjs/client/types.js +2 -0
  72. package/mjs/createDecorator.d.ts +19 -0
  73. package/mjs/createDecorator.js +44 -0
  74. package/mjs/createVovkApp.d.ts +118 -0
  75. package/mjs/createVovkApp.js +138 -0
  76. package/mjs/index.d.ts +61 -0
  77. package/mjs/index.js +29 -0
  78. package/mjs/types.d.ts +345 -0
  79. package/mjs/types.js +72 -0
  80. package/mjs/utils/createDecorator.d.ts +19 -0
  81. package/mjs/utils/createDecorator.js +46 -0
  82. package/mjs/utils/createLLMFunctions.d.ts +57 -0
  83. package/mjs/utils/createLLMFunctions.js +95 -0
  84. package/mjs/utils/generateStaticAPI.d.ts +7 -0
  85. package/mjs/utils/generateStaticAPI.js +30 -0
  86. package/mjs/utils/getSchema.d.ts +24 -0
  87. package/mjs/utils/getSchema.js +41 -0
  88. package/mjs/utils/multitenant.d.ts +26 -0
  89. package/mjs/utils/multitenant.js +170 -0
  90. package/mjs/utils/parseQuery.d.ts +25 -0
  91. package/mjs/utils/parseQuery.js +147 -0
  92. package/mjs/utils/reqForm.d.ts +2 -0
  93. package/mjs/utils/reqForm.js +29 -0
  94. package/mjs/utils/reqMeta.d.ts +5 -0
  95. package/mjs/utils/reqMeta.js +12 -0
  96. package/mjs/utils/reqQuery.d.ts +2 -0
  97. package/mjs/utils/reqQuery.js +12 -0
  98. package/mjs/utils/serializeQuery.d.ts +13 -0
  99. package/mjs/utils/serializeQuery.js +64 -0
  100. package/mjs/utils/setHandlerSchema.d.ts +7 -0
  101. package/mjs/utils/setHandlerSchema.js +17 -0
  102. package/mjs/utils/shim.d.ts +1 -0
  103. package/mjs/utils/shim.js +18 -0
  104. package/mjs/utils/withValidation.d.ts +47 -0
  105. package/mjs/utils/withValidation.js +122 -0
  106. package/package.json +15 -5
  107. package/.npmignore +0 -2
  108. package/HttpException.d.ts +0 -7
  109. package/HttpException.js +0 -15
  110. package/Segment.d.ts +0 -28
  111. package/Segment.js +0 -182
  112. package/StreamResponse.d.ts +0 -16
  113. package/StreamResponse.js +0 -53
  114. package/client/clientizeController.d.ts +0 -4
  115. package/client/clientizeController.js +0 -93
  116. package/client/defaultFetcher.d.ts +0 -4
  117. package/client/defaultFetcher.js +0 -49
  118. package/client/defaultHandler.d.ts +0 -2
  119. package/client/defaultHandler.js +0 -21
  120. package/client/defaultStreamHandler.d.ts +0 -4
  121. package/client/defaultStreamHandler.js +0 -82
  122. package/client/index.d.ts +0 -4
  123. package/client/index.js +0 -5
  124. package/client/types.d.ts +0 -100
  125. package/client/types.js +0 -2
  126. package/createDecorator.d.ts +0 -4
  127. package/createDecorator.js +0 -38
  128. package/createSegment.d.ts +0 -62
  129. package/createSegment.js +0 -118
  130. package/generateStaticAPI.d.ts +0 -4
  131. package/generateStaticAPI.js +0 -18
  132. package/index.d.ts +0 -60
  133. package/index.js +0 -20
  134. package/types.d.ts +0 -186
  135. package/types.js +0 -65
  136. package/utils/getSchema.d.ts +0 -8
  137. package/utils/getSchema.js +0 -38
  138. package/utils/reqMeta.d.ts +0 -3
  139. package/utils/reqMeta.js +0 -13
  140. package/utils/reqQuery.js +0 -25
  141. package/utils/setClientValidatorsForHandler.d.ts +0 -5
  142. package/utils/setClientValidatorsForHandler.js +0 -28
  143. package/utils/shim.js +0 -17
  144. package/worker/index.d.ts +0 -3
  145. package/worker/index.js +0 -7
  146. package/worker/promisifyWorker.d.ts +0 -2
  147. package/worker/promisifyWorker.js +0 -143
  148. package/worker/types.d.ts +0 -31
  149. package/worker/types.js +0 -2
  150. package/worker/worker.d.ts +0 -1
  151. package/worker/worker.js +0 -44
  152. /package/{utils → cjs/utils}/shim.d.ts +0 -0
package/LICENSE CHANGED
@@ -16,7 +16,7 @@
16
16
  "nextjs",
17
17
  "router"
18
18
  ],
19
- "author": "Andrii Gubanov",
19
+ "author": "Andrey Gubanov",
20
20
  "license": "MIT",
21
21
  "bugs": {
22
22
  "url": "https://github.com/finom/vovk/issues"
package/README.md CHANGED
@@ -4,109 +4,21 @@
4
4
  <source width="300" media="(prefers-color-scheme: light)" srcset="https://vovk.dev/vovk-logo.svg">
5
5
  <img width="300" alt="vovk" src="https://vovk.dev/vovk-logo.svg">
6
6
  </picture><br>
7
- <strong>RESTful RPC for Next.js</strong>
8
-
7
+ <strong>RESTful + RPC = ♥️</strong>
9
8
  </p>
10
9
 
11
10
  <p align="center">
12
- Transforms <a href="https://nextjs.org/docs/app">Next.js</a> into a powerful REST API platform with RPC capabilities.
13
- <br><br>
14
- ℹ️ Improved syntax for Zod and DTO validation is coming soon. Stay tuned!
11
+ Back-end meta-framework for <a href="https://nextjs.org/docs/app">Next.js</a>
15
12
  </p>
16
13
 
17
- <p align="center">
18
- <a href="https://vovk.dev/">Documentation</a>&nbsp;&nbsp;&nbsp;&nbsp;
19
- <a href="https://discord.gg/qdT8WEHUuP">Discord</a>&nbsp;&nbsp;&nbsp;&nbsp;
20
- <a href="https://github.com/finom/vovk-examples">Code Examples</a>&nbsp;&nbsp;&nbsp;&nbsp;
21
- <a href="https://github.com/finom/vovk-zod">vovk-zod</a>&nbsp;&nbsp;&nbsp;&nbsp;
22
- <a href="https://github.com/finom/vovk-hello-world">vovk-hello-world</a>&nbsp;&nbsp;&nbsp;&nbsp;
23
- <a href="https://github.com/finom/vovk-react-native-example">vovk-react-native-example</a>
24
- </p>
25
- <p align="center">
26
- <a href="https://www.npmjs.com/package/vovk"><img src="https://badge.fury.io/js/vovk.svg" alt="npm version" /></a>&nbsp;
27
- <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg" alt="TypeScript" /></a>&nbsp;
28
- <a href="https://github.com/finom/vovk/actions/workflows/main.yml"><img src="https://github.com/finom/vovk/actions/workflows/main.yml/badge.svg" alt="Build status" /></a>
29
- </p>
14
+ ---
30
15
 
31
- <br />
16
+ ## vovk [![npm version](https://badge.fury.io/js/vovk.svg)](https://www.npmjs.com/package/vovk)
32
17
 
33
- Example back-end Controller Class:
18
+ The main library with [zero dependencies](https://bundlephobia.com/result?p=vovk) that's going to be used in production. It provides a wrapper for Next.js API routes, internal RPC API, utilities and types.
34
19
 
35
- ```ts
36
- // /src/modules/post/PostController.ts
37
- import { get, prefix, type VovkRequest } from 'vovk';
38
- import PostService from './PostService';
39
-
40
- @prefix('posts')
41
- export default class PostController {
42
- /**
43
- * Create a comment on a post
44
- * POST /api/posts/:postId/comments
45
- */
46
- @post(':postId/comments')
47
- static async createComment(
48
- // decorate NextRequest type with body and query types
49
- req: VovkRequest<{ content: string; userId: string }, { notificationType: 'push' | 'email' }>,
50
- { postId }: { postId: string } // params
51
- ) {
52
- // use standard Next.js API to get body and query
53
- const { content, userId } = await req.json();
54
- const notificationType = req.nextUrl.searchParams.get('notificationType');
55
-
56
- // perform the request to the database in a custom service
57
- return PostService.createComment({
58
- postId,
59
- content,
60
- userId,
61
- notificationType,
62
- });
63
- }
64
- }
20
+ ```sh
21
+ npm install vovk
65
22
  ```
66
23
 
67
- Example component that uses the auto-generated client library:
68
-
69
- ```tsx
70
- 'use client';
71
- import { useState } from 'react';
72
- import { PostController } from 'vovk-client';
73
- import type { VovkReturnType } from 'vovk';
74
-
75
- export default function Example() {
76
- const [response, setResponse] = useState<VovkReturnType<typeof PostController.createComment>>();
77
-
78
- return (
79
- <>
80
- <button
81
- onClick={async () =>
82
- setResponse(
83
- await PostController.createComment({
84
- body: {
85
- content: 'Hello, World!',
86
- userId: '1',
87
- },
88
- params: { postId: '69' },
89
- query: { notificationType: 'push' },
90
- })
91
- )
92
- }
93
- >
94
- Post a comment
95
- </button>
96
- <div>{JSON.stringify(response)}</div>
97
- </>
98
- );
99
- }
100
- ```
101
-
102
- Alternatively, the resource can be fetched wit the regular `fetch` function:
103
-
104
- ```ts
105
- fetch('/api/posts/69?notificationType=push', {
106
- method: 'POST',
107
- body: JSON.stringify({
108
- content: 'Hello, World!',
109
- userId: '1',
110
- }),
111
- });
112
- ```
24
+ For more information, please visit the [getting started guide](https://vovk.dev/getting-started) or check out the [Vovk.ts examples](https://vovk-examples.vercel.app/).
@@ -0,0 +1,7 @@
1
+ import type { HttpStatus } from './types.js';
2
+ export declare class HttpException extends Error {
3
+ statusCode: HttpStatus;
4
+ message: string;
5
+ cause?: unknown;
6
+ constructor(statusCode: HttpStatus, message: string, cause?: unknown);
7
+ }
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.HttpException = void 0;
4
+ class HttpException extends Error {
5
+ statusCode;
6
+ message;
7
+ cause;
8
+ constructor(statusCode, message, cause) {
9
+ super(message);
10
+ this.statusCode = statusCode;
11
+ this.message = message;
12
+ this.cause = cause;
13
+ }
14
+ }
15
+ exports.HttpException = HttpException;
@@ -0,0 +1,14 @@
1
+ import type { headers } from 'next/headers';
2
+ import type { KnownAny, StreamAbortMessage } from './types.js';
3
+ import './utils/shim.js';
4
+ export declare class JSONLinesResponse<T> extends Response {
5
+ isClosed: boolean;
6
+ controller?: ReadableStreamDefaultController;
7
+ readonly encoder: TextEncoder;
8
+ readonly readableStream: ReadableStream;
9
+ constructor(requestHeaders: Awaited<ReturnType<typeof headers>>, init?: ResponseInit);
10
+ send(data: T | StreamAbortMessage): void;
11
+ close(): void;
12
+ throw(e: KnownAny): void;
13
+ [Symbol.dispose](): void;
14
+ }
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.JSONLinesResponse = void 0;
4
+ require('./utils/shim.js');
5
+ class JSONLinesResponse extends Response {
6
+ isClosed = false;
7
+ controller;
8
+ encoder;
9
+ readableStream;
10
+ constructor(requestHeaders, init) {
11
+ const encoder = new TextEncoder();
12
+ let readableController;
13
+ const readableStream = new ReadableStream({
14
+ cancel: () => {
15
+ this.isClosed = true;
16
+ },
17
+ start: (controller) => {
18
+ readableController = controller;
19
+ },
20
+ });
21
+ if (!requestHeaders) {
22
+ throw new Error('Request headers are required');
23
+ }
24
+ const accept = requestHeaders.get('accept');
25
+ super(readableStream, {
26
+ ...init,
27
+ headers: {
28
+ ...init?.headers,
29
+ 'Content-Type': accept?.includes('application/jsonl')
30
+ ? 'application/jsonl; charset=utf-8'
31
+ : 'text/plain; charset=utf-8',
32
+ },
33
+ });
34
+ this.readableStream = readableStream;
35
+ this.encoder = encoder;
36
+ this.controller = readableController;
37
+ }
38
+ send(data) {
39
+ const { controller, encoder } = this;
40
+ if (this.isClosed) return;
41
+ return controller?.enqueue(encoder.encode(JSON.stringify(data) + '\n'));
42
+ }
43
+ close() {
44
+ const { controller } = this;
45
+ if (this.isClosed) return;
46
+ this.isClosed = true;
47
+ controller?.close();
48
+ }
49
+ throw(e) {
50
+ this.send({ isError: true, reason: e instanceof Error ? e.message : e });
51
+ return this.close();
52
+ }
53
+ [Symbol.dispose]() {
54
+ this.close();
55
+ }
56
+ }
57
+ exports.JSONLinesResponse = JSONLinesResponse;
@@ -0,0 +1,50 @@
1
+ import type { NextRequest } from 'next/server';
2
+ import { HttpMethod, HttpStatus, type RouteHandler, type VovkController, type DecoratorOptions } from './types.js';
3
+ export declare class VovkApp {
4
+ #private;
5
+ private static getHeadersFromOptions;
6
+ routes: Record<HttpMethod, Map<VovkController, Record<string, RouteHandler>>>;
7
+ GET: (
8
+ req: NextRequest,
9
+ data: {
10
+ params: Promise<Record<string, string[]>>;
11
+ }
12
+ ) => Promise<Response>;
13
+ POST: (
14
+ req: NextRequest,
15
+ data: {
16
+ params: Promise<Record<string, string[]>>;
17
+ }
18
+ ) => Promise<Response>;
19
+ PUT: (
20
+ req: NextRequest,
21
+ data: {
22
+ params: Promise<Record<string, string[]>>;
23
+ }
24
+ ) => Promise<Response>;
25
+ PATCH: (
26
+ req: NextRequest,
27
+ data: {
28
+ params: Promise<Record<string, string[]>>;
29
+ }
30
+ ) => Promise<Response>;
31
+ DELETE: (
32
+ req: NextRequest,
33
+ data: {
34
+ params: Promise<Record<string, string[]>>;
35
+ }
36
+ ) => Promise<Response>;
37
+ HEAD: (
38
+ req: NextRequest,
39
+ data: {
40
+ params: Promise<Record<string, string[]>>;
41
+ }
42
+ ) => Promise<Response>;
43
+ OPTIONS: (
44
+ req: NextRequest,
45
+ data: {
46
+ params: Promise<Record<string, string[]>>;
47
+ }
48
+ ) => Promise<Response>;
49
+ respond: (status: HttpStatus, body: unknown, options?: DecoratorOptions) => Response;
50
+ }
package/cjs/VovkApp.js ADDED
@@ -0,0 +1,199 @@
1
+ 'use strict';
2
+ var __importDefault =
3
+ (this && this.__importDefault) ||
4
+ function (mod) {
5
+ return mod && mod.__esModule ? mod : { default: mod };
6
+ };
7
+ var _a;
8
+ Object.defineProperty(exports, '__esModule', { value: true });
9
+ exports.VovkApp = void 0;
10
+ const types_js_1 = require('./types.js');
11
+ const HttpException_js_1 = require('./HttpException.js');
12
+ const JSONLinesResponse_js_1 = require('./JSONLinesResponse.js');
13
+ const reqQuery_js_1 = __importDefault(require('./utils/reqQuery.js'));
14
+ const reqMeta_js_1 = __importDefault(require('./utils/reqMeta.js'));
15
+ const reqForm_js_1 = __importDefault(require('./utils/reqForm.js'));
16
+ const headers_1 = require('next/headers');
17
+ class VovkApp {
18
+ static getHeadersFromOptions(options) {
19
+ if (!options) return {};
20
+ const corsHeaders = {
21
+ 'access-control-allow-origin': '*',
22
+ 'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS, HEAD',
23
+ 'access-control-allow-headers': 'content-type, authorization',
24
+ };
25
+ const headers = {
26
+ ...(options.cors ? corsHeaders : {}),
27
+ ...(options.headers ?? {}),
28
+ };
29
+ return headers;
30
+ }
31
+ routes = {
32
+ GET: new Map(),
33
+ POST: new Map(),
34
+ PUT: new Map(),
35
+ PATCH: new Map(),
36
+ DELETE: new Map(),
37
+ HEAD: new Map(),
38
+ OPTIONS: new Map(),
39
+ };
40
+ GET = async (req, data) => this.#callMethod(types_js_1.HttpMethod.GET, req, await data.params);
41
+ POST = async (req, data) => this.#callMethod(types_js_1.HttpMethod.POST, req, await data.params);
42
+ PUT = async (req, data) => this.#callMethod(types_js_1.HttpMethod.PUT, req, await data.params);
43
+ PATCH = async (req, data) => this.#callMethod(types_js_1.HttpMethod.PATCH, req, await data.params);
44
+ DELETE = async (req, data) => this.#callMethod(types_js_1.HttpMethod.DELETE, req, await data.params);
45
+ HEAD = async (req, data) => this.#callMethod(types_js_1.HttpMethod.HEAD, req, await data.params);
46
+ OPTIONS = async (req, data) => this.#callMethod(types_js_1.HttpMethod.OPTIONS, req, await data.params);
47
+ respond = (status, body, options) => {
48
+ return new Response(JSON.stringify(body), {
49
+ status,
50
+ headers: {
51
+ 'content-type': 'application/json',
52
+ ..._a.getHeadersFromOptions(options),
53
+ },
54
+ });
55
+ };
56
+ #respondWithError = (statusCode, message, options, cause) => {
57
+ return this.respond(
58
+ statusCode,
59
+ {
60
+ cause,
61
+ statusCode,
62
+ message,
63
+ isError: true,
64
+ },
65
+ options
66
+ );
67
+ };
68
+ #getHandler = ({ handlers, path, params }) => {
69
+ const methodParams = {};
70
+ if (Object.keys(params).length === 0) {
71
+ return { handler: handlers[''], methodParams };
72
+ }
73
+ const allMethodKeys = Object.keys(handlers);
74
+ let methodKeys = [];
75
+ const pathStr = path.join('/');
76
+ methodKeys = allMethodKeys
77
+ // First, try to match literal routes exactly.
78
+ .filter((p) => {
79
+ if (p.includes(':')) return false; // Skip parameterized paths
80
+ return p === pathStr;
81
+ });
82
+ if (!methodKeys.length) {
83
+ methodKeys = allMethodKeys.filter((p) => {
84
+ const routeSegments = p.split('/');
85
+ if (routeSegments.length !== path.length) return false;
86
+ for (let i = 0; i < routeSegments.length; i++) {
87
+ const routeSegment = routeSegments[i];
88
+ const pathSegment = path[i];
89
+ if (routeSegment.startsWith(':')) {
90
+ const parameter = routeSegment.slice(1);
91
+ if (parameter in methodParams) {
92
+ throw new HttpException_js_1.HttpException(
93
+ types_js_1.HttpStatus.INTERNAL_SERVER_ERROR,
94
+ `Duplicate parameter "${parameter}" at ${p}`
95
+ );
96
+ }
97
+ // If it's a parameterized segment, capture the parameter value.
98
+ methodParams[parameter] = pathSegment;
99
+ } else if (routeSegment !== pathSegment) {
100
+ // If it's a literal segment and it does not match the corresponding path segment, return false.
101
+ return false;
102
+ }
103
+ }
104
+ return true;
105
+ });
106
+ }
107
+ if (methodKeys.length > 1) {
108
+ throw new HttpException_js_1.HttpException(
109
+ types_js_1.HttpStatus.INTERNAL_SERVER_ERROR,
110
+ `Conflicting routes found: ${methodKeys.join(', ')}`
111
+ );
112
+ }
113
+ const [methodKey] = methodKeys;
114
+ if (methodKey) {
115
+ return { handler: handlers[methodKey], methodParams };
116
+ }
117
+ return { handler: null, methodParams };
118
+ };
119
+ #callMethod = async (httpMethod, nextReq, params) => {
120
+ const req = nextReq;
121
+ const controllers = this.routes[httpMethod];
122
+ const path = params[Object.keys(params)[0]];
123
+ const handlers = {};
124
+ controllers.forEach((staticMethods, controller) => {
125
+ const prefix = controller._prefix ?? '';
126
+ if (!controller._activated) {
127
+ throw new HttpException_js_1.HttpException(
128
+ types_js_1.HttpStatus.INTERNAL_SERVER_ERROR,
129
+ `Controller "${controller.name}" found but not activated`
130
+ );
131
+ }
132
+ Object.entries(staticMethods).forEach(([path, staticMethod]) => {
133
+ const fullPath = [prefix, path].filter(Boolean).join('/');
134
+ handlers[fullPath] = { staticMethod, controller };
135
+ });
136
+ });
137
+ const { handler, methodParams } = this.#getHandler({ handlers, path, params });
138
+ if (!handler) {
139
+ return this.#respondWithError(
140
+ types_js_1.HttpStatus.NOT_FOUND,
141
+ `${Object.keys(handlers)} - Route ${path.join('/')} is not found`
142
+ );
143
+ }
144
+ const { staticMethod, controller } = handler;
145
+ req.vovk = {
146
+ body: () => req.json(),
147
+ query: () => (0, reqQuery_js_1.default)(req),
148
+ meta: (meta) => (0, reqMeta_js_1.default)(req, meta),
149
+ form: () => (0, reqForm_js_1.default)(req),
150
+ params: () => methodParams,
151
+ };
152
+ try {
153
+ await staticMethod._options?.before?.call(controller, req);
154
+ const result = await staticMethod.call(controller, req, methodParams);
155
+ const isIterator =
156
+ typeof result === 'object' &&
157
+ !!result &&
158
+ ((Reflect.has(result, Symbol.iterator) && typeof result[Symbol.iterator] === 'function') ||
159
+ (Reflect.has(result, Symbol.asyncIterator) && typeof result[Symbol.asyncIterator] === 'function'));
160
+ if (isIterator && !(result instanceof Array)) {
161
+ const streamResponse = new JSONLinesResponse_js_1.JSONLinesResponse(await (0, headers_1.headers)(), {
162
+ headers: {
163
+ ..._a.getHeadersFromOptions(staticMethod._options),
164
+ },
165
+ });
166
+ void (async () => {
167
+ try {
168
+ for await (const chunk of result) {
169
+ streamResponse.send(chunk);
170
+ }
171
+ } catch (e) {
172
+ return streamResponse.throw(e);
173
+ }
174
+ return streamResponse.close();
175
+ })();
176
+ return streamResponse;
177
+ }
178
+ if (result instanceof Response) {
179
+ return result;
180
+ }
181
+ return this.respond(200, result ?? null, staticMethod._options);
182
+ } catch (e) {
183
+ const err = e;
184
+ try {
185
+ await controller._onError?.(err, req);
186
+ } catch (onErrorError) {
187
+ // eslint-disable-next-line no-console
188
+ console.error(onErrorError);
189
+ }
190
+ if (err.message !== 'NEXT_REDIRECT' && err.message !== 'NEXT_NOT_FOUND') {
191
+ const statusCode = err.statusCode || types_js_1.HttpStatus.INTERNAL_SERVER_ERROR;
192
+ return this.#respondWithError(statusCode, err.message, staticMethod._options, err.cause);
193
+ }
194
+ throw e; // if NEXT_REDIRECT or NEXT_NOT_FOUND, rethrow it
195
+ }
196
+ };
197
+ }
198
+ exports.VovkApp = VovkApp;
199
+ _a = VovkApp;
@@ -0,0 +1,11 @@
1
+ import type { KnownAny, VovkSchema } from '../types.js';
2
+ import type { VovkClientOptions, VovkClient, VovkDefaultFetcherOptions } from './types.js';
3
+ export declare const createRPC: <
4
+ T,
5
+ OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions<Record<string, never>>,
6
+ >(
7
+ schema: VovkSchema,
8
+ segmentName: string,
9
+ rpcModuleName: string,
10
+ options?: VovkClientOptions<OPTS>
11
+ ) => VovkClient<T, OPTS>;
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+ var __importDefault =
3
+ (this && this.__importDefault) ||
4
+ function (mod) {
5
+ return mod && mod.__esModule ? mod : { default: mod };
6
+ };
7
+ Object.defineProperty(exports, '__esModule', { value: true });
8
+ exports.createRPC = void 0;
9
+ const fetcher_js_1 = require('./fetcher.js');
10
+ const defaultHandler_js_1 = require('./defaultHandler.js');
11
+ const defaultStreamHandler_js_1 = require('./defaultStreamHandler.js');
12
+ const serializeQuery_js_1 = __importDefault(require('../utils/serializeQuery.js'));
13
+ const trimPath = (path) => path.trim().replace(/^\/|\/$/g, '');
14
+ const getHandlerPath = (endpoint, params, query) => {
15
+ let result = endpoint;
16
+ const queryStr = query ? (0, serializeQuery_js_1.default)(query) : null;
17
+ for (const [key, value] of Object.entries(params ?? {})) {
18
+ result = result.replace(`:${key}`, value);
19
+ }
20
+ return `${result}${queryStr ? '?' : ''}${queryStr}`;
21
+ };
22
+ const createRPC = (schema, segmentName, rpcModuleName, options) => {
23
+ const segmentNamePath = options?.segmentNameOverride ?? segmentName;
24
+ const segmentSchema = schema.segments[segmentName];
25
+ if (!segmentSchema) throw new Error(`Unable to create RPC object. Segment schema is missing. Check client template.`);
26
+ const controllerSchema = schema.segments[segmentName]?.controllers[rpcModuleName];
27
+ const client = {};
28
+ if (!controllerSchema)
29
+ throw new Error(`Unable to create RPC object. Controller schema is missing. Check client template.`);
30
+ const controllerPrefix = trimPath(controllerSchema.prefix ?? '');
31
+ const { fetcher: settingsFetcher = fetcher_js_1.fetcher } = options ?? {};
32
+ for (const [staticMethodName, handlerSchema] of Object.entries(controllerSchema.handlers)) {
33
+ const { path, httpMethod, validation } = handlerSchema;
34
+ const getEndpoint = ({ apiRoot, params, query }) => {
35
+ const endpoint = [
36
+ apiRoot.startsWith('http://') || apiRoot.startsWith('https://') || apiRoot.startsWith('/') ? '' : '/',
37
+ apiRoot,
38
+ segmentNamePath,
39
+ getHandlerPath([controllerPrefix, path].filter(Boolean).join('/'), params, query),
40
+ ]
41
+ .filter(Boolean)
42
+ .join('/');
43
+ return endpoint;
44
+ };
45
+ const handler = (input = {}) => {
46
+ const fetcher = input.fetcher ?? settingsFetcher;
47
+ const validate = async ({ body, query, params, endpoint }) => {
48
+ const validateOnClient = input.validateOnClient ?? options?.validateOnClient;
49
+ if (validateOnClient && validation) {
50
+ if (typeof validateOnClient !== 'function') {
51
+ throw new Error('validateOnClient must be a function');
52
+ }
53
+ await validateOnClient({ body, query, params, endpoint }, validation, schema);
54
+ }
55
+ };
56
+ const internalOptions = {
57
+ name: staticMethodName,
58
+ httpMethod: httpMethod,
59
+ getEndpoint,
60
+ validate,
61
+ defaultHandler: defaultHandler_js_1.defaultHandler,
62
+ defaultStreamHandler: defaultStreamHandler_js_1.defaultStreamHandler,
63
+ };
64
+ const internalInput = {
65
+ ...options?.defaultOptions,
66
+ ...input,
67
+ body: input.body ?? null,
68
+ query: input.query ?? {},
69
+ params: input.params ?? {},
70
+ // TS workaround
71
+ fetcher: undefined,
72
+ validateOnClient: undefined,
73
+ };
74
+ delete internalInput.fetcher;
75
+ delete internalInput.validateOnClient;
76
+ if (!fetcher) throw new Error('Fetcher is not provided');
77
+ const fetcherPromise = fetcher(internalOptions, internalInput);
78
+ if (!(fetcherPromise instanceof Promise)) return Promise.resolve(fetcherPromise);
79
+ return input.transform ? fetcherPromise.then(input.transform) : fetcherPromise;
80
+ };
81
+ handler.schema = handlerSchema;
82
+ handler.controllerSchema = controllerSchema;
83
+ handler.segmentSchema = segmentSchema;
84
+ handler.fullSchema = schema;
85
+ handler.isRPC = true;
86
+ handler.path = [segmentNamePath, controllerPrefix, path].filter(Boolean).join('/');
87
+ // @ts-expect-error TODO
88
+ client[staticMethodName] = handler;
89
+ }
90
+ return client;
91
+ };
92
+ exports.createRPC = createRPC;
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultHandler';
2
+ export declare const defaultHandler: (response: Response) => Promise<unknown>;
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.defaultHandler = exports.DEFAULT_ERROR_MESSAGE = void 0;
4
+ const HttpException_js_1 = require('../HttpException.js');
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
+ } catch (e) {
11
+ // handle parsing errors
12
+ throw new HttpException_js_1.HttpException(response.status, e?.message ?? exports.DEFAULT_ERROR_MESSAGE);
13
+ }
14
+ if (!response.ok) {
15
+ // handle server errors
16
+ const errorResponse = result;
17
+ throw new HttpException_js_1.HttpException(
18
+ response.status,
19
+ errorResponse?.message ?? exports.DEFAULT_ERROR_MESSAGE,
20
+ errorResponse?.cause
21
+ );
22
+ }
23
+ return result;
24
+ };
25
+ exports.defaultHandler = defaultHandler;
@@ -0,0 +1,4 @@
1
+ import type { VovkStreamAsyncIterable } from './types.js';
2
+ import '../utils/shim.js';
3
+ export declare const DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultStreamHandler';
4
+ export declare const defaultStreamHandler: (response: Response) => Promise<VovkStreamAsyncIterable<unknown>>;