vovk 3.7.0 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/client/create-rpc.d.ts +3 -3
  3. package/dist/client/create-rpc.js +6 -8
  4. package/dist/client/default-stream-handler.d.ts +1 -5
  5. package/dist/client/default-stream-handler.js +53 -45
  6. package/dist/client/fetcher.d.ts +1 -1
  7. package/dist/client/fetcher.js +53 -4
  8. package/dist/client/progressive.d.ts +1 -5
  9. package/dist/client/progressive.js +19 -9
  10. package/dist/client/serialize-query.d.ts +0 -11
  11. package/dist/client/serialize-query.js +4 -28
  12. package/dist/core/controllers-to-static-params.d.ts +1 -2
  13. package/dist/core/controllers-to-static-params.js +1 -2
  14. package/dist/core/create-decorator.d.ts +1 -1
  15. package/dist/core/decorate.d.ts +2 -6
  16. package/dist/core/decorators.js +3 -3
  17. package/dist/core/get-schema.d.ts +1 -1
  18. package/dist/core/get-schema.js +1 -3
  19. package/dist/core/http-exception.d.ts +1 -1
  20. package/dist/core/http-exception.js +1 -1
  21. package/dist/core/init-segment.js +1 -1
  22. package/dist/core/json-lines-responder.d.ts +8 -12
  23. package/dist/core/json-lines-responder.js +42 -20
  24. package/dist/core/vovk-app.d.ts +1 -1
  25. package/dist/core/vovk-app.js +62 -16
  26. package/dist/index.d.ts +16 -18
  27. package/dist/index.js +14 -15
  28. package/dist/internal.d.ts +14 -12
  29. package/dist/internal.js +8 -7
  30. package/dist/openapi/error.js +2 -2
  31. package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.d.ts +1 -12
  32. package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.js +4 -10
  33. package/dist/openapi/openapi-to-vovk-schema/index.d.ts +1 -1
  34. package/dist/openapi/openapi-to-vovk-schema/index.js +20 -6
  35. package/dist/openapi/openapi-to-vovk-schema/inline-refs.d.ts +0 -7
  36. package/dist/openapi/openapi-to-vovk-schema/inline-refs.js +2 -13
  37. package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.d.ts +0 -5
  38. package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.js +3 -8
  39. package/dist/openapi/vovk-schema-to-openapi.d.ts +1 -1
  40. package/dist/openapi/vovk-schema-to-openapi.js +9 -3
  41. package/dist/req/parse-query.d.ts +0 -23
  42. package/dist/req/parse-query.js +51 -43
  43. package/dist/req/validate-content-type.js +1 -1
  44. package/dist/samples/create-code-samples.d.ts +1 -1
  45. package/dist/samples/create-code-samples.js +4 -3
  46. package/dist/samples/schema-to-code.d.ts +1 -1
  47. package/dist/samples/schema-to-code.js +18 -15
  48. package/dist/samples/schema-to-object.d.ts +1 -1
  49. package/dist/samples/schema-to-object.js +18 -15
  50. package/dist/tools/create-tool-factory.d.ts +2 -2
  51. package/dist/tools/derive-tools.d.ts +9 -13
  52. package/dist/tools/derive-tools.js +48 -47
  53. package/dist/tools/to-model-output-default.d.ts +2 -2
  54. package/dist/tools/to-model-output-default.js +20 -6
  55. package/dist/tools/to-model-output-mcp.d.ts +3 -2
  56. package/dist/tools/to-model-output.d.ts +1 -1
  57. package/dist/types/client.d.ts +4 -4
  58. package/dist/types/config.d.ts +5 -6
  59. package/dist/types/core.d.ts +4 -1
  60. package/dist/types/enums.d.ts +1 -1
  61. package/dist/types/enums.js +1 -1
  62. package/dist/types/inference.d.ts +2 -3
  63. package/dist/types/request.d.ts +1 -5
  64. package/dist/types/standard-schema.js +0 -2
  65. package/dist/types/standard-tool.d.ts +8 -12
  66. package/dist/types/tools.d.ts +2 -40
  67. package/dist/types/validation.d.ts +3 -3
  68. package/dist/utils/camel-case.d.ts +0 -5
  69. package/dist/utils/camel-case.js +2 -10
  70. package/dist/utils/deep-extend.d.ts +1 -10
  71. package/dist/utils/deep-extend.js +16 -4
  72. package/dist/validation/create-standard-validation.d.ts +3 -126
  73. package/dist/validation/create-standard-validation.js +2 -3
  74. package/dist/validation/create-validate-on-client.d.ts +3 -3
  75. package/dist/validation/json-schema-only-spec.d.ts +14 -0
  76. package/dist/validation/json-schema-only-spec.js +57 -0
  77. package/dist/validation/procedure.d.ts +1 -124
  78. package/dist/validation/validation-schemas-object-to-single-validation-schema.d.ts +0 -9
  79. package/dist/validation/validation-schemas-object-to-single-validation-schema.js +11 -14
  80. package/dist/validation/with-validation-library.d.ts +2 -2
  81. package/dist/validation/with-validation-library.js +11 -5
  82. package/package.json +3 -10
