vovk 3.0.0-draft.8 → 3.0.0-draft.80

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 (78) hide show
  1. package/README.md +8 -95
  2. package/{HttpException.d.ts → dist/HttpException.d.ts} +2 -2
  3. package/{HttpException.js → dist/HttpException.js} +3 -3
  4. package/{StreamResponse.d.ts → dist/StreamJSONResponse.d.ts} +3 -3
  5. package/{StreamResponse.js → dist/StreamJSONResponse.js} +5 -5
  6. package/{Segment.d.ts → dist/VovkApp.d.ts} +11 -10
  7. package/{Segment.js → dist/VovkApp.js} +28 -24
  8. package/dist/client/createRPC.d.ts +4 -0
  9. package/{client/clientizeController.js → dist/client/createRPC.js} +22 -40
  10. package/dist/client/defaultFetcher.d.ts +4 -0
  11. package/{client → dist/client}/defaultFetcher.js +19 -8
  12. package/{client → dist/client}/defaultHandler.d.ts +1 -1
  13. package/dist/client/defaultHandler.js +22 -0
  14. package/dist/client/defaultStreamHandler.d.ts +4 -0
  15. package/{client → dist/client}/defaultStreamHandler.js +5 -5
  16. package/dist/client/index.d.ts +2 -0
  17. package/dist/client/index.js +8 -0
  18. package/dist/client/types.d.ts +100 -0
  19. package/dist/createDecorator.d.ts +4 -0
  20. package/{createDecorator.js → dist/createDecorator.js} +16 -15
  21. package/{createSegment.d.ts → dist/createVovkApp.d.ts} +10 -10
  22. package/{createSegment.js → dist/createVovkApp.js} +30 -30
  23. package/dist/index.d.ts +60 -0
  24. package/dist/index.js +24 -0
  25. package/dist/openapi/fromSchema.d.ts +3 -0
  26. package/dist/openapi/fromSchema.js +26 -0
  27. package/dist/openapi/index.d.ts +1 -0
  28. package/dist/openapi/index.js +5 -0
  29. package/dist/openapi/openapi.d.ts +11 -0
  30. package/dist/openapi/openapi.js +70 -0
  31. package/dist/types.d.ts +148 -0
  32. package/dist/types.js +65 -0
  33. package/dist/utils/generateStaticAPI.d.ts +4 -0
  34. package/{generateStaticAPI.js → dist/utils/generateStaticAPI.js} +3 -3
  35. package/{utils → dist/utils}/getSchema.d.ts +1 -2
  36. package/dist/utils/getSchema.js +29 -0
  37. package/dist/utils/parseQuery.d.ts +25 -0
  38. package/dist/utils/parseQuery.js +156 -0
  39. package/dist/utils/reqForm.d.ts +2 -0
  40. package/dist/utils/reqForm.js +13 -0
  41. package/{utils → dist/utils}/reqMeta.d.ts +1 -2
  42. package/{utils → dist/utils}/reqQuery.d.ts +1 -2
  43. package/dist/utils/reqQuery.js +10 -0
  44. package/dist/utils/serializeQuery.d.ts +13 -0
  45. package/dist/utils/serializeQuery.js +65 -0
  46. package/dist/utils/setHandlerValidation.d.ts +4 -0
  47. package/dist/utils/setHandlerValidation.js +15 -0
  48. package/package.json +8 -2
  49. package/.npmignore +0 -2
  50. package/client/clientizeController.d.ts +0 -4
  51. package/client/defaultFetcher.d.ts +0 -4
  52. package/client/defaultHandler.js +0 -21
  53. package/client/defaultStreamHandler.d.ts +0 -4
  54. package/client/index.d.ts +0 -4
  55. package/client/index.js +0 -5
  56. package/client/types.d.ts +0 -100
  57. package/createDecorator.d.ts +0 -4
  58. package/generateStaticAPI.d.ts +0 -4
  59. package/index.d.ts +0 -60
  60. package/index.js +0 -20
  61. package/types.d.ts +0 -186
  62. package/types.js +0 -65
  63. package/utils/getSchema.js +0 -38
  64. package/utils/reqQuery.js +0 -25
  65. package/utils/setClientValidatorsForHandler.d.ts +0 -5
  66. package/utils/setClientValidatorsForHandler.js +0 -28
  67. package/worker/index.d.ts +0 -3
  68. package/worker/index.js +0 -7
  69. package/worker/promisifyWorker.d.ts +0 -2
  70. package/worker/promisifyWorker.js +0 -143
  71. package/worker/types.d.ts +0 -31
  72. package/worker/types.js +0 -2
  73. package/worker/worker.d.ts +0 -1
  74. package/worker/worker.js +0 -44
  75. /package/{client → dist/client}/types.js +0 -0
  76. /package/{utils → dist/utils}/reqMeta.js +0 -0
  77. /package/{utils → dist/utils}/shim.d.ts +0 -0
  78. /package/{utils → dist/utils}/shim.js +0 -0
