vovk 3.5.1 → 3.7.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 (113) hide show
  1. package/dist/client/create-rpc.d.ts +13 -0
  2. package/dist/client/create-rpc.js +147 -0
  3. package/dist/client/default-handler.d.ts +6 -0
  4. package/dist/client/default-handler.js +25 -0
  5. package/dist/client/default-stream-handler.d.ts +16 -0
  6. package/dist/client/default-stream-handler.js +282 -0
  7. package/dist/client/fetcher.d.ts +1 -1
  8. package/dist/client/fetcher.js +2 -2
  9. package/dist/client/serialize-query.d.ts +13 -0
  10. package/dist/client/serialize-query.js +62 -0
  11. package/dist/core/apply-decorator-adapter.d.ts +7 -0
  12. package/dist/core/apply-decorator-adapter.js +50 -0
  13. package/dist/core/controllers-to-static-params.d.ts +13 -0
  14. package/dist/core/controllers-to-static-params.js +32 -0
  15. package/dist/core/create-decorator.d.ts +12 -0
  16. package/dist/core/create-decorator.js +52 -0
  17. package/dist/core/decorators.js +4 -4
  18. package/dist/core/get-schema.d.ts +21 -0
  19. package/dist/core/get-schema.js +31 -0
  20. package/dist/core/http-exception.d.ts +16 -0
  21. package/dist/core/http-exception.js +26 -0
  22. package/dist/core/init-segment.d.ts +33 -0
  23. package/dist/core/init-segment.js +62 -0
  24. package/dist/core/json-lines-responder.d.ts +42 -0
  25. package/dist/core/json-lines-responder.js +94 -0
  26. package/dist/core/resolve-generator-config-values.d.ts +19 -0
  27. package/dist/core/resolve-generator-config-values.js +59 -0
  28. package/dist/core/set-handler-schema.d.ts +4 -0
  29. package/dist/core/set-handler-schema.js +12 -0
  30. package/dist/core/to-download-response.d.ts +11 -0
  31. package/dist/core/to-download-response.js +25 -0
  32. package/dist/core/vovk-app.d.ts +36 -0
  33. package/dist/core/vovk-app.js +318 -0
  34. package/dist/index.d.ts +10 -10
  35. package/dist/index.js +10 -10
  36. package/dist/internal.d.ts +10 -10
  37. package/dist/internal.js +9 -9
  38. package/dist/openapi/error.js +1 -1
  39. package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.d.ts +23 -0
  40. package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.js +90 -0
  41. package/dist/openapi/openapi-to-vovk-schema/index.d.ts +5 -0
  42. package/dist/openapi/openapi-to-vovk-schema/index.js +179 -0
  43. package/dist/openapi/openapi-to-vovk-schema/inline-refs.d.ts +9 -0
  44. package/dist/openapi/openapi-to-vovk-schema/inline-refs.js +99 -0
  45. package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.d.ts +7 -0
  46. package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.js +51 -0
  47. package/dist/openapi/operation.js +1 -1
  48. package/dist/openapi/tool.js +1 -1
  49. package/dist/openapi/vovk-schema-to-openapi.d.ts +21 -0
  50. package/dist/openapi/vovk-schema-to-openapi.js +250 -0
  51. package/dist/req/buffer-body.d.ts +1 -0
  52. package/dist/req/buffer-body.js +30 -0
  53. package/dist/req/parse-body.d.ts +4 -0
  54. package/dist/req/parse-body.js +49 -0
  55. package/dist/req/parse-form.d.ts +1 -0
  56. package/dist/req/parse-form.js +24 -0
  57. package/dist/req/parse-query.d.ts +24 -0
  58. package/dist/req/parse-query.js +156 -0
  59. package/dist/req/req-meta.d.ts +2 -0
  60. package/dist/req/req-meta.js +10 -0
  61. package/dist/req/req-query.d.ts +2 -0
  62. package/dist/req/req-query.js +4 -0
  63. package/dist/req/validate-content-type.d.ts +1 -0
  64. package/dist/req/validate-content-type.js +32 -0
  65. package/dist/samples/create-code-samples.d.ts +20 -0
  66. package/dist/samples/create-code-samples.js +293 -0
  67. package/dist/samples/object-to-code.d.ts +8 -0
  68. package/dist/samples/object-to-code.js +38 -0
  69. package/dist/samples/schema-to-code.d.ts +11 -0
  70. package/dist/samples/schema-to-code.js +264 -0
  71. package/dist/samples/schema-to-object.d.ts +2 -0
  72. package/dist/samples/schema-to-object.js +164 -0
  73. package/dist/samples/schema-to-ts-type.d.ts +2 -0
  74. package/dist/samples/schema-to-ts-type.js +114 -0
  75. package/dist/tools/create-tool-factory.d.ts +135 -0
  76. package/dist/tools/create-tool-factory.js +62 -0
  77. package/dist/tools/create-tool.d.ts +126 -0
  78. package/dist/tools/create-tool.js +6 -0
  79. package/dist/tools/derive-tools.d.ts +46 -0
  80. package/dist/tools/derive-tools.js +131 -0
  81. package/dist/tools/to-model-output-default.d.ts +7 -0
  82. package/dist/tools/to-model-output-default.js +7 -0
  83. package/dist/tools/to-model-output-mcp.d.ts +30 -0
  84. package/dist/tools/to-model-output-mcp.js +54 -0
  85. package/dist/tools/to-model-output.d.ts +8 -0
  86. package/dist/tools/to-model-output.js +10 -0
  87. package/dist/types/client.d.ts +3 -3
  88. package/dist/types/core.d.ts +1 -1
  89. package/dist/types/inference.d.ts +1 -1
  90. package/dist/types/validation.d.ts +1 -1
  91. package/dist/utils/camel-case.d.ts +6 -0
  92. package/dist/utils/camel-case.js +34 -0
  93. package/dist/utils/deep-extend.d.ts +54 -0
  94. package/dist/utils/deep-extend.js +127 -0
  95. package/dist/utils/file-name-to-disposition.d.ts +1 -0
  96. package/dist/utils/file-name-to-disposition.js +3 -0
  97. package/dist/utils/to-kebab-case.d.ts +1 -0
  98. package/dist/utils/to-kebab-case.js +5 -0
  99. package/dist/utils/trim-path.d.ts +1 -0
  100. package/dist/utils/trim-path.js +1 -0
  101. package/dist/utils/upper-first.d.ts +1 -0
  102. package/dist/utils/upper-first.js +3 -0
  103. package/dist/validation/create-standard-validation.d.ts +268 -0
  104. package/dist/validation/create-standard-validation.js +45 -0
  105. package/dist/validation/create-validate-on-client.d.ts +14 -0
  106. package/dist/validation/create-validate-on-client.js +23 -0
  107. package/dist/validation/procedure.d.ts +24 -24
  108. package/dist/validation/procedure.js +1 -1
  109. package/dist/validation/validation-schemas-object-to-single-validation-schema.d.ts +17 -0
  110. package/dist/validation/validation-schemas-object-to-single-validation-schema.js +92 -0
  111. package/dist/validation/with-validation-library.d.ts +119 -0
  112. package/dist/validation/with-validation-library.js +184 -0
  113. package/package.json +13 -5
