vovk 3.0.0-draft.98 → 3.0.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 (167) hide show
  1. package/README.md +22 -13
  2. package/bin/index.mjs +10 -0
  3. package/dist/client/createRPC.d.ts +13 -3
  4. package/dist/client/createRPC.js +112 -50
  5. package/dist/client/defaultHandler.d.ts +5 -1
  6. package/dist/client/defaultHandler.js +12 -9
  7. package/dist/client/defaultStreamHandler.d.ts +16 -4
  8. package/dist/client/defaultStreamHandler.js +259 -62
  9. package/dist/client/fetcher.d.ts +41 -3
  10. package/dist/client/fetcher.js +125 -60
  11. package/dist/client/progressive.d.ts +15 -0
  12. package/dist/client/progressive.js +56 -0
  13. package/dist/{utils → client}/serializeQuery.d.ts +2 -2
  14. package/dist/{utils → client}/serializeQuery.js +1 -4
  15. package/dist/core/HttpException.d.ts +16 -0
  16. package/dist/core/HttpException.js +26 -0
  17. package/dist/core/JSONLinesResponder.d.ts +42 -0
  18. package/dist/core/JSONLinesResponder.js +92 -0
  19. package/dist/core/controllersToStaticParams.d.ts +13 -0
  20. package/dist/core/controllersToStaticParams.js +36 -0
  21. package/dist/core/createDecorator.d.ts +12 -0
  22. package/dist/{createDecorator.js → core/createDecorator.js} +18 -12
  23. package/dist/core/decorators.d.ts +59 -0
  24. package/dist/core/decorators.js +132 -0
  25. package/dist/core/getSchema.d.ts +21 -0
  26. package/dist/core/getSchema.js +31 -0
  27. package/dist/core/initSegment.d.ts +33 -0
  28. package/dist/core/initSegment.js +35 -0
  29. package/dist/core/multitenant.d.ts +33 -0
  30. package/dist/core/multitenant.js +132 -0
  31. package/dist/core/resolveGeneratorConfigValues.d.ts +19 -0
  32. package/dist/core/resolveGeneratorConfigValues.js +59 -0
  33. package/dist/{utils → core}/setHandlerSchema.d.ts +2 -2
  34. package/dist/{utils → core}/setHandlerSchema.js +1 -4
  35. package/dist/core/toDownloadResponse.d.ts +11 -0
  36. package/dist/core/toDownloadResponse.js +25 -0
  37. package/dist/core/vovkApp.d.ts +36 -0
  38. package/dist/core/vovkApp.js +316 -0
  39. package/dist/index.d.ts +25 -59
  40. package/dist/index.js +23 -23
  41. package/dist/internal.d.ts +17 -0
  42. package/dist/internal.js +10 -0
  43. package/dist/openapi/error.d.ts +2 -0
  44. package/dist/openapi/error.js +97 -0
  45. package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.d.ts +3 -0
  46. package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.js +65 -0
  47. package/dist/openapi/openAPIToVovkSchema/index.d.ts +5 -0
  48. package/dist/openapi/openAPIToVovkSchema/index.js +153 -0
  49. package/dist/openapi/openAPIToVovkSchema/inlineRefs.d.ts +9 -0
  50. package/dist/openapi/openAPIToVovkSchema/inlineRefs.js +99 -0
  51. package/dist/openapi/operation.d.ts +10 -0
  52. package/dist/openapi/operation.js +19 -0
  53. package/dist/openapi/tool.d.ts +2 -0
  54. package/dist/openapi/tool.js +12 -0
  55. package/dist/openapi/vovkSchemaToOpenAPI.d.ts +21 -0
  56. package/dist/openapi/vovkSchemaToOpenAPI.js +250 -0
  57. package/dist/req/bufferBody.d.ts +1 -0
  58. package/dist/req/bufferBody.js +30 -0
  59. package/dist/req/parseBody.d.ts +4 -0
  60. package/dist/req/parseBody.js +49 -0
  61. package/dist/req/parseForm.d.ts +1 -0
  62. package/dist/req/parseForm.js +24 -0
  63. package/dist/{utils → req}/parseQuery.d.ts +1 -2
  64. package/dist/{utils → req}/parseQuery.js +2 -5
  65. package/dist/req/reqMeta.d.ts +2 -0
  66. package/dist/{utils → req}/reqMeta.js +1 -4
  67. package/dist/req/reqQuery.d.ts +2 -0
  68. package/dist/req/reqQuery.js +4 -0
  69. package/dist/req/validateContentType.d.ts +1 -0
  70. package/dist/req/validateContentType.js +32 -0
  71. package/dist/samples/createCodeSamples.d.ts +20 -0
  72. package/dist/samples/createCodeSamples.js +293 -0
  73. package/dist/samples/objectToCode.d.ts +8 -0
  74. package/dist/samples/objectToCode.js +38 -0
  75. package/dist/samples/schemaToCode.d.ts +11 -0
  76. package/dist/samples/schemaToCode.js +264 -0
  77. package/dist/samples/schemaToObject.d.ts +2 -0
  78. package/dist/samples/schemaToObject.js +164 -0
  79. package/dist/samples/schemaToTsType.d.ts +2 -0
  80. package/dist/samples/schemaToTsType.js +114 -0
  81. package/dist/tools/ToModelOutput.d.ts +8 -0
  82. package/dist/tools/ToModelOutput.js +10 -0
  83. package/dist/tools/createTool.d.ts +126 -0
  84. package/dist/tools/createTool.js +6 -0
  85. package/dist/tools/createToolFactory.d.ts +135 -0
  86. package/dist/tools/createToolFactory.js +61 -0
  87. package/dist/tools/deriveTools.d.ts +46 -0
  88. package/dist/tools/deriveTools.js +134 -0
  89. package/dist/tools/toModelOutputDefault.d.ts +7 -0
  90. package/dist/tools/toModelOutputDefault.js +7 -0
  91. package/dist/tools/toModelOutputMCP.d.ts +30 -0
  92. package/dist/tools/toModelOutputMCP.js +54 -0
  93. package/dist/tsconfig.tsbuildinfo +1 -0
  94. package/dist/types/client.d.ts +140 -0
  95. package/dist/types/client.js +1 -0
  96. package/dist/types/config.d.ts +151 -0
  97. package/dist/types/config.js +1 -0
  98. package/dist/types/core.d.ts +115 -0
  99. package/dist/types/core.js +1 -0
  100. package/dist/types/enums.d.ts +75 -0
  101. package/dist/{types.js → types/enums.js} +21 -9
  102. package/dist/types/inference.d.ts +117 -0
  103. package/dist/types/inference.js +1 -0
  104. package/dist/types/json-schema.d.ts +51 -0
  105. package/dist/types/json-schema.js +1 -0
  106. package/dist/types/operation.d.ts +5 -0
  107. package/dist/types/operation.js +1 -0
  108. package/dist/types/package.d.ts +544 -0
  109. package/dist/types/package.js +5 -0
  110. package/dist/types/request.d.ts +48 -0
  111. package/dist/types/request.js +1 -0
  112. package/dist/types/standard-schema.d.ts +117 -0
  113. package/dist/types/standard-schema.js +6 -0
  114. package/dist/types/tools.d.ts +43 -0
  115. package/dist/types/tools.js +1 -0
  116. package/dist/types/utils.d.ts +9 -0
  117. package/dist/types/utils.js +1 -0
  118. package/dist/types/validation.d.ts +48 -0
  119. package/dist/types/validation.js +1 -0
  120. package/dist/utils/camelCase.d.ts +6 -0
  121. package/dist/utils/camelCase.js +34 -0
  122. package/dist/utils/deepExtend.d.ts +53 -0
  123. package/dist/utils/deepExtend.js +128 -0
  124. package/dist/utils/fileNameToDisposition.d.ts +1 -0
  125. package/dist/utils/fileNameToDisposition.js +3 -0
  126. package/dist/utils/shim.d.ts +1 -0
  127. package/dist/utils/shim.js +1 -1
  128. package/dist/utils/toKebabCase.d.ts +1 -0
  129. package/dist/utils/toKebabCase.js +5 -0
  130. package/dist/utils/trimPath.d.ts +1 -0
  131. package/dist/utils/trimPath.js +1 -0
  132. package/dist/utils/upperFirst.d.ts +1 -0
  133. package/dist/utils/upperFirst.js +3 -0
  134. package/dist/validation/createStandardValidation.d.ts +268 -0
  135. package/dist/validation/createStandardValidation.js +45 -0
  136. package/dist/validation/createValidateOnClient.d.ts +14 -0
  137. package/dist/validation/createValidateOnClient.js +23 -0
  138. package/dist/validation/procedure.d.ts +261 -0
  139. package/dist/validation/procedure.js +8 -0
  140. package/dist/validation/withValidationLibrary.d.ts +119 -0
  141. package/dist/validation/withValidationLibrary.js +174 -0
  142. package/package.json +44 -10
  143. package/dist/HttpException.d.ts +0 -7
  144. package/dist/HttpException.js +0 -15
  145. package/dist/StreamJSONResponse.d.ts +0 -14
  146. package/dist/StreamJSONResponse.js +0 -57
  147. package/dist/VovkApp.d.ts +0 -29
  148. package/dist/VovkApp.js +0 -188
  149. package/dist/client/index.d.ts +0 -3
  150. package/dist/client/index.js +0 -7
  151. package/dist/client/types.d.ts +0 -104
  152. package/dist/client/types.js +0 -2
  153. package/dist/createDecorator.d.ts +0 -6
  154. package/dist/createVovkApp.d.ts +0 -62
  155. package/dist/createVovkApp.js +0 -118
  156. package/dist/types.d.ts +0 -220
  157. package/dist/utils/generateStaticAPI.d.ts +0 -4
  158. package/dist/utils/generateStaticAPI.js +0 -18
  159. package/dist/utils/getSchema.d.ts +0 -20
  160. package/dist/utils/getSchema.js +0 -33
  161. package/dist/utils/reqForm.d.ts +0 -2
  162. package/dist/utils/reqForm.js +0 -13
  163. package/dist/utils/reqMeta.d.ts +0 -2
  164. package/dist/utils/reqQuery.d.ts +0 -2
  165. package/dist/utils/reqQuery.js +0 -10
  166. package/dist/utils/withValidation.d.ts +0 -20
  167. package/dist/utils/withValidation.js +0 -72