package/README.md CHANGED
@@ -4,108 +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>REST + 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!
15
- </p>
16
-
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>
11
+ Back-end meta-framework for <a href="https://nextjs.org/docs/app">Next.js</a>
29
12
  </p>
30
13
 
14
+ ---
31
15
 
32
- <br />
16
+ ## vovk [![npm version](https://badge.fury.io/js/vovk.svg)](https://www.npmjs.com/package/vovk)
33
17
 
34
- 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.
35
19
 
36
- ```ts
37
- // /src/modules/post/PostController.ts
38
- import { get, prefix, type VovkRequest } from 'vovk';
39
- import PostService from './PostService';
40
-
41
- @prefix('posts')
42
- export default class PostController {
43
- /**
44
- * Create a comment on a post
45
- * POST /api/posts/:postId/comments
46
- */
47
- @post(':postId/comments')
48
- static async createComment(
49
- // decorate NextRequest type with body and query types
50
- req: VovkRequest<
51
- { content: string; userId: string },
52
- { notificationType: 'push' | 'email' }
53
- >,
54
- { postId }: { postId: string } // params
55
- ) {
56
- // use standard Next.js API to get body and query
57
- const { content, userId } = await req.json();
58
- const notificationType = req.nextUrl.searchParams.get('notificationType');
59
-
60
- // perform the request to the database in a custom service
61
- return PostService.createComment({
62
- postId, content, userId, notificationType,
63
- });
64
- }
65
- }
20
+ ```sh
21
+ npm install vovk
66
22
  ```
67
23
 
68
- Example component that uses the auto-generated client library:
69
-
70
- ```tsx
71
- 'use client';
72
- import { useState } from 'react';
73
- import { PostController } from 'vovk-client';
74
- import type { VovkReturnType } from 'vovk';
75
-
76
- export default function Example() {
77
- const [response, setResponse] = useState<VovkReturnType<typeof PostController.createComment>>();
78
-
79
- return (
80
- <>
81
- <button
82
- onClick={async () => 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
- Post a comment
94
- </button>
95
- <div>{JSON.stringify(response)}</div>
96
- </>
97
- );
98
- }
99
- ```
100
-
101
- Alternatively, the resource can be fetched wit the regular `fetch` function:
102
-
103
- ```ts
104
- fetch('/api/posts/69?notificationType=push', {
105
- method: 'POST',
106
- body: JSON.stringify({
107
- content: 'Hello, World!',
108
- userId: '1',
109
- }),
110
- })
111
- ```
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/).
@@ -1,5 +1,5 @@
1
- import type { _HttpStatus as HttpStatus } from './types';
2
- export declare class _HttpException extends Error {
1
+ import type { HttpStatus } from './types';
2
+ export declare class HttpException extends Error {
3
3
  statusCode: HttpStatus;
4
4
  message: string;
5
5
  cause?: unknown;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._HttpException = void 0;
4
- class _HttpException extends Error {
3
+ exports.HttpException = void 0;
4
+ class HttpException extends Error {
5
5
  statusCode;
6
6
  message;
7
7
  cause;
@@ -12,4 +12,4 @@ class _HttpException extends Error {
12
12
  this.cause = cause;
13
13
  }
14
14
  }
15
- exports._HttpException = _HttpException;
15
+ exports.HttpException = HttpException;
@@ -1,8 +1,8 @@
1
- import { _KnownAny as KnownAny, _StreamAbortMessage as StreamAbortMessage } from './types';
1
+ import type { KnownAny, StreamAbortMessage } from './types';
2
2
  import './utils/shim';
3
- export declare class _StreamResponse<T> extends Response {
3
+ export declare class StreamJSONResponse<T> extends Response {
4
4
  static defaultHeaders: {
5
- 'Content-Type': string;
5
+ 'content-type': string;
6
6
  };
7
7
  isClosed: boolean;
8
8
  controller?: ReadableStreamDefaultController;
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._StreamResponse = void 0;
3
+ exports.StreamJSONResponse = void 0;
4
4
  require("./utils/shim");
5
- class _StreamResponse extends Response {
5
+ class StreamJSONResponse extends Response {
6
6
  static defaultHeaders = {
7
- 'Content-Type': 'text/plain; charset=utf-8',
7
+ 'content-type': 'text/plain; format=jsonlines',
8
8
  };
9
9
  isClosed = false;
10
10
  controller;
@@ -23,7 +23,7 @@ class _StreamResponse extends Response {
23
23
  });
24
24
  super(readableStream, {
25
25
  ...init,
26
- headers: init?.headers ?? _StreamResponse.defaultHeaders,
26
+ headers: init?.headers ?? StreamJSONResponse.defaultHeaders,
27
27
  });
28
28
  this.readableStream = readableStream;
29
29
  this.encoder = encoder;
@@ -50,4 +50,4 @@ class _StreamResponse extends Response {
50
50
  this.close();
51
51
  }
52
52
  }
53
- exports._StreamResponse = _StreamResponse;
53
+ exports.StreamJSONResponse = StreamJSONResponse;
@@ -1,27 +1,28 @@
1
- import { _HttpMethod as HttpMethod, _HttpStatus as HttpStatus, type _RouteHandler as RouteHandler, type _VovkController as VovkController, type _DecoratorOptions as DecoratorOptions, type _VovkRequest as VovkRequest } from './types';
2
- export declare class _Segment {
1
+ import type { NextRequest } from 'next/server';
2
+ import { HttpMethod, HttpStatus, type RouteHandler, type VovkController, type DecoratorOptions } from './types';
3
+ export declare class VovkApp {
3
4
  #private;
4
5
  private static getHeadersFromOptions;
5
- _routes: Record<HttpMethod, Map<VovkController, Record<string, RouteHandler>>>;
6
- GET: (req: VovkRequest, data: {
6
+ routes: Record<HttpMethod, Map<VovkController, Record<string, RouteHandler>>>;
7
+ GET: (req: NextRequest, data: {
7
8
  params: Promise<Record<string, string[]>>;
8
9
  }) => Promise<Response>;
9
- POST: (req: VovkRequest, data: {
10
+ POST: (req: NextRequest, data: {
10
11
  params: Promise<Record<string, string[]>>;
11
12
  }) => Promise<Response>;
12
- PUT: (req: VovkRequest, data: {
13
+ PUT: (req: NextRequest, data: {
13
14
  params: Promise<Record<string, string[]>>;
14
15
  }) => Promise<Response>;
15
- PATCH: (req: VovkRequest, data: {
16
+ PATCH: (req: NextRequest, data: {
16
17
  params: Promise<Record<string, string[]>>;
17
18
  }) => Promise<Response>;
18
- DELETE: (req: VovkRequest, data: {
19
+ DELETE: (req: NextRequest, data: {
19
20
  params: Promise<Record<string, string[]>>;
20
21
  }) => Promise<Response>;
21
- HEAD: (req: VovkRequest, data: {
22
+ HEAD: (req: NextRequest, data: {
22
23
  params: Promise<Record<string, string[]>>;
23
24
  }) => Promise<Response>;
24
- OPTIONS: (req: VovkRequest, data: {
25
+ OPTIONS: (req: NextRequest, data: {
25
26
  params: Promise<Record<string, string[]>>;
26
27
  }) => Promise<Response>;
27
28
  respond: (status: HttpStatus, body: unknown, options?: DecoratorOptions) => Response;
@@ -4,13 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  var _a;
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports._Segment = void 0;
7
+ exports.VovkApp = void 0;
8
8
  const types_1 = require("./types");
9
9
  const HttpException_1 = require("./HttpException");
10
- const StreamResponse_1 = require("./StreamResponse");
10
+ const StreamJSONResponse_1 = require("./StreamJSONResponse");
11
11
  const reqQuery_1 = __importDefault(require("./utils/reqQuery"));
12
12
  const reqMeta_1 = __importDefault(require("./utils/reqMeta"));
13
- class _Segment {
13
+ const reqForm_1 = __importDefault(require("./utils/reqForm"));
14
+ class VovkApp {
14
15
  static getHeadersFromOptions(options) {
15
16
  if (!options)
16
17
  return {};
@@ -25,7 +26,7 @@ class _Segment {
25
26
  };
26
27
  return headers;
27
28
  }
28
- _routes = {
29
+ routes = {
29
30
  GET: new Map(),
30
31
  POST: new Map(),
31
32
  PUT: new Map(),
@@ -34,13 +35,13 @@ class _Segment {
34
35
  HEAD: new Map(),
35
36
  OPTIONS: new Map(),
36
37
  };
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);
38
+ GET = async (req, data) => this.#callMethod(types_1.HttpMethod.GET, req, await data.params);
39
+ POST = async (req, data) => this.#callMethod(types_1.HttpMethod.POST, req, await data.params);
40
+ PUT = async (req, data) => this.#callMethod(types_1.HttpMethod.PUT, req, await data.params);
41
+ PATCH = async (req, data) => this.#callMethod(types_1.HttpMethod.PATCH, req, await data.params);
42
+ DELETE = async (req, data) => this.#callMethod(types_1.HttpMethod.DELETE, req, await data.params);
43
+ HEAD = async (req, data) => this.#callMethod(types_1.HttpMethod.HEAD, req, await data.params);
44
+ OPTIONS = async (req, data) => this.#callMethod(types_1.HttpMethod.OPTIONS, req, await data.params);
44
45
  respond = (status, body, options) => {
45
46
  return new Response(JSON.stringify(body), {
46
47
  status,
@@ -50,22 +51,24 @@ class _Segment {
50
51
  },
51
52
  });
52
53
  };
53
- #respondWithError = (statusCode, message, options) => {
54
+ #respondWithError = (statusCode, message, options, cause) => {
54
55
  return this.respond(statusCode, {
56
+ cause,
55
57
  statusCode,
56
58
  message,
57
59
  isError: true,
58
60
  }, options);
59
61
  };
60
- #callMethod = async (httpMethod, req, params) => {
61
- const controllers = this._routes[httpMethod];
62
+ #callMethod = async (httpMethod, nextReq, params) => {
63
+ const req = nextReq;
64
+ const controllers = this.routes[httpMethod];
62
65
  const methodParams = {};
63
66
  const path = params[Object.keys(params)[0]];
64
67
  const handlers = {};
65
68
  controllers.forEach((staticMethods, controller) => {
66
69
  const prefix = controller._prefix ?? '';
67
70
  if (!controller._activated) {
68
- throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Controller "${controller.name}" found but not activated`);
71
+ throw new HttpException_1.HttpException(types_1.HttpStatus.INTERNAL_SERVER_ERROR, `Controller "${controller.name}" found but not activated`);
69
72
  }