@@ -0,0 +1,13 @@
1
+ import type { VovkHandlerSchema } from '../internal.js';
2
+ import type { VovkRequest } from '../types/request.js';
3
+ import type { VovkRPCModule, VovkFetcher, VovkFetcherOptions } from '../types/client.js';
4
+ import type { CombinedSpec } from '../types/validation.js';
5
+ import type { KnownAny } from '../types/utils.js';
6
+ export type { VovkHandlerSchema, VovkRequest, CombinedSpec };
7
+ /**
8
+ * Creates a client-side RPC module for interacting with server-side controllers.
9
+ * @see https://vovk.dev/typescript
10
+ */
11
+ export declare const createRPC: <T, OPTS extends Record<string, KnownAny> = Record<string, never>>(givenSchema: unknown, segmentName: string, rpcModuleName: string, givenFetcher?: VovkFetcher<OPTS> | Promise<{
12
+ fetcher: VovkFetcher<OPTS>;
13
+ }>, options?: VovkFetcherOptions<OPTS>) => VovkRPCModule<T, OPTS>;
@@ -0,0 +1,147 @@
1
+ import { fetcher as defaultFetcher } from './fetcher.js';
2
+ import { defaultHandler } from './default-handler.js';
3
+ import { defaultStreamHandler } from './default-stream-handler.js';
4
+ import { serializeQuery } from './serialize-query.js';
5
+ import { deepExtend } from '../utils/deep-extend.js';
6
+ const trimPath = (path) => path.trim().replace(/^\/|\/$/g, '');
7
+ const getHandlerPath = (endpoint, params, query) => {
8
+ let result = endpoint;
9
+ const queryStr = query ? serializeQuery(query) : null;
10
+ for (const [key, value] of Object.entries(params ?? {})) {
11
+ result = result.replace(`{${key}}`, value);
12
+ }
13
+ return `${result}${queryStr ? `?${queryStr}` : ''}`;
14
+ };
15
+ /**
16
+ * Creates a client-side RPC module for interacting with server-side controllers.
17
+ * @see https://vovk.dev/typescript
18
+ */
19
+ export const createRPC = (givenSchema, segmentName, rpcModuleName, givenFetcher, options) => {
20
+ const schema = givenSchema; // fixes incompatibilities with JSON module
21
+ // fetcher ??= defaultFetcher as NonNullable<typeof fetcher>;
22
+ const segmentNamePath = options?.segmentNameOverride ?? segmentName;
23
+ const segmentSchema = schema.segments[segmentName];
24
+ if (!segmentSchema)
25
+ throw new Error(`Unable to create RPC module. Segment schema is missing for segment "${segmentName}".`);
26
+ let controllerSchema = schema.segments[segmentName]?.controllers[rpcModuleName];
27
+ const client = {};
28
+ if (!controllerSchema) {
29
+ // eslint-disable-next-line no-console
30
+ console.warn(`🐺 Unable to create RPC module. Controller schema is missing for module "${rpcModuleName}" from segment "${segmentName}". Assuming that schema is not ready yet and a segment is importing an uncompiled RPC module.`);
31
+ controllerSchema = {
32
+ rpcModuleName,
33
+ prefix: '',
34
+ handlers: {},
35
+ };
36
+ }
37
+ const controllerPrefix = trimPath(controllerSchema.prefix ?? '');
38
+ const forceApiRoot = segmentSchema.forceApiRoot;
39
+ const configRootEntry = schema.meta?.config?.rootEntry;
40
+ const originalApiRoot = forceApiRoot ?? options?.apiRoot ?? (configRootEntry ? `/${configRootEntry}` : '/api');
41
+ for (const [staticMethodName, handlerSchema] of Object.entries(controllerSchema.handlers ?? {})) {
42
+ const { path, httpMethod, validation } = handlerSchema;
43
+ const getURL = ({ apiRoot, params, query } = {}) => {
44
+ apiRoot = apiRoot ?? originalApiRoot;
45
+ const endpoint = [
46
+ apiRoot.startsWith('http://') || apiRoot.startsWith('https://') || apiRoot.startsWith('/') ? '' : '/',
47
+ apiRoot,
48
+ forceApiRoot ? '' : segmentNamePath,
49
+ getHandlerPath([controllerPrefix, path].filter(Boolean).join('/'), params, query),
50
+ ]
51
+ .filter(Boolean)
52
+ .join('/')
53
+ .replace(/([^:])\/+/g, '$1/'); // replace // by / but not for protocols (http://, https://)
54
+ return endpoint;
55
+ };
56
+ const handler = (async (input = {}) => {
57
+ const optionsResolvedValidateOnClient = options?.validateOnClient instanceof Promise
58
+ ? (await options?.validateOnClient)?.validateOnClient
59
+ : options?.validateOnClient;
60
+ const fetcher = givenFetcher instanceof Promise
61
+ ? (await givenFetcher).fetcher
62
+ : (givenFetcher ?? defaultFetcher);
63
+ const validate = async (validationInput, { endpoint, }) => {
64
+ const validateOnClient = input.validateOnClient ?? optionsResolvedValidateOnClient;
65
+ if (validateOnClient && validation) {
66
+ if (typeof validateOnClient !== 'function') {
67
+ throw new Error('validateOnClient must be a function');
68
+ }
69
+ return ((await validateOnClient({ ...validationInput }, validation, { fullSchema: schema, endpoint })) ??
70
+ validationInput);
71
+ }
72
+ return validationInput;
73
+ };
74
+ const internalOptions = {
75
+ name: staticMethodName,
76
+ httpMethod: httpMethod,
77
+ getURL,
78
+ validate,
79
+ defaultHandler,
80
+ defaultStreamHandler,
81
+ schema: handlerSchema,
82
+ };
83
+ let processedBody = input.body;
84
+ if ((validation?.body?.['x-contentType']?.includes('multipart/form-data') ||
85
+ validation?.body?.['x-contentType']?.includes('application/x-www-form-urlencoded')) &&
86
+ input.body &&
87
+ !(input.body instanceof FormData || input.body instanceof URLSearchParams || input.body instanceof Blob)) {
88
+ processedBody = new FormData();
89
+ for (const [key, value] of Object.entries(input.body)) {
90
+ if (Array.isArray(value)) {
91
+ value.forEach((item) => {
92
+ processedBody.append(key, item);
93
+ });
94
+ }
95
+ else {
96
+ processedBody.append(key, value);
97
+ }
98
+ }
99
+ }
100
+ else {
101
+ processedBody = input.body;
102
+ }
103
+ const internalInput = {
104
+ ...deepExtend({}, options, {
105
+ validateOnClient: optionsResolvedValidateOnClient,
106
+ }, input),
107
+ body: processedBody ?? null,
108
+ query: input.query ?? {},
109
+ params: input.params ?? {},
110
+ };
111
+ if (!fetcher)
112
+ throw new Error('Fetcher is not provided');
113
+ const [respData, resp] = await fetcher(internalOptions, internalInput);
114
+ return input.transform ? input.transform(respData, resp) : respData;
115
+ });
116
+ // TODO use Object.freeze, Object.seal or Object.defineProperty to avoid mutation
117
+ handler.schema = handlerSchema;
118
+ handler.controllerSchema = controllerSchema;
119
+ handler.segmentSchema = segmentSchema;
120
+ handler.fullSchema = schema;
121
+ handler.isRPC = true;
122
+ handler.apiRoot = originalApiRoot;
123
+ handler.getURL = getURL;
124
+ handler.queryKey = (key) => [
125
+ handler.segmentSchema.segmentName,
126
+ handler.controllerSchema.prefix ?? '',
127
+ handler.controllerSchema.rpcModuleName,
128
+ handler.schema.path,
129
+ handler.schema.httpMethod,
130
+ ...(key ?? []),
131
+ ];
132
+ // @ts-expect-error TODO
133
+ client[staticMethodName] = handler;
134
+ }
135
+ Object.defineProperty(client, 'withDefaults', {
136
+ value: (newOptions) => {
137
+ return createRPC(schema, segmentName, rpcModuleName, givenFetcher, {
138
+ ...options,
139
+ ...newOptions,
140
+ });
141
+ },
142
+ enumerable: false,
143
+ writable: false,
144
+ configurable: false,
145
+ });
146
+ return client;
147
+ };
@@ -0,0 +1,6 @@
1
+ import type { VovkHandlerSchema } from '../types/core.js';
2
+ export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultHandler";
3
+ export declare const defaultHandler: ({ response, schema }: {
4
+ response: Response;
5
+ schema: VovkHandlerSchema;
6
+ }) => Promise<unknown>;
@@ -0,0 +1,25 @@
1
+ import { HttpException } from '../core/http-exception.js';
2
+ export const DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultHandler';
3
+ // Helper function to get a value from an object using dot notation path
4
+ const getNestedValue = (obj, path) => {
5
+ return path.split('.').reduce((o, key) => (o && typeof o === 'object' ? o[key] : undefined), obj);
6
+ };
7
+ export const defaultHandler = async ({ response, schema }) => {
8
+ let result;
9
+ try {
10
+ result = await response.json();
11
+ }
12
+ catch (e) {
13
+ // handle parsing errors
14
+ throw new HttpException(response.status, e?.message ?? DEFAULT_ERROR_MESSAGE);
15
+ }
16
+ if (!response.ok) {
17
+ const errorKey = schema.operationObject && 'x-errorMessageKey' in schema.operationObject
18
+ ? schema.operationObject['x-errorMessageKey']
19
+ : 'message';
20
+ // handle server errors
21
+ const errorResponse = result;
22
+ throw new HttpException(response.status, getNestedValue(errorResponse, errorKey) ?? DEFAULT_ERROR_MESSAGE, errorResponse?.cause ?? JSON.stringify(result));
23
+ }
24
+ return result;
25
+ };
@@ -0,0 +1,16 @@
1
+ import type { VovkStreamAsyncIterable } from '../types/client.js';
2
+ import '../utils/shim.js';
3
+ export declare const DEFAULT_ERROR_MESSAGE = "An unknown error at the default stream handler";
4
+ /**
5
+ * Converts a ReadableStream of JSON Lines into a VovkStreamAsyncIterable.
6
+ * This is the core streaming logic extracted for reuse outside of HTTP contexts.
7
+ * @see https://vovk.dev/jsonlines
8
+ */
9
+ export declare const readableStreamToAsyncIterable: <T = unknown>({ readableStream, abortController, }: {
10
+ readableStream: ReadableStream<Uint8Array | string>;
11
+ abortController?: AbortController;
12
+ }) => Omit<VovkStreamAsyncIterable<T>, "abortController" | "status">;
13
+ export declare const defaultStreamHandler: ({ response, abortController, }: {
14
+ response: Response;
15
+ abortController: AbortController;
16
+ }) => VovkStreamAsyncIterable<unknown>;
@@ -0,0 +1,282 @@
1
+ import { HttpStatus } from '../types/enums.js';
2
+ import { HttpException } from '../core/http-exception.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
+ };
88
+ try {
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 (true) {
117
+ newlineIdx = buffer.indexOf('\n');
118
+ if (newlineIdx === -1)
119
+ break;
120
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
121
+ break;
122
+ }
123
+ const line = buffer.slice(0, newlineIdx);
124
+ buffer = buffer.slice(newlineIdx + 1);
125
+ if (!line)
126
+ continue;
127
+ if (processLine(line))
128
+ return;
129
+ }
130
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
131
+ break;
132
+ }
133
+ }
134
+ // Process any remaining data in the buffer (last line without trailing newline)
135
+ const remaining = buffer.trim();
136
+ if (remaining) {
137
+ processLine(remaining);
138
+ }
139
+ }
140
+ finally {
141
+ streamExhausted = true;
142
+ notifyWaiters();
143
+ }
144
+ };
145
+ // --- Async iterator ---
146
+ async function* asyncIterator() {
147
+ if (!primaryStarted) {
148
+ primaryStarted = true;
149
+ void runPrimaryReader();
150
+ }
151
+ let index = 0;
152
+ while (true) {
153
+ // Check error first
154
+ if (streamError && index >= errorIndex) {
155
+ throw streamError;
156
+ }
157
+ // Clean exit on abort without error
158
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
159
+ return;
160
+ }
161
+ // Yield from cache if available
162
+ if (index < cachedItems.length) {
163
+ yield cachedItems[index++];
164
+ continue;
165
+ }
166
+ // Stream finished
167
+ if (streamExhausted) {
168
+ return;
169
+ }
170
+ // Wait for next item or completion
171
+ const result = await new Promise((resolve, reject) => {
172
+ // Re-check state inside promise to handle race conditions
173
+ if (streamError && index >= errorIndex) {
174
+ reject(streamError);
175
+ return;
176
+ }
177
+ if (abortController?.signal.aborted && isAbortedWithoutError) {
178
+ resolve({ value: undefined, done: true });
179
+ return;
180
+ }
181
+ if (index < cachedItems.length) {
182
+ resolve({ value: cachedItems[index], done: false });
183
+ return;
184
+ }
185
+ if (streamExhausted) {
186
+ resolve({ value: undefined, done: true });
187
+ return;
188
+ }
189
+ waiters.push({ index, resolve, reject });
190
+ });
191
+ if (result.done) {
192
+ return;
193
+ }
194
+ index++;
195
+ yield result.value;
196
+ }
197
+ }
198
+ // --- Public API ---
199
+ const asPromise = async () => {
200
+ const items = [];
201
+ for await (const item of asyncIterator()) {
202
+ items.push(item);
203
+ }
204
+ return items;
205
+ };
206
+ const abortSilently = (reason) => {
207
+ isAbortedWithoutError = true;
208
+ streamExhausted = true;
209
+ notifyWaiters();
210
+ abortController?.abort(reason);
211
+ reader.cancel().catch(() => { });
212
+ };
213
+ return {
214
+ asPromise,
215
+ // abortController,
216
+ [Symbol.asyncIterator]: asyncIterator,
217
+ [Symbol.dispose]: () => disposeStream('Stream disposed'),
218
+ [Symbol.asyncDispose]: async () => disposeStream('Stream async disposed'),
219
+ abortSilently,
220
+ onIterate: (cb) => {
221
+ if (abortController?.signal.aborted)
222
+ return () => { };
223
+ subscribers.add(cb);
224
+ return () => subscribers.delete(cb);
225
+ },
226
+ };
227
+ };
228
+ export const defaultStreamHandler = ({ response, abortController, }) => {
229
+ // Handle error responses by creating a stream that fails on first iteration
230
+ if (!response.ok) {
231
+ let cachedError = null;
232
+ let errorParsed = false;
233
+ // Parse error asynchronously and cache it
234
+ void response
235
+ .json()
236
+ .then((res) => {
237
+ cachedError = new HttpException(response.status, res.message ?? DEFAULT_ERROR_MESSAGE);
238
+ })
239
+ .catch((e) => {
240
+ cachedError = new HttpException(response.status, e.message ?? DEFAULT_ERROR_MESSAGE, e);
241
+ })
242
+ .finally(() => {
243
+ errorParsed = true;
244
+ });
245
+ const getError = async () => {
246
+ // Wait for error to be parsed
247
+ while (!errorParsed) {
248
+ await new Promise((resolve) => setTimeout(resolve, 0));
249
+ }
250
+ return cachedError ?? new HttpException(response.status, DEFAULT_ERROR_MESSAGE);
251
+ };
252
+ const errorIterator = () => ({
253
+ async next() {
254
+ throw await getError();
255
+ },
256
+ });
257
+ const noop = () => { };
258
+ return {
259
+ status: response.status,
260
+ asPromise: async () => {
261
+ throw await getError();
262
+ },
263
+ abortController,
264
+ [Symbol.asyncIterator]: errorIterator,
265
+ [Symbol.dispose]: noop,
266
+ [Symbol.asyncDispose]: async () => { },
267
+ abortSilently: noop,
268
+ onIterate: () => noop,
269
+ };
270
+ }
271
+ if (!response.body) {
272
+ throw new HttpException(HttpStatus.NULL, 'Stream body is falsy');
273
+ }
274
+ return {
275
+ status: response.status,
276
+ abortController,
277
+ ...readableStreamToAsyncIterable({
278
+ readableStream: response.body,
279
+ abortController,
280
+ }),
281
+ };
282
+ };
@@ -1,4 +1,4 @@
1
- import { HttpException } from '../core/HttpException.js';
1
+ import { HttpException } from '../core/http-exception.js';
2
2
  import type { VovkFetcherOptions, VovkFetcher } from '../types/client.js';