@@ -4,10 +4,7 @@ export declare abstract class Responder {
4
4
  response: Response;
5
5
  }
6
6
  /**
7
- * A Responder subclass for streaming JSON Lines (JSONL) data.
8
- * @see https://vovk.dev/jsonlines
9
- * @param request The incoming Request object.
10
- * @param getResponse Optional function to create a custom Response object.
7
+ * Responder subclass for streaming JSON Lines. @see https://vovk.dev/jsonlines
11
8
  * @example
12
9
  * ```ts
13
10
  * import { JSONLinesResponder } from 'vovk';
@@ -16,19 +13,18 @@ export declare abstract class Responder {
16
13
  * return new Response(responder.readableStream, { headers: responder.headers });
17
14
  * });
18
15
  *
19
- * // Send items
20
- * responder.send({ ... });
21
- * // Close the stream when done
22
- * responder.close();
23
- * // Or throw an error
16
+ * responder.send({ ... }); // send items
17
+ * responder.close(); // close the stream when done
24
18
  * responder.throw(new Error('Something went wrong'));
25
- * // get the Response object, headers, etc.
26
19
  * const { response, headers } = responder;
27
20
  * ```
28
21
  */
29
22
  export declare class JSONLinesResponder<T> extends Responder {
30
23
  private isClosed;
31
24
  private i;
25
+ private pendingSends;
26
+ private sendQueue;
27
+ private hasSent;
32
28
  private controller?;
33
29
  private readonly encoder;
34
30
  readonly readableStream: ReadableStream | null;
@@ -37,6 +33,6 @@ export declare class JSONLinesResponder<T> extends Responder {
37
33
  constructor(request?: Request | null, getResponse?: (responder: JSONLinesResponder<T>) => Response);
38
34
  readonly send: (item: T) => Promise<void>;
39
35
  sendLineOrError: (data: T | StreamAbortMessage) => void;
40
- readonly close: () => void;
41
- readonly throw: (e: unknown) => void;
36
+ readonly close: () => Promise<void>;
37
+ readonly throw: (e: unknown) => Promise<void>;
42
38
  }
@@ -3,10 +3,7 @@ export class Responder {
3
3
  response;
4
4
  }
5
5
  /**
6
- * A Responder subclass for streaming JSON Lines (JSONL) data.
7
- * @see https://vovk.dev/jsonlines
8
- * @param request The incoming Request object.
9
- * @param getResponse Optional function to create a custom Response object.
6
+ * Responder subclass for streaming JSON Lines. @see https://vovk.dev/jsonlines
10
7
  * @example
11
8
  * ```ts
12
9
  * import { JSONLinesResponder } from 'vovk';
@@ -15,19 +12,19 @@ export class Responder {
15
12
  * return new Response(responder.readableStream, { headers: responder.headers });
16
13
  * });
17
14
  *
18
- * // Send items
19
- * responder.send({ ... });
20
- * // Close the stream when done
21
- * responder.close();
22
- * // Or throw an error
15
+ * responder.send({ ... }); // send items
16
+ * responder.close(); // close the stream when done
23
17
  * responder.throw(new Error('Something went wrong'));
24
- * // get the Response object, headers, etc.
25
18
  * const { response, headers } = responder;
26
19
  * ```
27
20
  */
28
21
  export class JSONLinesResponder extends Responder {
29
22
  isClosed = false;
30
23
  i = 0;
24
+ pendingSends = new Set();
25
+ // sends are chained so unawaited calls keep their order, see send()
26
+ sendQueue = Promise.resolve();
27
+ hasSent = false;
31
28
  controller;
32
29
  // biome-ignore lint/correctness/noUnusedPrivateClassMembers: biome bug
33
30
  encoder;
@@ -63,15 +60,28 @@ export class JSONLinesResponder extends Responder {
63
60
  this.controller?.enqueue(encoder?.encode(''));
64
61
  }
65
62
  send = async (item) => {
63
+ // chaining keeps lines in call order even when send() is not awaited
64
+ const promise = this.sendQueue.then(async () => {
65
+ try {
66
+ if (!this.hasSent) {
67
+ this.hasSent = true;
68
+ // zero timeout lets withValidationLibrary set onBeforeSend before the first send,
69
+ // otherwise immediate streaming would skip the first iteration validation
70
+ await new Promise((resolve) => setTimeout(resolve, 0));
71
+ }
72
+ this.sendLineOrError(await this.onBeforeSend(item, this.i++));
73
+ }
74
+ catch (e) {
75
+ this.throw(e);
76
+ }
77
+ });
78
+ this.sendQueue = promise;
79
+ this.pendingSends.add(promise);
66
80
  try {
67
- // onBeforeSend is set by withValidationLibrary if iteration validation is provided
68
- // in case if data is streamed immediately in a controller/service, we're going to lose the first iteration validation
69
- // the await with zero timeout ensures onBeforeSend is set before the first send
70
- await new Promise((resolve) => setTimeout(resolve, 0));
71
- this.sendLineOrError(await this.onBeforeSend(item, this.i++));
81
+ await promise;
72
82
  }
73
- catch (e) {
74
- this.throw(e);
83
+ finally {
84
+ this.pendingSends.delete(promise);
75
85
  }
76
86
  };
77
87
  sendLineOrError = (data) => {
@@ -80,14 +90,26 @@ export class JSONLinesResponder extends Responder {
80
90
  return;
81
91
  controller?.enqueue(encoder?.encode(`${JSON.stringify(data)}\n`));
82
92
  };
83
- close = () => {
84
- const { controller } = this;
93
+ close = async () => {
94
+ if (this.isClosed)
95
+ return;
96
+ // let unawaited send() calls finish first, per the documented send-then-close pattern
97
+ while (this.pendingSends.size) {
98
+ await Promise.allSettled([...this.pendingSends]);
99
+ }
85
100
  if (this.isClosed)
86
101
  return;
87
102
  this.isClosed = true;
88
- controller?.close();
103
+ this.controller?.close();
89
104
  };
90
105
  throw = (e) => {
106
+ // same rule as a non streaming handler, an error without a statusCode is internal
107
+ const isExpected = typeof e?.statusCode === 'number';
108
+ if (!isExpected && process.env.NODE_ENV === 'production') {
109
+ console.error('🐺 Unhandled error in a Vovk stream:', e);
110
+ this.sendLineOrError({ isError: true, reason: 'Internal server error' });
111
+ return this.close();
112
+ }
91
113
  this.sendLineOrError({ isError: true, reason: e instanceof Error ? e.message : e });
92
114
  return this.close();
93
115
  };
@@ -1,5 +1,5 @@
1
+ import type { DecoratorOptions, RouteHandler, VovkController } from '../types/core.js';
1
2
  import { HttpMethod, HttpStatus } from '../types/enums.js';
2
- import type { RouteHandler, VovkController, DecoratorOptions } from '../types/core.js';
3
3
  declare class VovkApp {
4
4
  #private;
5
5
  private static getHeadersFromDecoratorOptions;
@@ -1,10 +1,10 @@
1
1
  var _a;
2
- import { HttpException } from './http-exception.js';
3
- import { JSONLinesResponder, Responder } from './json-lines-responder.js';
4
- import { reqQuery } from '../req/req-query.js';
2
+ import { parseBody } from '../req/parse-body.js';
5
3
  import { reqMeta } from '../req/req-meta.js';
4
+ import { reqQuery } from '../req/req-query.js';
6
5
  import { HttpMethod, HttpStatus } from '../types/enums.js';
7
- import { parseBody } from '../req/parse-body.js';
6
+ import { HttpException } from './http-exception.js';
7
+ import { JSONLinesResponder, Responder } from './json-lines-responder.js';
8
8
  class VovkApp {
9
9
  static getHeadersFromDecoratorOptions(options) {
10
10
  if (!options)
@@ -12,7 +12,8 @@ class VovkApp {
12
12
  const corsHeaders = {
13
13
  'access-control-allow-origin': '*',
14
14
  'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS, HEAD',
15
- 'access-control-allow-headers': 'content-type, authorization',
15
+ // x-meta is ours, the client sends it whenever meta is set
16
+ 'access-control-allow-headers': 'content-type, authorization, x-meta',
16
17
  };
17
18
  const headers = {
18
19
  ...(options.cors ? corsHeaders : {}),
@@ -62,23 +63,29 @@ class VovkApp {
62
63
  #routeRegexCache = new Map();
63
64
  #routeSegmentsCache = new Map();
64
65
  #routeParamPositionsCache = new Map();
65
- #routeMatchCache = new Map();
66
+ // matches are only valid for the handlers map they were resolved against, so scope by its identity
67
+ #routeMatchCache = new WeakMap();
68
+ // concrete paths come from the URL, cap the per handlers map cache so it can't grow forever
69
+ static #ROUTE_MATCH_CACHE_LIMIT = 1000;
66
70
  #getHandler = ({ handlers, path, params, }) => {
67
71
  let methodParams = {};
68
72
  if (Object.keys(params).length === 0) {
69
73
  return { handler: handlers[''], methodParams };
70
74
  }
75
+ // a decoded "/" inside one segment makes the joined path ambiguous, /files/a%2Fb vs /files/a/b
76
+ const hasEncodedSlash = path.some((segment) => segment.includes('/'));
71
77
  const pathStr = path.join('/');
72
78
  // Fast path: Check if this exact path has been matched before
73
- const cachedMatch = this.#routeMatchCache.get(pathStr);
79
+ let matchCache = hasEncodedSlash ? undefined : this.#routeMatchCache.get(handlers);
80
+ const cachedMatch = matchCache?.get(pathStr);
74
81
  if (cachedMatch) {
75
82
  return {
76
83
  handler: handlers[cachedMatch.route],
77
84
  methodParams: cachedMatch.params,
78
85
  };
79
86
  }
80
- // Check for direct static route match
81
- let methodKey = handlers[pathStr] ? pathStr : null;
87
+ // Check for direct static route match, hasOwn so /toString doesn't resolve a prototype member
88
+ let methodKey = !hasEncodedSlash && Object.hasOwn(handlers, pathStr) ? pathStr : null;
82
89
  if (!methodKey) {
83
90
  const methodKeys = [];
84
91
  const pathLength = path.length;
@@ -140,7 +147,8 @@ class VovkApp {
140
147
  if (!regex) {
141
148
  const regexPattern = routeSegment
142
149
  .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
143
- .replace(/\\{(\w+)\\}/g, '(?<$1>[^/]+)');
150
+ // matched per segment, so a decoded "/" from %2F belongs to the value
151
+ .replace(/\\{(\w+)\\}/g, '(?<$1>[\\s\\S]+)');
144
152
  regex = new RegExp(`^${regexPattern}$`);
145
153
  this.#routeRegexCache.set(routeSegment, regex);
146
154
  }
@@ -177,9 +185,16 @@ class VovkApp {
177
185
  throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR, `Conflicting routes found: ${methodKeys.join(', ')}`);
178
186
  }
179
187
  [methodKey] = methodKeys;
180
- // Cache successful matches
181
- if (methodKey) {
182
- this.#routeMatchCache.set(pathStr, { route: methodKey, params: methodParams });
188
+ // Cache successful matches, an ambiguous joined path must not become a cache key
189
+ if (methodKey && !hasEncodedSlash) {
190
+ if (!matchCache) {
191
+ matchCache = new Map();
192
+ this.#routeMatchCache.set(handlers, matchCache);
193
+ }
194
+ if (matchCache.size >= _a.#ROUTE_MATCH_CACHE_LIMIT) {
195
+ matchCache.delete(matchCache.keys().next().value);
196
+ }
197
+ matchCache.set(pathStr, { route: methodKey, params: methodParams });
183
198
  }
184
199
  }
185
200
  if (methodKey) {
@@ -217,7 +232,20 @@ class VovkApp {
217
232
  headerList = null;
218
233
  }
219
234
  const xMeta = headerList?.get('x-meta');
220
- const xMetaHeader = xMeta && JSON.parse(xMeta);
235
+ let xMetaHeader = null;
236
+ if (xMeta) {
237
+ try {
238
+ xMetaHeader = JSON.parse(xMeta);
239
+ }
240
+ catch {
241
+ // malformed client input is a 400, not an uncaught SyntaxError
242
+ return this.#respondWithError({
243
+ req,
244
+ statusCode: HttpStatus.BAD_REQUEST,
245
+ message: 'Invalid x-meta request header',
246
+ });
247
+ }
248
+ }
221
249
  if (xMetaHeader)
222
250
  reqMeta(req, { xMetaHeader });
223
251
  const { handler, methodParams } = this.#getHandler({ handlers, path, params });
@@ -240,7 +268,8 @@ class VovkApp {
240
268
  try {
241
269
  await staticMethod._options?.before?.call(controller, req);
242
270
  await onBefore?.(req);
243
- const result = await staticMethod.call(controller, req, methodParams);
271
+ // dispatch via the latest wrapper so decorators applied above the HTTP decorator still run
272
+ const result = await (staticMethod._sourceMethod?.wrapper ?? staticMethod).call(controller, req, methodParams);
244
273
  if (result instanceof Response) {
245
274
  await onSuccess?.(result, req);
246
275
  // set headers from decorator options
@@ -279,6 +308,13 @@ class VovkApp {
279
308
  }
280
309
  }
281
310
  catch (e) {
311
+ // the outer catch already returned the response, so onError has to run here
312
+ try {
313
+ await controller._onError?.(e, req);
314
+ }
315
+ catch (onErrorError) {
316
+ console.error('An error caught in onError handler:', onErrorError);
317
+ }
282
318
  return responder.throw(e);
283
319
  }
284
320
  return responder.close();
@@ -296,11 +332,21 @@ class VovkApp {
296
332
  await controller._onError?.(err, req);
297
333
  }
298
334
  catch (onErrorError) {
299
- // eslint-disable-next-line no-console
300
335
  console.error('An error caught in onError handler:', onErrorError);
301
336
  }
302
337
  if (err.message !== 'NEXT_REDIRECT' && err.message !== 'NEXT_NOT_FOUND') {
303
338
  const statusCode = err.statusCode || HttpStatus.INTERNAL_SERVER_ERROR;
339
+ // an error without a statusCode is internal, its message and cause stay on the server in production
340
+ const isExpected = typeof err.statusCode === 'number';
341
+ if (!isExpected && process.env.NODE_ENV === 'production') {
342
+ console.error('🐺 Unhandled error in a Vovk handler:', err);
343
+ return this.#respondWithError({
344
+ req,
345
+ statusCode,
346
+ message: 'Internal server error',
347
+ options: staticMethod._options,
348
+ });
349
+ }
304
350
  return this.#respondWithError({
305
351
  req,
306
352
  statusCode,
package/dist/index.d.ts CHANGED
@@ -1,26 +1,24 @@
1
- export { HttpException } from './core/http-exception.js';
2
- export { createDecorator } from './core/create-decorator.js';
1
+ export { createFetcher, fetcher } from './client/fetcher.js';
2
+ export { progressive } from './client/progressive.js';
3
3
  export { controllersToStaticParams } from './core/controllers-to-static-params.js';
4
- export { multitenant } from './core/multitenant.js';
5
- export { JSONLinesResponder } from './core/json-lines-responder.js';
6
- export { toDownloadResponse } from './core/to-download-response.js';
7
- export { get, post, put, patch, del, head, options, prefix, cloneControllerMetadata } from './core/decorators.js';
4
+ export { createDecorator } from './core/create-decorator.js';
8
5
  export { decorate } from './core/decorate.js';
9
- export { progressive } from './client/progressive.js';
10
- export { fetcher, createFetcher } from './client/fetcher.js';
6
+ export { cloneControllerMetadata, del, get, head, options, patch, post, prefix, put } from './core/decorators.js';
7
+ export { HttpException } from './core/http-exception.js';
11
8
  export { initSegment } from './core/init-segment.js';
9
+ export { JSONLinesResponder } from './core/json-lines-responder.js';
10
+ export { multitenant } from './core/multitenant.js';
11
+ export { toDownloadResponse } from './core/to-download-response.js';
12
12
  export { operation } from './openapi/operation.js';
13
- export { createValidateOnClient } from './validation/create-validate-on-client.js';
14
- export { procedure } from './validation/procedure.js';
15
- export { ToModelOutput } from './tools/to-model-output.js';
16
- export { createTool } from './tools/create-tool.js';
17
13
  export { deriveTools } from './tools/derive-tools.js';
18
- export { HttpStatus, HttpMethod } from './types/enums.js';
19
- export type { VovkBody, VovkQuery, VovkParams, VovkInput, VovkReturnType, VovkYieldType, VovkOutput, VovkIteration, } from './types/inference.js';
20
- export type { VovkRequest } from './types/request.js';
21
- export type { VovkJSONSchemaBase } from './types/json-schema.js';
14
+ export { ToModelOutput } from './tools/to-model-output.js';
15
+ export type { VovkFetcher } from './types/client.js';
22
16
  export type { VovkConfig } from './types/config.js';
23
17
  export type { VovkSchema } from './types/core.js';
24
- export type { VovkFetcher } from './types/client.js';
18
+ export { HttpMethod, HttpStatus } from './types/enums.js';
19
+ export type { VovkBody, VovkInput, VovkIteration, VovkOutput, VovkParams, VovkQuery, VovkReturnType, VovkYieldType, } from './types/inference.js';
20
+ export type { VovkJSONSchemaBase } from './types/json-schema.js';
21
+ export type { VovkRequest } from './types/request.js';
25
22
  export type { VovkValidateOnClient } from './types/validation.js';
26
- export type { VovkTool } from './types/tools.js';
23
+ export { createValidateOnClient } from './validation/create-validate-on-client.js';
24
+ export { procedure } from './validation/procedure.js';
package/dist/index.js CHANGED
@@ -1,24 +1,23 @@
1
1
  // core
2
- export { HttpException } from './core/http-exception.js';
3
- export { createDecorator } from './core/create-decorator.js';
4
- export { controllersToStaticParams } from './core/controllers-to-static-params.js';
5
- export { multitenant } from './core/multitenant.js';
6
- export { JSONLinesResponder } from './core/json-lines-responder.js';
7
- export { toDownloadResponse } from './core/to-download-response.js';
8
- export { get, post, put, patch, del, head, options, prefix, cloneControllerMetadata } from './core/decorators.js';
9
- export { decorate } from './core/decorate.js';
2
+ export { createFetcher, fetcher } from './client/fetcher.js';
10
3
  // client
11
4
  export { progressive } from './client/progressive.js';
12
- export { fetcher, createFetcher } from './client/fetcher.js';
5
+ export { controllersToStaticParams } from './core/controllers-to-static-params.js';
6
+ export { createDecorator } from './core/create-decorator.js';
7
+ export { decorate } from './core/decorate.js';
8
+ export { cloneControllerMetadata, del, get, head, options, patch, post, prefix, put } from './core/decorators.js';
9
+ export { HttpException } from './core/http-exception.js';
13
10
  export { initSegment } from './core/init-segment.js';
11
+ export { JSONLinesResponder } from './core/json-lines-responder.js';
12
+ export { multitenant } from './core/multitenant.js';
13
+ export { toDownloadResponse } from './core/to-download-response.js';
14
14
  // openapi
15
15
  export { operation } from './openapi/operation.js';
16
- // validation
17
- export { createValidateOnClient } from './validation/create-validate-on-client.js';
18
- export { procedure } from './validation/procedure.js';
16
+ export { deriveTools } from './tools/derive-tools.js';
19
17
  // tools
20
18
  export { ToModelOutput } from './tools/to-model-output.js';
21
- export { createTool } from './tools/create-tool.js';
22
- export { deriveTools } from './tools/derive-tools.js';
23
19
  // types
24
- export { HttpStatus, HttpMethod } from './types/enums.js';
20
+ export { HttpMethod, HttpStatus } from './types/enums.js';
21
+ // validation
22
+ export { createValidateOnClient } from './validation/create-validate-on-client.js';
23
+ export { procedure } from './validation/procedure.js';
@@ -1,18 +1,20 @@
1
- export { deepExtend } from './utils/deep-extend.js';
1
+ export { readableStreamToAsyncIterable } from './client/default-stream-handler.js';
2
2
  export { resolveGeneratorConfigValues } from './core/resolve-generator-config-values.js';
3
- export { createCodeSamples } from './samples/create-code-samples.js';
4
- export { withValidationLibrary } from './validation/with-validation-library.js';
5
- export { validationSchemasObjectToSingleValidationSchema } from './validation/validation-schemas-object-to-single-validation-schema.js';
6
- export { operation } from './openapi/operation.js';
7
- export { openAPIToVovkSchema } from './openapi/openapi-to-vovk-schema/index.js';
3
+ export { vovkApp } from './core/vovk-app.js';
8
4
  export { applyComponentsSchemas, reattachMixinDefs, } from './openapi/openapi-to-vovk-schema/apply-components-schemas.js';
5
+ export { openAPIToVovkSchema } from './openapi/openapi-to-vovk-schema/index.js';
6
+ export { operation } from './openapi/operation.js';
9
7
  export { vovkSchemaToOpenAPI } from './openapi/vovk-schema-to-openapi.js';
10
- export { readableStreamToAsyncIterable } from './client/default-stream-handler.js';
11
- export { VovkSchemaIdEnum } from './types/enums.js';
8
+ export { createCodeSamples } from './samples/create-code-samples.js';
12
9
  export type { MCPModelOutput } from './tools/to-model-output-mcp.js';
13
- export type { VovkErrorResponse, VovkMetaSchema, VovkSegmentSchema, VovkControllerSchema, VovkHandlerSchema, VovkValidationType, } from './types/core.js';
14
- export type { VovkTypedProcedure } from './types/validation.js';
15
- export type { VovkOutputConfig, VovkReadmeConfig, VovkSamplesConfig, VovkPackageJson, VovkOpenAPIMixin, VovkOpenAPIMixinNormalized, VovkStrictConfig, VovkBundleConfig, VovkSegmentConfig, } from './types/config.js';
10
+ export type { VovkFetcherOptions, VovkRPCModule, VovkStreamAsyncIterable } from './types/client.js';
11
+ export type { VovkBundleConfig, VovkOpenAPIMixin, VovkOpenAPIMixinNormalized, VovkOutputConfig, VovkPackageJson, VovkReadmeConfig, VovkSamplesConfig, VovkSegmentConfig, VovkStrictConfig, } from './types/config.js';
12
+ export type { VovkControllerSchema, VovkErrorResponse, VovkHandlerSchema, VovkMetaSchema, VovkSegmentSchema, VovkValidationType, } from './types/core.js';
13
+ export { VovkSchemaIdEnum } from './types/enums.js';
16
14
  export type { VovkOperationObject } from './types/operation.js';
17
- export type { VovkRPCModule, VovkFetcherOptions, VovkStreamAsyncIterable } from './types/client.js';
15
+ export type { StandardToolV0 } from './types/standard-tool.js';
18
16
  export type { IsAny, IsNotAny } from './types/utils.js';
17
+ export type { VovkTypedProcedure } from './types/validation.js';
18
+ export { deepExtend } from './utils/deep-extend.js';
19
+ export { validationSchemasObjectToSingleValidationSchema } from './validation/validation-schemas-object-to-single-validation-schema.js';
20
+ export { withValidationLibrary } from './validation/with-validation-library.js';
package/dist/internal.js CHANGED
@@ -1,12 +1,13 @@
1
1
  // internal exports for other packages and tests
2
- export { deepExtend } from './utils/deep-extend.js';
2
+ export { readableStreamToAsyncIterable } from './client/default-stream-handler.js';
3
3
  export { resolveGeneratorConfigValues } from './core/resolve-generator-config-values.js';
4
- export { createCodeSamples } from './samples/create-code-samples.js';
5
- export { withValidationLibrary } from './validation/with-validation-library.js';
6
- export { validationSchemasObjectToSingleValidationSchema } from './validation/validation-schemas-object-to-single-validation-schema.js';
7
- export { operation } from './openapi/operation.js';
8
- export { openAPIToVovkSchema } from './openapi/openapi-to-vovk-schema/index.js';
4
+ export { vovkApp } from './core/vovk-app.js';
9
5
  export { applyComponentsSchemas, reattachMixinDefs, } from './openapi/openapi-to-vovk-schema/apply-components-schemas.js';
6
+ export { openAPIToVovkSchema } from './openapi/openapi-to-vovk-schema/index.js';
7
+ export { operation } from './openapi/operation.js';
10
8
  export { vovkSchemaToOpenAPI } from './openapi/vovk-schema-to-openapi.js';
11
- export { readableStreamToAsyncIterable } from './client/default-stream-handler.js';
9
+ export { createCodeSamples } from './samples/create-code-samples.js';
12
10
  export { VovkSchemaIdEnum } from './types/enums.js';
11
+ export { deepExtend } from './utils/deep-extend.js';
12
+ export { validationSchemasObjectToSingleValidationSchema } from './validation/validation-schemas-object-to-single-validation-schema.js';
13
+ export { withValidationLibrary } from './validation/with-validation-library.js';
@@ -1,5 +1,5 @@
1
- import { HttpStatus } from '../types/enums.js';
2
1
  import { createDecorator } from '../core/create-decorator.js';
2
+ import { HttpStatus } from '../types/enums.js';
3
3
  const statusDisplayText = {
4
4
  [HttpStatus.NULL]: 'Error',
5
5
  [HttpStatus.CONTINUE]: 'Continue',
@@ -43,7 +43,7 @@ const statusDisplayText = {
43
43
  [HttpStatus.UNPROCESSABLE_ENTITY]: 'Unprocessable Entity',
44
44
  [HttpStatus.FAILED_DEPENDENCY]: 'Failed Dependency',
45
45
  [HttpStatus.PRECONDITION_REQUIRED]: 'Precondition Required',
46
- [HttpStatus.TOO_MANY_TRequestS]: 'Too Many Requests',
46
+ [HttpStatus.TOO_MANY_REQUESTS]: 'Too Many Requests',
47
47
  [HttpStatus.INTERNAL_SERVER_ERROR]: 'Internal Server Error',
48
48
  [HttpStatus.NOT_IMPLEMENTED]: 'Not Implemented',
49
49
  [HttpStatus.BAD_GATEWAY]: 'Bad Gateway',
@@ -1,17 +1,6 @@
1
1
  import type { ComponentsObject } from 'openapi3-ts/oas31';
2
2
  import type { VovkJSONSchemaBase } from '../../types/json-schema.js';
3
- export declare function applyComponentsSchemas(schema: VovkJSONSchemaBase, components: ComponentsObject['schemas'], mixinName: string,
4
- /**
5
- * true (default): embed the ref closure in `$defs` (self-contained — for AJV + Rust).
6
- * false: keep `#/components/schemas/X`, emit no `$defs` (response slots, typed via
7
- * `x-tsType`) — avoids the per-handler dup that overflows JSON.stringify on big specs.
8
- */
9
- emitDefs?: boolean): VovkJSONSchemaBase | VovkJSONSchemaBase[];
10
- /**
11
- * Re-attach a response slot's `$defs` closure at render time, for generators that
12
- * resolve `$ref` against a self-contained schema (Rust). Pulls components from the
13
- * segment's shared meta → identical to the `emitDefs=true` slot. No-op for non-mixin.
14
- */
3
+ export declare function applyComponentsSchemas(schema: VovkJSONSchemaBase, components: ComponentsObject['schemas'], mixinName: string, emitDefs?: boolean): VovkJSONSchemaBase | VovkJSONSchemaBase[];
15
4
  export declare function reattachMixinDefs(slot: VovkJSONSchemaBase | undefined, segment: {
16
5
  segmentType?: string;
17
6
  segmentName: string;
@@ -15,11 +15,8 @@ function cloneJSON(obj) {
15
15
  return result;
16
16
  }
17
17
  export function applyComponentsSchemas(schema, components, mixinName,
18
- /**
19
- * true (default): embed the ref closure in `$defs` (self-contained — for AJV + Rust).
20
- * false: keep `#/components/schemas/X`, emit no `$defs` (response slots, typed via
21
- * `x-tsType`) — avoids the per-handler dup that overflows JSON.stringify on big specs.
22
- */
18
+ // true (default): embed the ref closure in `$defs`, self-contained (AJV + Rust);
19
+ // false: keep `#/components/schemas/X` refs, no `$defs` (avoids the per-handler dup that overflows big specs)
23
20
  emitDefs = true) {
24
21
  const key = 'components/schemas';
25
22
  if (!components || !Object.keys(components).length)
@@ -75,11 +72,8 @@ emitDefs = true) {
75
72
  // Process the main schema
76
73
  return processSchema(result);
77
74
  }
78
- /**
79
- * Re-attach a response slot's `$defs` closure at render time, for generators that
80
- * resolve `$ref` against a self-contained schema (Rust). Pulls components from the
81
- * segment's shared meta → identical to the `emitDefs=true` slot. No-op for non-mixin.
82
- */
75
+ // re-attaches a response slot's `$defs` closure at render time (Rust needs self-contained schemas);
76
+ // pulls components from the segment's shared meta, no-op for non-mixin
83
77
  export function reattachMixinDefs(slot, segment) {
84
78
  if (!slot || segment?.segmentType !== 'mixin')
85
79
  return slot;
@@ -1,5 +1,5 @@
1
- import type { VovkSchema } from '../../types/core.js';
2
1
  import type { VovkOpenAPIMixinNormalized } from '../../types/config.js';
2
+ import type { VovkSchema } from '../../types/core.js';
3
3
  export declare function openAPIToVovkSchema({ apiRoot, source: { object: openAPIObject }, getModuleName, getMethodName, filterOperations, pruneComponents, errorMessageKey, segmentName, }: VovkOpenAPIMixinNormalized & {
4
4
  segmentName?: string;
5
5
  }): VovkSchema;
@@ -1,8 +1,8 @@
1
+ import { schemaToTsType } from '../../samples/schema-to-ts-type.js';
2
+ import { VovkSchemaIdEnum } from '../../types/enums.js';
1
3
  import { applyComponentsSchemas } from './apply-components-schemas.js';
2
4
  import { inlineRefs } from './inline-refs.js';
3
5
  import { pruneComponentsSchemas } from './prune-components-schemas.js';
4
- import { VovkSchemaIdEnum } from '../../types/enums.js';
5
- import { schemaToTsType } from '../../samples/schema-to-ts-type.js';
6
6
  function getTsTypeString(contentType, schema) {
7
7
  const tsTypes = new Set(contentType.flatMap((ct) => {
8
8
  switch (ct) {
@@ -20,8 +20,24 @@ function getTsTypeString(contentType, schema) {
20
20
  }));
21
21
  return [...tsTypes].join(' | ') || schemaToTsType(schema);
22
22
  }
23
+ // a spec is third party input, its x-tsType would land in the generated client as raw TS
24
+ function stripXTsType(value) {
25
+ if (Array.isArray(value))
26
+ return value.map(stripXTsType);
27
+ if (!value || typeof value !== 'object')
28
+ return value;
29
+ const result = {};
30
+ for (const [key, val] of Object.entries(value)) {
31
+ if (key === 'x-tsType')
32
+ continue;
33
+ result[key] = stripXTsType(val);
34
+ }
35
+ return result;
36
+ }
23
37
  export function openAPIToVovkSchema({ apiRoot, source: { object: openAPIObject }, getModuleName, getMethodName, filterOperations, pruneComponents, errorMessageKey, segmentName, }) {
24
38
  segmentName = segmentName ?? '';
39
+ // x-tsType is emitted verbatim into the generated client, only ours may reach it
40
+ openAPIObject = stripXTsType(openAPIObject);
25
41
  const forceApiRoot = apiRoot ||
26
42
  (openAPIObject.servers?.[0]?.url ??
27
43
  ('host' in openAPIObject
@@ -161,10 +177,8 @@ export function openAPIToVovkSchema({ apiRoot, source: { object: openAPIObject }
161
177
  });
162
178
  });
163
179
  if (pruneComponents && noPathsOpenAPIObject.components?.schemas) {
164
- // Reassign with fresh objects only — `noPathsOpenAPIObject` shares references with the
165
- // caller's spec, so the original `components.schemas` must stay untouched. Walking the
166
- // whole controllers tree (validation slots + raw operation objects) keeps every `$ref`
167
- // a kept handler carries resolvable against the pruned meta.
180
+ // reassign with fresh objects only, the caller's spec shares references so its
181
+ // components.schemas must stay untouched; walking the whole controllers tree keeps every kept $ref resolvable
168
182
  segment.meta = {
169
183
  openAPIObject: {
170
184
  ...noPathsOpenAPIObject,
@@ -1,9 +1,2 @@
1
1
  import type { OpenAPIObject } from 'openapi3-ts/oas31';
2
- /**
3
- * Resolves $ref references at the first level only (except for components/schemas references)
4
- * For arrays, checks each item at the first level
5
- * @param obj - The object to process (may contain $ref properties)
6
- * @param openAPIObject - The complete OpenAPI document containing definitions
7
- * @returns The object with resolved references (except components/schemas)
8
- */
9
2
  export declare function inlineRefs<T extends object>(obj: unknown, openAPIObject: OpenAPIObject): T | null;
@@ -1,10 +1,4 @@
1
- /**
2
- * Resolves $ref references at the first level only (except for components/schemas references)
3
- * For arrays, checks each item at the first level
4
- * @param obj - The object to process (may contain $ref properties)
5
- * @param openAPIObject - The complete OpenAPI document containing definitions
6
- * @returns The object with resolved references (except components/schemas)
7
- */
1
+ // resolves $ref at the first level only (skips components/schemas refs), arrays checked per item
8
2
  export function inlineRefs(obj, openAPIObject) {
9
3
  // Handle null or undefined
10
4
  if (obj === null || obj === undefined) {
@@ -63,12 +57,7 @@ export function inlineRefs(obj, openAPIObject) {
63
57
  // For regular objects without $ref, return as-is (no recursion)
64
58
  return obj;
65
59
  }
66
- /**
67
- * Resolves a JSON Reference ($ref) to its target value
68
- * @param ref - The reference string (e.g., "#/components/parameters/id")
69
- * @param openAPIObject - The complete OpenAPI document
70
- * @returns The resolved value or undefined if not found
71
- */
60
+ // resolves a local $ref like "#/components/parameters/id", undefined if not found
72
61
  function resolveRef(ref, openAPIObject) {
73
62
  // Handle only local references (starting with #)
74
63
  if (!ref.startsWith('#/')) {
@@ -1,7 +1,2 @@
1
1
  import type { ComponentsObject } from 'openapi3-ts/oas31';
2
- /**
3
- * Shrinks a `components.schemas` dict to the transitive `$ref` closure of `roots`
4
- * (BFS with a visited set — component graphs of large specs like Stripe are cyclic).
5
- * Preserves the original key order for deterministic output.
6
- */
7
2
  export declare function pruneComponentsSchemas(roots: unknown, componentsSchemas: NonNullable<ComponentsObject['schemas']>): NonNullable<ComponentsObject['schemas']>;