70
73
  Object.entries(staticMethods).forEach(([path, staticMethod]) => {
71
74
  const fullPath = [prefix, path].filter(Boolean).join('/');
@@ -96,7 +99,7 @@ class _Segment {
96
99
  if (routeSegment.startsWith(':')) {
97
100
  const parameter = routeSegment.slice(1);
98
101
  if (parameter in methodParams) {
99
- throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Duplicate parameter "${parameter}"`);
102
+ throw new HttpException_1.HttpException(types_1.HttpStatus.INTERNAL_SERVER_ERROR, `Duplicate parameter "${parameter}"`);
100
103
  }
101
104
  // If it's a parameterized segment, capture the parameter value.
102
105
  methodParams[parameter] = pathSegment;
@@ -110,7 +113,7 @@ class _Segment {
110
113
  });
111
114
  }
112
115
  if (methodKeys.length > 1) {
113
- throw new HttpException_1._HttpException(types_1._HttpStatus.INTERNAL_SERVER_ERROR, `Conflicting routes found: ${methodKeys.join(', ')}`);
116
+ throw new HttpException_1.HttpException(types_1.HttpStatus.INTERNAL_SERVER_ERROR, `Conflicting routes found: ${methodKeys.join(', ')}`);
114
117
  }
115
118
  const [methodKey] = methodKeys;
116
119
  if (methodKey) {
@@ -120,13 +123,14 @@ class _Segment {
120
123
  };
121
124
  const handler = getHandler();
122
125
  if (!handler) {
123
- return this.#respondWithError(types_1._HttpStatus.NOT_FOUND, `Route ${path.join('/')} is not found`);
126
+ return this.#respondWithError(types_1.HttpStatus.NOT_FOUND, `Route ${path.join('/')} is not found`);
124
127
  }
125
128
  const { staticMethod, controller } = handler;
126
129
  req.vovk = {
127
130
  body: () => req.json(),
128
131
  query: () => (0, reqQuery_1.default)(req),
129
132
  meta: (meta) => (0, reqMeta_1.default)(req, meta),
133
+ form: () => (0, reqForm_1.default)(req),
130
134
  };
131
135
  try {
132
136
  const result = await staticMethod.call(controller, req, methodParams);
@@ -137,9 +141,9 @@ class _Segment {
137
141
  (Reflect.has(result, Symbol.asyncIterator) &&
138
142
  typeof result[Symbol.asyncIterator] === 'function'));
139
143
  if (isIterator && !(result instanceof Array)) {
140
- const streamResponse = new StreamResponse_1._StreamResponse({
144
+ const streamResponse = new StreamJSONResponse_1.StreamJSONResponse({
141
145
  headers: {
142
- ...StreamResponse_1._StreamResponse.defaultHeaders,
146
+ ...StreamJSONResponse_1.StreamJSONResponse.defaultHeaders,
143
147
  ..._a.getHeadersFromOptions(staticMethod._options),
144
148
  },
145
149
  });
@@ -171,12 +175,12 @@ class _Segment {
171
175
  console.error(onErrorError);
172
176
  }
173
177
  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);
178
+ const statusCode = err.statusCode ?? types_1.HttpStatus.INTERNAL_SERVER_ERROR;
179
+ return this.#respondWithError(statusCode, err.message, staticMethod._options, err.cause);
176
180
  }
177
181
  throw e; // if NEXT_REDIRECT or NEXT_NOT_FOUND, rethrow it
178
182
  }
179
183
  };
180
184
  }
181
- exports._Segment = _Segment;
182
- _a = _Segment;
185
+ exports.VovkApp = VovkApp;
186
+ _a = VovkApp;
@@ -0,0 +1,4 @@
1
+ import { type VovkControllerSchema, type KnownAny } from '../types';
2
+ import { type VovkClientOptions, type VovkClient, type VovkDefaultFetcherOptions } from './types';
3
+ declare const createRPC: <T, OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions>(controllerSchema: VovkControllerSchema, segmentName?: string, options?: VovkClientOptions<OPTS>) => VovkClient<T, OPTS>;
4
+ export default createRPC;
@@ -3,68 +3,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports._clientizeController = exports.ARRAY_QUERY_KEY = void 0;
7
6
  const defaultFetcher_1 = __importDefault(require("./defaultFetcher"));
8
7
  const defaultHandler_1 = require("./defaultHandler");
9
8
  const defaultStreamHandler_1 = require("./defaultStreamHandler");
10
- exports.ARRAY_QUERY_KEY = '_vovkarr';
9
+ const serializeQuery_1 = __importDefault(require("../utils/serializeQuery"));
11
10
  const trimPath = (path) => path.trim().replace(/^\/|\/$/g, '');
12
11
  const getHandlerPath = (endpoint, params, query) => {
13
12
  let result = endpoint;
14
13
  for (const [key, value] of Object.entries(params ?? {})) {
15
14
  result = result.replace(`:${key}`, value);
16
15
  }
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()}`;
16
+ const queryStr = query ? (0, serializeQuery_1.default)(query) : null;
17
+ return `${result}${queryStr ? '?' : ''}${queryStr}`;
38
18
  };
39
- const _clientizeController = (givenController, segmentName, options) => {
40
- const controller = givenController;
19
+ const createRPC = (controllerSchema, segmentName, options) => {
20
+ const schema = controllerSchema;
41
21
  const client = {};
42
- if (!controller)
43
- throw new Error(`Unable to clientize. Controller schema is not provided`);
44
- const schema = controller._handlers;
45
22
  if (!schema)
46
- throw new Error(`Unable to clientize. No schema for controller ${String(controller?._controllerName)}`);
47
- const controllerPrefix = trimPath(controller._prefix ?? '');
23
+ throw new Error(`Unable to clientize. Controller schema is not provided`);
24
+ if (!schema.handlers)
25
+ throw new Error(`Unable to clientize. No schema for controller ${String(schema?.controllerName)} provided`);
26
+ const controllerPrefix = trimPath(schema.prefix ?? '');
48
27
  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}/`) +
28
+ for (const [staticMethodName, handlerSchema] of Object.entries(schema.handlers)) {
29
+ const { path, httpMethod, validation } = handlerSchema;
30
+ const getEndpoint = ({ apiRoot, params, query, }) => {
31
+ const mainPrefix = (apiRoot.startsWith('http://') || apiRoot.startsWith('https://') || apiRoot.startsWith('/') ? '' : '/') +
32
+ (apiRoot.endsWith('/') ? apiRoot : `${apiRoot}/`) +
53
33
  (segmentName ? `${segmentName}/` : '');
54
34
  return mainPrefix + getHandlerPath([controllerPrefix, path].filter(Boolean).join('/'), params, query);
55
35
  };
56
36
  const handler = (input = {}) => {
57
37
  const fetcher = input.fetcher ?? settingsFetcher;
58
38
  const validate = async ({ body, query, endpoint }) => {
59
- await (input.validateOnClient ?? options?.validateOnClient)?.({ body, query, endpoint }, clientValidators ?? {});
39
+ await (input.validateOnClient ?? options?.validateOnClient)?.({ body, query, endpoint }, validation ?? {});
60
40
  };
61
41
  const internalOptions = {
62
42
  name: staticMethodName,
63
- httpMethod,
43
+ httpMethod: httpMethod,
64
44
  getEndpoint,
65
45
  validate,
66
- defaultHandler: defaultHandler_1._defaultHandler,
67
- defaultStreamHandler: defaultStreamHandler_1._defaultStreamHandler,
46
+ defaultHandler: defaultHandler_1.defaultHandler,
47
+ defaultStreamHandler: defaultStreamHandler_1.defaultStreamHandler,
68
48
  };
69
49
  const internalInput = {
70
50
  ...options?.defaultOptions,
@@ -85,9 +65,11 @@ const _clientizeController = (givenController, segmentName, options) => {
85
65
  return Promise.resolve(fetcherPromise);
86
66
  return input.transform ? fetcherPromise.then(input.transform) : fetcherPromise;
87
67
  };
88
- // @ts-expect-error TODO: Fix this
68
+ handler.schema = handlerSchema;
69
+ handler.controllerSchema = schema;
70
+ // @ts-expect-error TODO
89
71
  client[staticMethodName] = handler;
90
72
  }
91
73
  return client;
92
74
  };
93
- exports._clientizeController = _clientizeController;
75
+ exports.default = createRPC;
@@ -0,0 +1,4 @@
1
+ import type { VovkDefaultFetcherOptions, VovkClientFetcher } from './types';
2
+ export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at the defaultFetcher";
3
+ declare const defaultFetcher: VovkClientFetcher<VovkDefaultFetcherOptions>;
4
+ export default defaultFetcher;
@@ -3,21 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_ERROR_MESSAGE = void 0;
4
4
  const types_1 = require("../types");
5
5
  const HttpException_1 = require("../HttpException");
6
- exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultFetcher';
6
+ exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at the defaultFetcher';
7
7
  // defaultFetcher uses HttpException class to throw errors of fake HTTP status 0 if client-side error occurs
8
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 });
9
+ const defaultFetcher = async ({ httpMethod, getEndpoint, validate, defaultHandler, defaultStreamHandler }, { params, query, body, apiRoot = '/api', ...options }) => {
10
+ const endpoint = getEndpoint({ apiRoot, params, query });
11
11
  if (!options.disableClientValidation) {
12
12
  try {
13
13
  await validate({ body, query, endpoint });
14
14
  }
15
15
  catch (e) {
16
16
  // if HttpException is thrown, rethrow it
17
- if (e instanceof HttpException_1._HttpException)
17
+ if (e instanceof HttpException_1.HttpException)
18
18
  throw e;
19
19
  // otherwise, throw HttpException with status 0
20
- throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e.message ?? exports.DEFAULT_ERROR_MESSAGE);
20
+ throw new HttpException_1.HttpException(types_1.HttpStatus.NULL, e.message ?? exports.DEFAULT_ERROR_MESSAGE, {
21
+ body,
22
+ query,
23
+ params,
24
+ endpoint,
25
+ });
21
26
  }
22
27
  }
23
28
  const init = {
@@ -36,12 +41,18 @@ const defaultFetcher = async ({ httpMethod, getEndpoint, validate, defaultHandle
36
41
  }
37
42
  catch (e) {
38
43
  // handle network errors
39
- throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e?.message ?? exports.DEFAULT_ERROR_MESSAGE);
44
+ throw new HttpException_1.HttpException(types_1.HttpStatus.NULL, e?.message ?? exports.DEFAULT_ERROR_MESSAGE, {
45
+ body,
46
+ query,
47
+ params,
48
+ endpoint,
49
+ });
40
50
  }
41
- if (response.headers.get('content-type')?.includes('application/json')) {
51
+ const contentType = response.headers.get('content-type');
52
+ if (contentType?.startsWith('application/json')) {
42
53
  return defaultHandler(response);
43
54
  }
44
- if (response.headers.get('content-type')?.includes('text/plain; charset=utf-8')) {
55
+ if (contentType === 'text/plain; format=jsonlines') {
45
56
  return defaultStreamHandler(response);
46
57
  }
47
58
  return response;
@@ -1,2 +1,2 @@
1
1
  export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultHandler";
2
- export declare const _defaultHandler: (response: Response) => Promise<unknown>;
2
+ export declare const defaultHandler: (response: Response) => Promise<unknown>;
@@ -0,0 +1,22 @@
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
+ const errorResponse = result;
18
+ throw new HttpException_1.HttpException(response.status, errorResponse?.message ?? exports.DEFAULT_ERROR_MESSAGE, errorResponse?.cause);
19
+ }
20
+ return result;
21
+ };
22
+ exports.defaultHandler = defaultHandler;
@@ -0,0 +1,4 @@
1
+ import type { VovkStreamAsyncIterable } 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<VovkStreamAsyncIterable<unknown>>;
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._defaultStreamHandler = exports.DEFAULT_ERROR_MESSAGE = void 0;
3
+ exports.defaultStreamHandler = exports.DEFAULT_ERROR_MESSAGE = void 0;
4
4
  const types_1 = require("../types");
5
5
  const HttpException_1 = require("../HttpException");
6
6
  require("../utils/shim");
7
7
  exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultStreamHandler';
8
- const _defaultStreamHandler = async (response) => {
8
+ const defaultStreamHandler = async (response) => {
9
9
  if (!response.ok) {
10
10
  let result;
11
11
  try {
@@ -15,10 +15,10 @@ const _defaultStreamHandler = async (response) => {
15
15
  // ignore parsing errors
16
16
  }
17
17
  // handle server errors
18
- throw new HttpException_1._HttpException(response.status, result.message ?? exports.DEFAULT_ERROR_MESSAGE);
18
+ throw new HttpException_1.HttpException(response.status, result.message ?? exports.DEFAULT_ERROR_MESSAGE);
19
19
  }
20
20
  if (!response.body)
21
- throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, 'Stream body is falsy. Check your controller code.');
21
+ throw new HttpException_1.HttpException(types_1.HttpStatus.NULL, 'Stream body is falsy. Check your controller code.');
22
22
  const reader = response.body.getReader();
23
23
  // if streaming is too rapid, we need to make sure that the loop is stopped
24
24
  let canceled = false;
@@ -79,4 +79,4 @@ const _defaultStreamHandler = async (response) => {
79
79
  },
80
80
  };
81
81
  };
82
- exports._defaultStreamHandler = _defaultStreamHandler;
82
+ exports.defaultStreamHandler = defaultStreamHandler;
@@ -0,0 +1,2 @@
1
+ export { default as createRPC } from './createRPC';
2
+ export type { VovkClient, VovkClientFetcher, VovkClientOptions, VovkDefaultFetcherOptions, VovkValidateOnClient, VovkStreamAsyncIterable, } from './types';
@@ -0,0 +1,8 @@
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.createRPC = void 0;
7
+ var createRPC_1 = require("./createRPC");
8
+ Object.defineProperty(exports, "createRPC", { enumerable: true, get: function () { return __importDefault(createRPC_1).default; } });