@@ -1,82 +1,279 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultStreamHandler = exports.DEFAULT_ERROR_MESSAGE = void 0;
4
- const types_1 = require("../types");
5
- const HttpException_1 = require("../HttpException");
6
- require("../utils/shim");
7
- exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultStreamHandler';
8
- const defaultStreamHandler = async (response) => {
9
- if (!response.ok) {
10
- let result;
1
+ import { HttpStatus } from '../types/enums.js';
2
+ import { HttpException } from '../core/HttpException.js';
3
+ import '../utils/shim.js';
4
+ export const DEFAULT_ERROR_MESSAGE = 'An unknown error at the default stream handler';
5
+ /**
6
+ * Converts a ReadableStream of JSON Lines into a VovkStreamAsyncIterable.
7
+ * This is the core streaming logic extracted for reuse outside of HTTP contexts.
8
+ * @see https://vovk.dev/jsonlines
9
+ */
10
+ export const readableStreamToAsyncIterable = ({ readableStream, abortController, }) => {
11
+ const reader = readableStream.getReader();
12
+ const subscribers = new Set();
13
+ // State
14
+ let isAbortedWithoutError = false;
15
+ let streamExhausted = false;
16
+ let streamError = null;
17
+ let errorIndex = -1;
18
+ let primaryStarted = false;
19
+ const cachedItems = [];
20
+ const waiters = [];
21
+ // --- Helper functions ---
22
+ const notifyWaiters = () => {
23
+ for (let i = waiters.length - 1; i >= 0; i--) {
24
+ const waiter = waiters[i];
25
+ let handled = false;
26
+ if (streamError && waiter.index >= errorIndex) {
27
+ waiter.reject(streamError);
28
+ handled = true;
29
+ }
30
+ else if (waiter.index < cachedItems.length) {
31
+ waiter.resolve({ value: cachedItems[waiter.index], done: false });
32
+ handled = true;
33
+ }
34
+ else if (streamExhausted || (abortController?.signal.aborted && isAbortedWithoutError)) {
35
+ waiter.resolve({ value: undefined, done: true });
36
+ handled = true;
37
+ }
38
+ if (handled) {
39
+ waiters.splice(i, 1);
40
+ }
41
+ }
42
+ };
43
+ const setStreamError = (error) => {
44
+ errorIndex = cachedItems.length;
45
+ streamError = error;
46
+ notifyWaiters();
47
+ };
48
+ const disposeStream = (reason) => {
49
+ isAbortedWithoutError = true;
50
+ streamExhausted = true;
51
+ notifyWaiters();
52
+ abortController?.abort(reason);
53
+ reader.cancel().catch(() => { });
54
+ };
55
+ // --- Primary reader ---
56
+ const runPrimaryReader = async () => {
57
+ let buffer = '';
58
+ let iterationIndex = 0;
59
+ // Returns true if the stream should stop (error encountered)
60
+ const processLine = (line) => {
61
+ let data;
62
+ try {
63
+ data = JSON.parse(line);
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ if (data) {
69
+ subscribers.forEach((cb) => {
70
+ if (!abortController?.signal.aborted)
71
+ cb(data, iterationIndex);
72
+ });
73
+ iterationIndex++;
74
+ if (typeof data === 'object' && data !== null && 'isError' in data && 'reason' in data) {
75
+ const upcomingError = data.reason;
76
+ abortController?.abort(upcomingError);
77
+ const error = typeof upcomingError === 'string' ? new Error(upcomingError) : upcomingError;
78
+ setStreamError(error);
79
+ return true;
80
+ }
81
+ else if (!abortController?.signal.aborted) {
82
+ cachedItems.push(data);
83
+ notifyWaiters();
84
+ }
85
+ }
86
+ return false;
87
+ };
11
88
  try {
12
- result = await response.json();
89
+ while (true) {
90
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
91
+ break;
92
+ }
93
+ let value;
94
+ let done;
95
+ try {
96
+ ({ value, done } = await reader.read());
97
+ if (done)
98
+ break;
99
+ }
100
+ catch (error) {
101
+ if (error?.name === 'AbortError' && isAbortedWithoutError) {
102
+ break;
103
+ }
104
+ const err = new Error('JSONLines stream error. ' + String(error));
105
+ err.cause = error;
106
+ setStreamError(err);
107
+ return;
108
+ }
109
+ const chunk = typeof value === 'string'
110
+ ? value
111
+ : typeof value === 'number'
112
+ ? String.fromCharCode(value)
113
+ : new TextDecoder().decode(value);
114
+ buffer += chunk;
115
+ let newlineIdx;
116
+ while ((newlineIdx = buffer.indexOf('\n')) !== -1) {
117
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
118
+ break;
119
+ }
120
+ const line = buffer.slice(0, newlineIdx);
121
+ buffer = buffer.slice(newlineIdx + 1);
122
+ if (!line)
123
+ continue;
124
+ if (processLine(line))
125
+ return;
126
+ }
127
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
128
+ break;
129
+ }
130
+ }
131
+ // Process any remaining data in the buffer (last line without trailing newline)
132
+ const remaining = buffer.trim();
133
+ if (remaining) {
134
+ processLine(remaining);
135
+ }
13
136
  }
14
- catch {
15
- // ignore parsing errors
137
+ finally {
138
+ streamExhausted = true;
139
+ notifyWaiters();
16
140
  }
17
- // handle server errors
18
- throw new HttpException_1.HttpException(response.status, result.message ?? exports.DEFAULT_ERROR_MESSAGE);
19
- }
20
- if (!response.body)
21
- throw new HttpException_1.HttpException(types_1.HttpStatus.NULL, 'Stream body is falsy. Check your controller code.');
22
- const reader = response.body.getReader();
23
- // if streaming is too rapid, we need to make sure that the loop is stopped
24
- let canceled = false;
141
+ };
142
+ // --- Async iterator ---
25
143
  async function* asyncIterator() {
26
- let prepend = '';
144
+ if (!primaryStarted) {
145
+ primaryStarted = true;
146
+ void runPrimaryReader();
147
+ }
148
+ let index = 0;
27
149
  while (true) {
28
- let value;
29
- let done = false;
30
- try {
31
- ({ value, done } = await reader.read());
150
+ // Check error first
151
+ if (streamError && index >= errorIndex) {
152
+ throw streamError;
153
+ }
154
+ // Clean exit on abort without error
155
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
156
+ return;
32
157
  }
33
- catch (error) {
34
- await reader.cancel();
35
- const err = new Error('Stream error. ' + String(error));
36
- err.cause = error;
37
- throw err;
158
+ // Yield from cache if available
159
+ if (index < cachedItems.length) {
160
+ yield cachedItems[index++];
161
+ continue;
38
162
  }
39
- if (done) {
163
+ // Stream finished
164
+ if (streamExhausted) {
40
165
  return;
41
166
  }
42
- // typeof value === 'number' is a workaround for React Native
43
- const string = typeof value === 'number' ? String.fromCharCode(value) : new TextDecoder().decode(value);
44
- prepend += string;
45
- const lines = prepend.split('\n').filter(Boolean);
46
- for (const line of lines) {
47
- let data;
48
- try {
49
- data = JSON.parse(line);
50
- prepend = '';
167
+ // Wait for next item or completion
168
+ const result = await new Promise((resolve, reject) => {
169
+ // Re-check state inside promise to handle race conditions
170
+ if (streamError && index >= errorIndex) {
171
+ reject(streamError);
172
+ return;
51
173
  }
52
- catch {
53
- break;
174
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
175
+ resolve({ value: undefined, done: true });
176
+ return;
54
177
  }
55
- if (data) {
56
- if ('isError' in data && 'reason' in data) {
57
- const upcomingError = data.reason;
58
- await reader.cancel();
59
- if (typeof upcomingError === 'string') {
60
- throw new Error(upcomingError);
61
- }
62
- throw upcomingError;
63
- }
64
- else if (!canceled) {
65
- yield data;
66
- }
178
+ if (index < cachedItems.length) {
179
+ resolve({ value: cachedItems[index], done: false });
180
+ return;
181
+ }
182
+ if (streamExhausted) {
183
+ resolve({ value: undefined, done: true });
184
+ return;
67
185
  }
186
+ waiters.push({ index, resolve, reject });
187
+ });
188
+ if (result.done) {
189
+ return;
68
190
  }
191
+ index++;
192
+ yield result.value;
69
193
  }
70
194
  }
195
+ // --- Public API ---
196
+ const asPromise = async () => {
197
+ const items = [];
198
+ for await (const item of asyncIterator()) {
199
+ items.push(item);
200
+ }
201
+ return items;
202
+ };
203
+ const abortSilently = (reason) => {
204
+ isAbortedWithoutError = true;
205
+ streamExhausted = true;
206
+ notifyWaiters();
207
+ abortController?.abort(reason);
208
+ reader.cancel().catch(() => { });
209
+ };
71
210
  return {
72
- status: response.status,
211
+ asPromise,
212
+ // abortController,
73
213
  [Symbol.asyncIterator]: asyncIterator,
74
- [Symbol.dispose]: () => reader.cancel(),
75
- [Symbol.asyncDispose]: () => reader.cancel(),
76
- cancel: () => {
77
- canceled = true;
78
- return reader.cancel();
214
+ [Symbol.dispose]: () => disposeStream('Stream disposed'),
215
+ [Symbol.asyncDispose]: async () => disposeStream('Stream async disposed'),
216
+ abortSilently,
217
+ onIterate: (cb) => {
218
+ if (abortController?.signal.aborted)
219
+ return () => { };
220
+ subscribers.add(cb);
221
+ return () => subscribers.delete(cb);
79
222
  },
80
223
  };
81
224
  };
82
- exports.defaultStreamHandler = defaultStreamHandler;
225
+ export const defaultStreamHandler = ({ response, abortController, }) => {
226
+ // Handle error responses by creating a stream that fails on first iteration
227
+ if (!response.ok) {
228
+ let cachedError = null;
229
+ let errorParsed = false;
230
+ // Parse error asynchronously and cache it
231
+ void response
232
+ .json()
233
+ .then((res) => {
234
+ cachedError = new HttpException(response.status, res.message ?? DEFAULT_ERROR_MESSAGE);
235
+ })
236
+ .catch((e) => {
237
+ cachedError = new HttpException(response.status, e.message ?? DEFAULT_ERROR_MESSAGE, e);
238
+ })
239
+ .finally(() => {
240
+ errorParsed = true;
241
+ });
242
+ const getError = async () => {
243
+ // Wait for error to be parsed
244
+ while (!errorParsed) {
245
+ await new Promise((resolve) => setTimeout(resolve, 0));
246
+ }
247
+ return cachedError ?? new HttpException(response.status, DEFAULT_ERROR_MESSAGE);
248
+ };
249
+ const errorIterator = () => ({
250
+ async next() {
251
+ throw await getError();
252
+ },
253
+ });
254
+ const noop = () => { };
255
+ return {
256
+ status: response.status,
257
+ asPromise: async () => {
258
+ throw await getError();
259
+ },
260
+ abortController,
261
+ [Symbol.asyncIterator]: errorIterator,
262
+ [Symbol.dispose]: noop,
263
+ [Symbol.asyncDispose]: async () => { },
264
+ abortSilently: noop,
265
+ onIterate: () => noop,
266
+ };
267
+ }
268
+ if (!response.body) {
269
+ throw new HttpException(HttpStatus.NULL, 'Stream body is falsy');
270
+ }
271
+ return {
272
+ status: response.status,
273
+ abortController,
274
+ ...readableStreamToAsyncIterable({
275
+ readableStream: response.body,
276
+ abortController,
277
+ }),
278
+ };
279
+ };
@@ -1,3 +1,41 @@
1
- import type { VovkDefaultFetcherOptions, VovkClientFetcher } from './types';
2
- export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at the defaultFetcher";
3
- export declare const fetcher: VovkClientFetcher<VovkDefaultFetcherOptions>;
1
+ import { HttpException } from '../core/HttpException.js';
2
+ import type { VovkFetcherOptions, VovkFetcher } from '../types/client.js';
3
+ import type { VovkHandlerSchema } from '../types/core.js';
4
+ export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at default fetcher";
5
+ export type { VovkFetcher };
6
+ /**
7
+ * Creates a customizable fetcher function for client requests.
8
+ * @see https://vovk.dev/imports
9
+ */
10
+ export declare function createFetcher<T>({ prepareRequestInit, transformResponse, onSuccess, onError, }?: {
11
+ prepareRequestInit?: (init: RequestInit, options: VovkFetcherOptions<T>) => RequestInit | Promise<RequestInit>;
12
+ transformResponse?: (respData: unknown, options: VovkFetcherOptions<T>, info: {
13
+ response: Response;
14
+ init: RequestInit;
15
+ schema: VovkHandlerSchema;
16
+ }) => unknown | Promise<unknown>;
17
+ onSuccess?: (respData: unknown, options: VovkFetcherOptions<T>, info: {
18
+ response: Response;
19
+ init: RequestInit;
20
+ schema: VovkHandlerSchema;
21
+ }) => void | Promise<void>;
22
+ onError?: (error: HttpException, options: VovkFetcherOptions<T>, info: {
23
+ response: Response | null;
24
+ init: RequestInit | null;
25
+ respData: unknown | null;
26
+ schema: VovkHandlerSchema;
27
+ }) => void | Promise<void>;
28
+ }): VovkFetcher<VovkFetcherOptions<T>>;
29
+ /**
30
+ * Default fetcher implementation for client requests.
31
+ * @see https://vovk.dev/imports
32
+ */
33
+ export declare const fetcher: VovkFetcher<{
34
+ apiRoot?: string;
35
+ disableClientValidation?: boolean;
36
+ validateOnClient?: import("../index.js").VovkValidateOnClient<unknown> | Promise<{
37
+ validateOnClient: import("../index.js").VovkValidateOnClient<unknown>;
38
+ }> | undefined;
39
+ interpretAs?: string;
40
+ init?: RequestInit;
41
+ }>;
@@ -1,64 +1,129 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fetcher = exports.DEFAULT_ERROR_MESSAGE = void 0;
4
- const types_1 = require("../types");
5
- const HttpException_1 = require("../HttpException");
6
- exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at the defaultFetcher';
7
- // defaultFetcher uses HttpException class to throw errors of fake HTTP status 0 if client-side error occurs
8
- // For normal HTTP errors, it uses message and status code from the response of VovkErrorResponse type
9
- const fetcher = async ({ httpMethod, getEndpoint, validate, defaultHandler, defaultStreamHandler }, { params, query, body, apiRoot = '/api', ...options }) => {
10
- const endpoint = getEndpoint({ apiRoot, params, query });
11
- if (!options.disableClientValidation) {
1
+ import { HttpStatus } from '../types/enums.js';
2
+ import { HttpException } from '../core/HttpException.js';
3
+ import { fileNameToDisposition } from '../utils/fileNameToDisposition.js';
4
+ export const DEFAULT_ERROR_MESSAGE = 'Unknown error at default fetcher';
5
+ /**
6
+ * Creates a customizable fetcher function for client requests.
7
+ * @see https://vovk.dev/imports
8
+ */
9
+ export function createFetcher({ prepareRequestInit, transformResponse, onSuccess, onError, } = {}) {
10
+ // fetcher uses HttpException class to throw errors of fake HTTP status 0 if client-side error occurs
11
+ // For normal HTTP errors, it uses message and status code from the response of VovkErrorResponse type
12
+ const newFetcher = async ({ httpMethod, getURL, validate, defaultHandler, defaultStreamHandler, schema }, inputOptions) => {
13
+ let response = null;
14
+ let respData = null;
15
+ let requestInit = null;
12
16
  try {
13
- await validate({ body, query, params, endpoint });
17
+ const { meta, apiRoot, disableClientValidation, init, interpretAs } = inputOptions;
18
+ let { body, query, params } = inputOptions;
19
+ const endpoint = getURL({ apiRoot, params, query });
20
+ const unusedParams = Array.from(new URL(endpoint.startsWith('/') ? `http://localhost${endpoint}` : endpoint).pathname.matchAll(/\{([^}]+)\}/g)).map((m) => m[1]);
21
+ if (unusedParams.length) {
22
+ throw new HttpException(HttpStatus.NULL, `Unused params: ${unusedParams.join(', ')} in ${endpoint}`, {
23
+ body,
24
+ query,
25
+ params,
26
+ endpoint,
27
+ });
28
+ }
29
+ if (!disableClientValidation) {
30
+ try {
31
+ ({ body, query, params } = (await validate(inputOptions, { endpoint })) ?? { body, query, params });
32
+ }
33
+ catch (e) {
34
+ // if HttpException is thrown, rethrow it
35
+ if (e instanceof HttpException)
36
+ throw e;
37
+ // otherwise, throw HttpException with status 0
38
+ throw new HttpException(HttpStatus.NULL, e.message ?? DEFAULT_ERROR_MESSAGE, {
39
+ body,
40
+ query,
41
+ params,
42
+ endpoint,
43
+ });
44
+ }
45
+ }
46
+ const resolvedContentType = body instanceof FormData
47
+ ? undefined // browser sets multipart/form-data with boundary automatically
48
+ : body instanceof URLSearchParams
49
+ ? 'application/x-www-form-urlencoded'
50
+ : typeof body === 'string'
51
+ ? 'text/plain'
52
+ : body instanceof Blob
53
+ ? body.type || 'application/octet-stream'
54
+ : body instanceof ArrayBuffer || body instanceof Uint8Array
55
+ ? 'application/octet-stream'
56
+ : 'application/json';
57
+ const resolvedFileName = body instanceof File ? body.name : undefined;
58
+ // Default headers (lowercase keys)
59
+ const defaultHeaders = {
60
+ accept: 'application/jsonl, application/json',
61
+ ...(resolvedContentType ? { 'content-type': resolvedContentType } : {}),
62
+ ...(resolvedFileName ? { 'content-disposition': fileNameToDisposition(resolvedFileName) } : {}),
63
+ ...(meta ? { 'x-meta': JSON.stringify(meta) } : {}),
64
+ };
65
+ // Normalize user headers to lowercase keys via Headers API (handles plain objects, arrays, and Headers instances)
66
+ const userHeaders = init?.headers ? Object.fromEntries(new Headers(init.headers).entries()) : {};
67
+ requestInit = {
68
+ method: httpMethod,
69
+ ...init,
70
+ headers: { ...defaultHeaders, ...userHeaders },
71
+ };
72
+ if (body instanceof FormData || body instanceof URLSearchParams) {
73
+ requestInit.body = body;
74
+ }
75
+ else if (body instanceof Blob) {
76
+ requestInit.body = body;
77
+ }
78
+ else if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
79
+ requestInit.body = body;
80
+ }
81
+ else if (typeof body === 'string') {
82
+ requestInit.body = body;
83
+ }
84
+ else if (body) {
85
+ requestInit.body = JSON.stringify(body);
86
+ }
87
+ const abortController = new AbortController();
88
+ requestInit.signal = abortController.signal;
89
+ requestInit = prepareRequestInit ? await prepareRequestInit(requestInit, inputOptions) : requestInit;
90
+ try {
91
+ response = await fetch(endpoint, requestInit);
92
+ }
93
+ catch (e) {
94
+ // handle network errors
95
+ throw new HttpException(HttpStatus.NULL, (e?.message ?? DEFAULT_ERROR_MESSAGE) + ' ' + endpoint, {
96
+ body,
97
+ query,
98
+ params,
99
+ endpoint,
100
+ });
101
+ }
102
+ const contentType = interpretAs ?? response.headers.get('content-type');
103
+ if (contentType?.startsWith('application/jsonl')) {
104
+ respData = defaultStreamHandler({ response, abortController });
105
+ }
106
+ else if (contentType?.startsWith('application/json')) {
107
+ respData = await defaultHandler({ response, schema });
108
+ }
109
+ else {
110
+ respData = response;
111
+ }
112
+ respData = transformResponse
113
+ ? await transformResponse(respData, inputOptions, { response, init: requestInit, schema })
114
+ : respData;
115
+ await onSuccess?.(respData, inputOptions, { response, init: requestInit, schema });
116
+ return [respData, response];
14
117
  }
15
- catch (e) {
16
- // if HttpException is thrown, rethrow it
17
- if (e instanceof HttpException_1.HttpException)
18
- throw e;
19
- // otherwise, throw HttpException with status 0
20
- throw new HttpException_1.HttpException(types_1.HttpStatus.NULL, e.message ?? exports.DEFAULT_ERROR_MESSAGE, {
21
- body,
22
- query,
23
- params,
24
- endpoint,
25
- });
118
+ catch (error) {
119
+ await onError?.(error, inputOptions, { response, init: requestInit, respData, schema });
120
+ throw error;
26
121
  }
27
- }
28
- const init = {
29
- method: httpMethod,
30
- ...options,
31
- headers: {
32
- ...options.headers,
33
- accept: 'application/jsonl, application/json',
34
- },
35
122
  };
36
- if (body instanceof FormData) {
37
- init.body = body;
38
- }
39
- else if (body) {
40
- init.body = JSON.stringify(body);
41
- }
42
- let response;
43
- try {
44
- response = await fetch(endpoint, init);
45
- }
46
- catch (e) {
47
- // handle network errors
48
- throw new HttpException_1.HttpException(types_1.HttpStatus.NULL, e?.message ?? exports.DEFAULT_ERROR_MESSAGE, {
49
- body,
50
- query,
51
- params,
52
- endpoint,
53
- });
54
- }
55
- const contentType = response.headers.get('content-type');
56
- if (contentType?.startsWith('application/json')) {
57
- return defaultHandler(response);
58
- }
59
- if (contentType?.startsWith('text/plain') && contentType.includes('x-format=jsonlines')) {
60
- return defaultStreamHandler(response);
61
- }
62
- return response;
63
- };
64
- exports.fetcher = fetcher;
123
+ return newFetcher;
124
+ }
125
+ /**
126
+ * Default fetcher implementation for client requests.
127
+ * @see https://vovk.dev/imports
128
+ */
129
+ export const fetcher = createFetcher();
@@ -0,0 +1,15 @@
1
+ import type { VovkStreamAsyncIterable } from '../types/client.js';
2
+ import type { VovkYieldType } from '../types/inference.js';
3
+ import type { KnownAny } from '../types/utils.js';
4
+ type UnionToIntersection<U> = (U extends KnownAny ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
5
+ type PromisifyProperties<T> = {
6
+ [K in keyof T]: Promise<T[K]>;
7
+ };
8
+ type TransformUnionToPromises<T> = PromisifyProperties<UnionToIntersection<T>>;
9
+ /**
10
+ * Implements progressive fetching by returning a proxy object where each property is a promise
11
+ * that resolves when the corresponding value is available from the stream.
12
+ * @see https://vovk.dev/jsonlines
13
+ */
14
+ export declare function progressive<T extends (...args: KnownAny[]) => Promise<VovkStreamAsyncIterable<KnownAny>>>(fn: T, ...args: undefined extends Parameters<T>[0] ? [arg?: Parameters<T>[0]] : [arg: Parameters<T>[0]]): TransformUnionToPromises<VovkYieldType<T>>;
15
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Implements progressive fetching by returning a proxy object where each property is a promise
3
+ * that resolves when the corresponding value is available from the stream.
4
+ * @see https://vovk.dev/jsonlines
5
+ */
6
+ export function progressive(fn, ...args) {
7
+ const [arg] = args;
8
+ const reg = {};
9
+ void fn(arg)
10
+ .then(async (result) => {
11
+ for await (const item of result) {
12
+ for (const [key, value] of Object.entries(item)) {
13
+ if (key in reg) {
14
+ if (!reg[key].isSettled) {
15
+ reg[key].isSettled = true;
16
+ reg[key].resolve(value);
17
+ }
18
+ }
19
+ else {
20
+ const { promise, resolve, reject } = Promise.withResolvers();
21
+ reg[key] = { resolve, reject, promise, isSettled: true };
22
+ reg[key].resolve(value);
23
+ }
24
+ }
25
+ }
26
+ Object.keys(reg).forEach((key) => {
27
+ if (reg[key].isSettled)
28
+ return;
29
+ reg[key].isSettled = true;
30
+ reg[key].reject(new Error(`The connection was closed without sending a value for "${key}"`));
31
+ });
32
+ return result;
33
+ })
34
+ .catch((error) => {
35
+ Object.keys(reg).forEach((key) => {
36
+ if (reg[key].isSettled)
37
+ return;
38
+ reg[key].isSettled = true;
39
+ reg[key].reject(error);
40
+ });
41
+ return error;
42
+ });
43
+ return new Proxy({}, {
44
+ get(_target, prop) {
45
+ if (prop in reg) {
46
+ return reg[prop].promise;
47
+ }
48
+ const { promise, resolve, reject } = Promise.withResolvers();
49
+ reg[prop] = { resolve, reject, promise, isSettled: false };
50
+ return promise;
51
+ },
52
+ ownKeys: () => {
53
+ throw new Error('Getting own keys is not possible as they are dynamically created');
54
+ },
55
+ });
56
+ }
@@ -1,4 +1,4 @@
1
- import type { KnownAny } from '../types';
1
+ import type { KnownAny } from '../types/utils.js';
2
2
  /**
3
3
  * Serialize a nested object (including arrays, arrays of objects, etc.)
4
4
  * into a bracket-based query string.
@@ -10,4 +10,4 @@ import type { KnownAny } from '../types';
10
10
  * @param obj - The input object to be serialized
11
11
  * @returns - A bracket-based query string (without leading "?")
12
12
  */
13
- export default function serializeQuery(obj: Record<string, KnownAny>): string;
13
+ export declare function serializeQuery(obj: Record<string, KnownAny>): string;