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.
- package/README.md +22 -13
- package/bin/index.mjs +10 -0
- package/dist/client/createRPC.d.ts +13 -3
- package/dist/client/createRPC.js +112 -50
- package/dist/client/defaultHandler.d.ts +5 -1
- package/dist/client/defaultHandler.js +12 -9
- package/dist/client/defaultStreamHandler.d.ts +16 -4
- package/dist/client/defaultStreamHandler.js +259 -62
- package/dist/client/fetcher.d.ts +41 -3
- package/dist/client/fetcher.js +125 -60
- package/dist/client/progressive.d.ts +15 -0
- package/dist/client/progressive.js +56 -0
- package/dist/{utils → client}/serializeQuery.d.ts +2 -2
- package/dist/{utils → client}/serializeQuery.js +1 -4
- package/dist/core/HttpException.d.ts +16 -0
- package/dist/core/HttpException.js +26 -0
- package/dist/core/JSONLinesResponder.d.ts +42 -0
- package/dist/core/JSONLinesResponder.js +92 -0
- package/dist/core/controllersToStaticParams.d.ts +13 -0
- package/dist/core/controllersToStaticParams.js +36 -0
- package/dist/core/createDecorator.d.ts +12 -0
- package/dist/{createDecorator.js → core/createDecorator.js} +18 -12
- package/dist/core/decorators.d.ts +59 -0
- package/dist/core/decorators.js +132 -0
- package/dist/core/getSchema.d.ts +21 -0
- package/dist/core/getSchema.js +31 -0
- package/dist/core/initSegment.d.ts +33 -0
- package/dist/core/initSegment.js +35 -0
- package/dist/core/multitenant.d.ts +33 -0
- package/dist/core/multitenant.js +132 -0
- package/dist/core/resolveGeneratorConfigValues.d.ts +19 -0
- package/dist/core/resolveGeneratorConfigValues.js +59 -0
- package/dist/{utils → core}/setHandlerSchema.d.ts +2 -2
- package/dist/{utils → core}/setHandlerSchema.js +1 -4
- package/dist/core/toDownloadResponse.d.ts +11 -0
- package/dist/core/toDownloadResponse.js +25 -0
- package/dist/core/vovkApp.d.ts +36 -0
- package/dist/core/vovkApp.js +316 -0
- package/dist/index.d.ts +25 -59
- package/dist/index.js +23 -23
- package/dist/internal.d.ts +17 -0
- package/dist/internal.js +10 -0
- package/dist/openapi/error.d.ts +2 -0
- package/dist/openapi/error.js +97 -0
- package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.d.ts +3 -0
- package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.js +65 -0
- package/dist/openapi/openAPIToVovkSchema/index.d.ts +5 -0
- package/dist/openapi/openAPIToVovkSchema/index.js +153 -0
- package/dist/openapi/openAPIToVovkSchema/inlineRefs.d.ts +9 -0
- package/dist/openapi/openAPIToVovkSchema/inlineRefs.js +99 -0
- package/dist/openapi/operation.d.ts +10 -0
- package/dist/openapi/operation.js +19 -0
- package/dist/openapi/tool.d.ts +2 -0
- package/dist/openapi/tool.js +12 -0
- package/dist/openapi/vovkSchemaToOpenAPI.d.ts +21 -0
- package/dist/openapi/vovkSchemaToOpenAPI.js +250 -0
- package/dist/req/bufferBody.d.ts +1 -0
- package/dist/req/bufferBody.js +30 -0
- package/dist/req/parseBody.d.ts +4 -0
- package/dist/req/parseBody.js +49 -0
- package/dist/req/parseForm.d.ts +1 -0
- package/dist/req/parseForm.js +24 -0
- package/dist/{utils → req}/parseQuery.d.ts +1 -2
- package/dist/{utils → req}/parseQuery.js +2 -5
- package/dist/req/reqMeta.d.ts +2 -0
- package/dist/{utils → req}/reqMeta.js +1 -4
- package/dist/req/reqQuery.d.ts +2 -0
- package/dist/req/reqQuery.js +4 -0
- package/dist/req/validateContentType.d.ts +1 -0
- package/dist/req/validateContentType.js +32 -0
- package/dist/samples/createCodeSamples.d.ts +20 -0
- package/dist/samples/createCodeSamples.js +293 -0
- package/dist/samples/objectToCode.d.ts +8 -0
- package/dist/samples/objectToCode.js +38 -0
- package/dist/samples/schemaToCode.d.ts +11 -0
- package/dist/samples/schemaToCode.js +264 -0
- package/dist/samples/schemaToObject.d.ts +2 -0
- package/dist/samples/schemaToObject.js +164 -0
- package/dist/samples/schemaToTsType.d.ts +2 -0
- package/dist/samples/schemaToTsType.js +114 -0
- package/dist/tools/ToModelOutput.d.ts +8 -0
- package/dist/tools/ToModelOutput.js +10 -0
- package/dist/tools/createTool.d.ts +126 -0
- package/dist/tools/createTool.js +6 -0
- package/dist/tools/createToolFactory.d.ts +135 -0
- package/dist/tools/createToolFactory.js +61 -0
- package/dist/tools/deriveTools.d.ts +46 -0
- package/dist/tools/deriveTools.js +134 -0
- package/dist/tools/toModelOutputDefault.d.ts +7 -0
- package/dist/tools/toModelOutputDefault.js +7 -0
- package/dist/tools/toModelOutputMCP.d.ts +30 -0
- package/dist/tools/toModelOutputMCP.js +54 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/client.d.ts +140 -0
- package/dist/types/client.js +1 -0
- package/dist/types/config.d.ts +151 -0
- package/dist/types/config.js +1 -0
- package/dist/types/core.d.ts +115 -0
- package/dist/types/core.js +1 -0
- package/dist/types/enums.d.ts +75 -0
- package/dist/{types.js → types/enums.js} +21 -9
- package/dist/types/inference.d.ts +117 -0
- package/dist/types/inference.js +1 -0
- package/dist/types/json-schema.d.ts +51 -0
- package/dist/types/json-schema.js +1 -0
- package/dist/types/operation.d.ts +5 -0
- package/dist/types/operation.js +1 -0
- package/dist/types/package.d.ts +544 -0
- package/dist/types/package.js +5 -0
- package/dist/types/request.d.ts +48 -0
- package/dist/types/request.js +1 -0
- package/dist/types/standard-schema.d.ts +117 -0
- package/dist/types/standard-schema.js +6 -0
- package/dist/types/tools.d.ts +43 -0
- package/dist/types/tools.js +1 -0
- package/dist/types/utils.d.ts +9 -0
- package/dist/types/utils.js +1 -0
- package/dist/types/validation.d.ts +48 -0
- package/dist/types/validation.js +1 -0
- package/dist/utils/camelCase.d.ts +6 -0
- package/dist/utils/camelCase.js +34 -0
- package/dist/utils/deepExtend.d.ts +53 -0
- package/dist/utils/deepExtend.js +128 -0
- package/dist/utils/fileNameToDisposition.d.ts +1 -0
- package/dist/utils/fileNameToDisposition.js +3 -0
- package/dist/utils/shim.d.ts +1 -0
- package/dist/utils/shim.js +1 -1
- package/dist/utils/toKebabCase.d.ts +1 -0
- package/dist/utils/toKebabCase.js +5 -0
- package/dist/utils/trimPath.d.ts +1 -0
- package/dist/utils/trimPath.js +1 -0
- package/dist/utils/upperFirst.d.ts +1 -0
- package/dist/utils/upperFirst.js +3 -0
- package/dist/validation/createStandardValidation.d.ts +268 -0
- package/dist/validation/createStandardValidation.js +45 -0
- package/dist/validation/createValidateOnClient.d.ts +14 -0
- package/dist/validation/createValidateOnClient.js +23 -0
- package/dist/validation/procedure.d.ts +261 -0
- package/dist/validation/procedure.js +8 -0
- package/dist/validation/withValidationLibrary.d.ts +119 -0
- package/dist/validation/withValidationLibrary.js +174 -0
- package/package.json +44 -10
- package/dist/HttpException.d.ts +0 -7
- package/dist/HttpException.js +0 -15
- package/dist/StreamJSONResponse.d.ts +0 -14
- package/dist/StreamJSONResponse.js +0 -57
- package/dist/VovkApp.d.ts +0 -29
- package/dist/VovkApp.js +0 -188
- package/dist/client/index.d.ts +0 -3
- package/dist/client/index.js +0 -7
- package/dist/client/types.d.ts +0 -104
- package/dist/client/types.js +0 -2
- package/dist/createDecorator.d.ts +0 -6
- package/dist/createVovkApp.d.ts +0 -62
- package/dist/createVovkApp.js +0 -118
- package/dist/types.d.ts +0 -220
- package/dist/utils/generateStaticAPI.d.ts +0 -4
- package/dist/utils/generateStaticAPI.js +0 -18
- package/dist/utils/getSchema.d.ts +0 -20
- package/dist/utils/getSchema.js +0 -33
- package/dist/utils/reqForm.d.ts +0 -2
- package/dist/utils/reqForm.js +0 -13
- package/dist/utils/reqMeta.d.ts +0 -2
- package/dist/utils/reqQuery.d.ts +0 -2
- package/dist/utils/reqQuery.js +0 -10
- package/dist/utils/withValidation.d.ts +0 -20
- package/dist/utils/withValidation.js +0 -72
|
@@ -1,82 +1,279 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
137
|
+
finally {
|
|
138
|
+
streamExhausted = true;
|
|
139
|
+
notifyWaiters();
|
|
16
140
|
}
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
144
|
+
if (!primaryStarted) {
|
|
145
|
+
primaryStarted = true;
|
|
146
|
+
void runPrimaryReader();
|
|
147
|
+
}
|
|
148
|
+
let index = 0;
|
|
27
149
|
while (true) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
throw err;
|
|
158
|
+
// Yield from cache if available
|
|
159
|
+
if (index < cachedItems.length) {
|
|
160
|
+
yield cachedItems[index++];
|
|
161
|
+
continue;
|
|
38
162
|
}
|
|
39
|
-
|
|
163
|
+
// Stream finished
|
|
164
|
+
if (streamExhausted) {
|
|
40
165
|
return;
|
|
41
166
|
}
|
|
42
|
-
//
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
53
|
-
|
|
174
|
+
if (abortController?.signal.aborted && isAbortedWithoutError) {
|
|
175
|
+
resolve({ value: undefined, done: true });
|
|
176
|
+
return;
|
|
54
177
|
}
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
211
|
+
asPromise,
|
|
212
|
+
// abortController,
|
|
73
213
|
[Symbol.asyncIterator]: asyncIterator,
|
|
74
|
-
[Symbol.dispose]: () =>
|
|
75
|
-
[Symbol.asyncDispose]: () =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/dist/client/fetcher.d.ts
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
}>;
|
package/dist/client/fetcher.js
CHANGED
|
@@ -1,64 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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 (
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
13
|
+
export declare function serializeQuery(obj: Record<string, KnownAny>): string;
|