3
3
  import type { VovkHandlerSchema } from '../types/core.js';
4
4
  export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at default fetcher";
@@ -1,6 +1,6 @@
1
1
  import { HttpStatus } from '../types/enums.js';
2
- import { HttpException } from '../core/HttpException.js';
3
- import { fileNameToDisposition } from '../utils/fileNameToDisposition.js';
2
+ import { HttpException } from '../core/http-exception.js';
3
+ import { fileNameToDisposition } from '../utils/file-name-to-disposition.js';
4
4
  export const DEFAULT_ERROR_MESSAGE = 'Unknown error at default fetcher';
5
5
  /**
6
6
  * Creates a customizable fetcher function for client requests.
@@ -0,0 +1,13 @@
1
+ import type { KnownAny } from '../types/utils.js';
2
+ /**
3
+ * Serialize a nested object (including arrays, arrays of objects, etc.)
4
+ * into a bracket-based query string.
5
+ *
6
+ * @example
7
+ * serializeQuery({ x: 'xx', y: [1, 2], z: { f: 'x' } })
8
+ * => "x=xx&y[0]=1&y[1]=2&z[f]=x"
9
+ *
10
+ * @param obj - The input object to be serialized
11
+ * @returns - A bracket-based query string (without leading "?")
12
+ */
13
+ export declare function serializeQuery(obj: Record<string, KnownAny>): string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Recursively build query parameters from an object.
3
+ *
4
+ * @param key - The query key so far (e.g. 'user', 'user[0]', 'user[0][name]')
5
+ * @param value - The current value to serialize
6
+ * @returns - An array of `key=value` strings
7
+ */
8
+ function buildParams(key, value) {
9
+ if (value === null || value === undefined) {
10
+ return []; // skip null/undefined values entirely
11
+ }
12
+ // If value is an object or array, we need to recurse
13
+ if (typeof value === 'object') {
14
+ // Array case
15
+ if (Array.isArray(value)) {
16
+ /**
17
+ * We use index-based bracket notation here:
18
+ * e.g. for value = ['aa', 'bb'] and key = 'foo'
19
+ * => "foo[0]=aa&foo[1]=bb"
20
+ *
21
+ * If you prefer "foo[]=aa&foo[]=bb" style, replace:
22
+ * `${key}[${i}]`
23
+ * with:
24
+ * `${key}[]`
25
+ */
26
+ return value.flatMap((v, i) => {
27
+ const newKey = `${key}[${i}]`;
28
+ return buildParams(newKey, v);
29
+ });
30
+ }
31
+ // Plain object case
32
+ return Object.keys(value).flatMap((k) => {
33
+ const newKey = `${key}[${k}]`;
34
+ return buildParams(newKey, value[k]);
35
+ });
36
+ }
37
+ return [`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`];
38
+ }
39
+ /**
40
+ * Serialize a nested object (including arrays, arrays of objects, etc.)
41
+ * into a bracket-based query string.
42
+ *
43
+ * @example
44
+ * serializeQuery({ x: 'xx', y: [1, 2], z: { f: 'x' } })
45
+ * => "x=xx&y[0]=1&y[1]=2&z[f]=x"
46
+ *
47
+ * @param obj - The input object to be serialized
48
+ * @returns - A bracket-based query string (without leading "?")
49
+ */
50
+ export function serializeQuery(obj) {
51
+ if (!obj || typeof obj !== 'object')
52
+ return '';
53
+ // Collect query segments
54
+ const segments = [];
55
+ for (const key in obj) {
56
+ if (Object.hasOwn(obj, key)) {
57
+ const value = obj[key];
58
+ segments.push(...buildParams(key, value));
59
+ }
60
+ }
61
+ return segments.join('&');
62
+ }
@@ -0,0 +1,7 @@
1
+ import type { VovkController } from '../types/core.js';
2
+ import type { KnownAny } from '../types/utils.js';
3
+ /**
4
+ * Adapts a decorator callback to work with experimental, 2018-09, and TC39 Stage 3 decorator formats.
5
+ * The callback receives (controller, propertyKey) when the class and field/method value are available.
6
+ */
7
+ export declare function applyDecoratorAdapter(arg1: unknown, arg2: unknown, callback: (controller: VovkController, propertyKey: string) => void): KnownAny;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Adapts a decorator callback to work with experimental, 2018-09, and TC39 Stage 3 decorator formats.
3
+ * The callback receives (controller, propertyKey) when the class and field/method value are available.
4
+ */
5
+ export function applyDecoratorAdapter(arg1, arg2, callback) {
6
+ // Experimental decorators: (target, propertyKey: string)
7
+ if (typeof arg2 === 'string') {
8
+ callback(arg1, arg2);
9
+ return;
10
+ }
11
+ // TC39 Stage 3: (value, context: { kind, name, addInitializer })
12
+ if (typeof arg2 === 'object' && arg2 !== null && 'name' in arg2) {
13
+ const ctx = arg2;
14
+ const propertyKey = String(ctx.name);
15
+ if (ctx.kind === 'field') {
16
+ return function (initialValue) {
17
+ this[propertyKey] = initialValue;
18
+ callback(this, propertyKey);
19
+ return this[propertyKey];
20
+ };
21
+ }
22
+ ctx.addInitializer(function () {
23
+ callback(this, propertyKey);
24
+ });
25
+ return;
26
+ }
27
+ // 2018-09 proposal: (descriptor: { kind, key, placement, initializer? })
28
+ if (typeof arg1 === 'object' && arg1 !== null && 'kind' in arg1 && 'key' in arg1) {
29
+ const desc = arg1;
30
+ const propertyKey = String(desc.key);
31
+ if (desc.kind === 'field') {
32
+ const origInit = desc.initializer;
33
+ return {
34
+ ...desc,
35
+ initializer() {
36
+ const value = origInit?.call(this);
37
+ this[propertyKey] = value;
38
+ callback(this, propertyKey);
39
+ return this[propertyKey];
40
+ },
41
+ };
42
+ }
43
+ return {
44
+ ...desc,
45
+ finisher(klass) {
46
+ callback(klass, propertyKey);
47
+ },
48
+ };
49
+ }
50
+ }
@@ -0,0 +1,13 @@
1
+ import type { StaticClass } from '../types/utils.js';
2
+ /**
3
+ * Generates static API of the given controllers for a static segment.
4
+ * @see https://vovk.dev/segment
5
+ * @example
6
+ * ```ts
7
+ * export function generateStaticParams() {
8
+ * return controllersToStaticParams(controllers);
9
+ * }
10
+ */
11
+ export declare function controllersToStaticParams(c: Record<string, StaticClass>, slug?: string): {
12
+ [slug]: string[];
13
+ }[];