vovk 3.0.0-draft.7 → 3.0.0-draft.71

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 (102) hide show
  1. package/README.md +8 -95
  2. package/{HttpException.d.ts → dist/HttpException.d.ts} +2 -2
  3. package/{HttpException.js → dist/HttpException.js} +3 -3
  4. package/{StreamResponse.d.ts → dist/StreamJSONResponse.d.ts} +3 -3
  5. package/{StreamResponse.js → dist/StreamJSONResponse.js} +5 -5
  6. package/{Segment.d.ts → dist/VovkApp.d.ts} +11 -10
  7. package/{Segment.js → dist/VovkApp.js} +28 -24
  8. package/dist/client/createRPC.d.ts +4 -0
  9. package/{client/clientizeController.js → dist/client/createRPC.js} +22 -40
  10. package/dist/client/defaultFetcher.d.ts +4 -0
  11. package/{client → dist/client}/defaultFetcher.js +19 -8
  12. package/{client → dist/client}/defaultHandler.d.ts +1 -1
  13. package/dist/client/defaultHandler.js +22 -0
  14. package/dist/client/defaultStreamHandler.d.ts +4 -0
  15. package/{client → dist/client}/defaultStreamHandler.js +5 -5
  16. package/dist/client/index.d.ts +2 -0
  17. package/dist/client/index.js +8 -0
  18. package/dist/client/types.d.ts +103 -0
  19. package/dist/createDecorator.d.ts +4 -0
  20. package/{createDecorator.js → dist/createDecorator.js} +17 -15
  21. package/{createSegment.d.ts → dist/createVovkApp.d.ts} +10 -10
  22. package/{createSegment.js → dist/createVovkApp.js} +25 -25
  23. package/dist/index.d.ts +60 -0
  24. package/dist/index.js +24 -0
  25. package/dist/openapi/fromSchema.d.ts +3 -0
  26. package/dist/openapi/fromSchema.js +26 -0
  27. package/dist/openapi/index.d.ts +1 -0
  28. package/dist/openapi/index.js +5 -0
  29. package/dist/openapi/openapi.d.ts +10 -0
  30. package/dist/openapi/openapi.js +70 -0
  31. package/dist/types.d.ts +148 -0
  32. package/dist/types.js +65 -0
  33. package/dist/utils/generateStaticAPI.d.ts +4 -0
  34. package/{generateStaticAPI.js → dist/utils/generateStaticAPI.js} +3 -3
  35. package/{utils → dist/utils}/getSchema.d.ts +1 -2
  36. package/{utils → dist/utils}/getSchema.js +5 -16
  37. package/dist/utils/parseQuery.d.ts +25 -0
  38. package/dist/utils/parseQuery.js +156 -0
  39. package/dist/utils/reqForm.d.ts +2 -0
  40. package/dist/utils/reqForm.js +13 -0
  41. package/{utils → dist/utils}/reqMeta.d.ts +1 -2
  42. package/{utils → dist/utils}/reqQuery.d.ts +1 -2
  43. package/dist/utils/reqQuery.js +10 -0
  44. package/dist/utils/serializeQuery.d.ts +13 -0
  45. package/dist/utils/serializeQuery.js +65 -0
  46. package/dist/utils/setHandlerValidation.d.ts +4 -0
  47. package/dist/utils/setHandlerValidation.js +21 -0
  48. package/package.json +8 -2
  49. package/src/HttpException.ts +16 -0
  50. package/src/StreamJSONResponse.ts +61 -0
  51. package/src/VovkApp.ts +240 -0
  52. package/src/client/createRPC.ts +120 -0
  53. package/src/client/defaultFetcher.ts +69 -0
  54. package/src/client/defaultHandler.ts +23 -0
  55. package/src/client/defaultStreamHandler.ts +88 -0
  56. package/src/client/index.ts +9 -0
  57. package/src/client/types.ts +120 -0
  58. package/src/createDecorator.ts +61 -0
  59. package/src/createVovkApp.ts +168 -0
  60. package/src/index.ts +71 -0
  61. package/src/openapi/fromSchema.ts +33 -0
  62. package/src/openapi/index.ts +1 -0
  63. package/src/openapi/openapi.ts +85 -0
  64. package/src/types.ts +200 -0
  65. package/src/utils/generateStaticAPI.ts +18 -0
  66. package/src/utils/getSchema.ts +35 -0
  67. package/src/utils/parseQuery.ts +160 -0
  68. package/src/utils/reqForm.ts +16 -0
  69. package/src/utils/reqMeta.ts +16 -0
  70. package/src/utils/reqQuery.ts +6 -0
  71. package/src/utils/serializeQuery.ts +69 -0
  72. package/src/utils/setHandlerValidation.ts +35 -0
  73. package/src/utils/shim.ts +17 -0
  74. package/.npmignore +0 -2
  75. package/client/clientizeController.d.ts +0 -4
  76. package/client/defaultFetcher.d.ts +0 -4
  77. package/client/defaultHandler.js +0 -21
  78. package/client/defaultStreamHandler.d.ts +0 -4
  79. package/client/index.d.ts +0 -4
  80. package/client/index.js +0 -5
  81. package/client/types.d.ts +0 -102
  82. package/createDecorator.d.ts +0 -4
  83. package/generateStaticAPI.d.ts +0 -4
  84. package/index.d.ts +0 -60
  85. package/index.js +0 -20
  86. package/types.d.ts +0 -191
  87. package/types.js +0 -65
  88. package/utils/reqQuery.js +0 -25
  89. package/utils/setClientValidatorsForHandler.d.ts +0 -5
  90. package/utils/setClientValidatorsForHandler.js +0 -28
  91. package/worker/index.d.ts +0 -3
  92. package/worker/index.js +0 -7
  93. package/worker/promisifyWorker.d.ts +0 -2
  94. package/worker/promisifyWorker.js +0 -143
  95. package/worker/types.d.ts +0 -31
  96. package/worker/types.js +0 -2
  97. package/worker/worker.d.ts +0 -1
  98. package/worker/worker.js +0 -44
  99. /package/{client → dist/client}/types.js +0 -0
  100. /package/{utils → dist/utils}/reqMeta.js +0 -0
  101. /package/{utils → dist/utils}/shim.d.ts +0 -0
  102. /package/{utils → dist/utils}/shim.js +0 -0
package/src/types.ts ADDED
@@ -0,0 +1,200 @@
1
+ import type { NextRequest } from 'next/server';
2
+ import type { OperationObject } from 'openapi3-ts/oas31';
3
+ import type { StreamJSONResponse } from './StreamJSONResponse';
4
+ import { VovkStreamAsyncIterable } from './client/types';
5
+
6
+ export type KnownAny = any; // eslint-disable-line @typescript-eslint/no-explicit-any
7
+
8
+ export type StaticClass = Function; // eslint-disable-line @typescript-eslint/no-unsafe-function-type
9
+
10
+ export type VovkHandlerSchema = {
11
+ path: string;
12
+ httpMethod: string; // HttpMethod type makes JSON incompatible with VovkHandlerSchema type
13
+ validation?: { query?: KnownAny; body?: KnownAny; output?: KnownAny };
14
+ openapi?: OperationObject;
15
+ custom?: Record<string, KnownAny>;
16
+ };
17
+
18
+ export type VovkControllerSchema = {
19
+ controllerName: string;
20
+ originalControllerName: string;
21
+ prefix?: string;
22
+ handlers: Record<string, VovkHandlerSchema>;
23
+ };
24
+
25
+ export type VovkSchema = {
26
+ emitSchema: boolean;
27
+ segmentName: string;
28
+ controllers: Record<string, VovkControllerSchema>;
29
+ };
30
+
31
+ export type VovkErrorResponse = {
32
+ cause?: unknown;
33
+ statusCode: HttpStatus;
34
+ message: string;
35
+ isError: true;
36
+ };
37
+
38
+ export type VovkControllerInternal = {
39
+ _controllerName?: VovkControllerSchema['controllerName'];
40
+ _prefix?: VovkControllerSchema['prefix'];
41
+ _handlers: VovkControllerSchema['handlers'];
42
+ _activated?: true;
43
+ _onError?: (err: Error, req: VovkRequest) => void | Promise<void>;
44
+ };
45
+
46
+ export type VovkController = StaticClass &
47
+ VovkControllerInternal & {
48
+ [key: string]: unknown;
49
+ };
50
+
51
+ export type DecoratorOptions = {
52
+ cors?: boolean;
53
+ headers?: Record<string, string>;
54
+ };
55
+
56
+ export type RouteHandler = ((
57
+ req: VovkRequest,
58
+ params: Record<string, string>
59
+ ) => Response | Promise<Response> | Iterable<unknown> | AsyncIterable<unknown>) & {
60
+ _options?: DecoratorOptions;
61
+ };
62
+
63
+ export interface VovkRequest<BODY = undefined, QUERY extends object | undefined = undefined>
64
+ extends Omit<NextRequest, 'json' | 'nextUrl'> {
65
+ json: () => Promise<BODY>;
66
+ nextUrl: Omit<NextRequest['nextUrl'], 'searchParams'> & {
67
+ searchParams: Omit<
68
+ NextRequest['nextUrl']['searchParams'],
69
+ 'get' | 'getAll' | 'entries' | 'forEach' | 'keys' | 'values'
70
+ > & {
71
+ get: <KEY extends keyof QUERY>(key: KEY) => QUERY[KEY] extends readonly (infer ITEM)[] ? ITEM : QUERY[KEY];
72
+ getAll: <KEY extends keyof QUERY>(key: KEY) => QUERY[KEY] extends KnownAny[] ? QUERY[KEY] : QUERY[KEY][];
73
+ entries: () => IterableIterator<[keyof QUERY, QUERY[keyof QUERY]]>;
74
+ forEach: (
75
+ callbackfn: (
76
+ value: QUERY[keyof QUERY],
77
+ key: keyof QUERY,
78
+ searchParams: NextRequest['nextUrl']['searchParams'] // original searchParams
79
+ ) => void
80
+ ) => void;
81
+ keys: () => IterableIterator<keyof QUERY>;
82
+ values: () => IterableIterator<QUERY[keyof QUERY]>;
83
+ // TODO (?) append, delete, set
84
+ };
85
+ };
86
+ vovk: {
87
+ body: () => Promise<BODY>;
88
+ query: () => QUERY;
89
+ meta: <T = Record<KnownAny, KnownAny>>(meta?: T | null) => T;
90
+ form: <T = KnownAny>() => Promise<T>;
91
+ };
92
+ }
93
+
94
+ export type ControllerStaticMethod<
95
+ REQ extends VovkRequest<KnownAny, KnownAny> = VovkRequest<undefined, Record<string, KnownAny>>,
96
+ PARAMS extends { [key: string]: string } = KnownAny,
97
+ > = ((req: REQ, params: PARAMS) => unknown) & {
98
+ _controller?: VovkController;
99
+ };
100
+
101
+ export type VovkControllerBody<T extends (...args: KnownAny) => KnownAny> = Awaited<
102
+ ReturnType<Parameters<T>[0]['vovk']['body']>
103
+ >;
104
+
105
+ export type VovkControllerQuery<T extends (...args: KnownAny) => KnownAny> = ReturnType<
106
+ Parameters<T>[0]['vovk']['query']
107
+ >;
108
+
109
+ export type VovkControllerParams<T extends (...args: KnownAny) => KnownAny> = Parameters<T>[1];
110
+
111
+ export type VovkControllerYieldType<T extends (req: VovkRequest<KnownAny, KnownAny>) => KnownAny> = T extends (
112
+ ...args: KnownAny[]
113
+ ) => AsyncGenerator<infer Y, KnownAny, KnownAny>
114
+ ? Y
115
+ : T extends (...args: KnownAny[]) => Generator<infer Y, KnownAny, KnownAny>
116
+ ? Y
117
+ : T extends (...args: KnownAny[]) => Promise<StreamJSONResponse<infer Y>> | StreamJSONResponse<infer Y>
118
+ ? Y
119
+ : never;
120
+
121
+ export type VovkBody<T extends (...args: KnownAny[]) => unknown> = Parameters<T>[0]['body'];
122
+
123
+ export type VovkQuery<T extends (...args: KnownAny[]) => unknown> = Parameters<T>[0]['query'];
124
+
125
+ export type VovkParams<T extends (...args: KnownAny[]) => unknown> = Parameters<T>[0]['params'];
126
+
127
+ export type VovkYieldType<T extends (...args: KnownAny[]) => unknown> = T extends (
128
+ ...args: KnownAny[]
129
+ ) => Promise<VovkStreamAsyncIterable<infer Y>>
130
+ ? Y
131
+ : never;
132
+
133
+ export type VovkReturnType<T extends (...args: KnownAny) => unknown> = Awaited<ReturnType<T>>;
134
+
135
+ export type StreamAbortMessage = {
136
+ isError: true;
137
+ reason: KnownAny;
138
+ };
139
+
140
+ export enum HttpMethod {
141
+ GET = 'GET',
142
+ POST = 'POST',
143
+ PUT = 'PUT',
144
+ PATCH = 'PATCH',
145
+ DELETE = 'DELETE',
146
+ HEAD = 'HEAD',
147
+ OPTIONS = 'OPTIONS',
148
+ }
149
+
150
+ export enum HttpStatus {
151
+ NULL = 0,
152
+ CONTINUE = 100,
153
+ SWITCHING_PROTOCOLS = 101,
154
+ PROCESSING = 102,
155
+ EARLYHINTS = 103,
156
+ OK = 200,
157
+ CREATED = 201,
158
+ ACCEPTED = 202,
159
+ NON_AUTHORITATIVE_INFORMATION = 203,
160
+ NO_CONTENT = 204,
161
+ RESET_CONTENT = 205,
162
+ PARTIAL_CONTENT = 206,
163
+ AMBIGUOUS = 300,
164
+ MOVED_PERMANENTLY = 301,
165
+ FOUND = 302,
166
+ SEE_OTHER = 303,
167
+ NOT_MODIFIED = 304,
168
+ TEMPORARY_REDIRECT = 307,
169
+ PERMANENT_REDIRECT = 308,
170
+ BAD_REQUEST = 400,
171
+ UNAUTHORIZED = 401,
172
+ PAYMENT_REQUIRED = 402,
173
+ FORBIDDEN = 403,
174
+ NOT_FOUND = 404,
175
+ METHOD_NOT_ALLOWED = 405,
176
+ NOT_ACCEPTABLE = 406,
177
+ PROXY_AUTHENTICATION_REQUIRED = 407,
178
+ REQUEST_TIMEOUT = 408,
179
+ CONFLICT = 409,
180
+ GONE = 410,
181
+ LENGTH_REQUIRED = 411,
182
+ PRECONDITION_FAILED = 412,
183
+ PAYLOAD_TOO_LARGE = 413,
184
+ URI_TOO_LONG = 414,
185
+ UNSUPPORTED_MEDIA_TYPE = 415,
186
+ REQUESTED_RANGE_NOT_SATISFIABLE = 416,
187
+ EXPECTATION_FAILED = 417,
188
+ I_AM_A_TEAPOT = 418,
189
+ MISDIRECTED = 421,
190
+ UNPROCESSABLE_ENTITY = 422,
191
+ FAILED_DEPENDENCY = 424,
192
+ PRECONDITION_REQUIRED = 428,
193
+ TOO_MANY_REQUESTS = 429,
194
+ INTERNAL_SERVER_ERROR = 500,
195
+ NOT_IMPLEMENTED = 501,
196
+ BAD_GATEWAY = 502,
197
+ SERVICE_UNAVAILABLE = 503,
198
+ GATEWAY_TIMEOUT = 504,
199
+ HTTP_VERSION_NOT_SUPPORTED = 505,
200
+ }
@@ -0,0 +1,18 @@
1
+ import type { VovkController, StaticClass } from '../types';
2
+
3
+ export function generateStaticAPI(c: Record<string, StaticClass>, slug = 'vovk') {
4
+ const controllers = c as Record<string, VovkController>;
5
+ return [
6
+ { [slug]: ['_schema_'] },
7
+ ...Object.values(controllers)
8
+ .map((controller) => {
9
+ const handlers = controller._handlers;
10
+ const splitPrefix = controller._prefix?.split('/') ?? [];
11
+
12
+ return Object.values(handlers).map((handler) => {
13
+ return { [slug]: [...splitPrefix, ...handler.path.split('/')].filter(Boolean) };
14
+ });
15
+ })
16
+ .flat(),
17
+ ];
18
+ }
@@ -0,0 +1,35 @@
1
+ import type { VovkSchema, VovkController, StaticClass } from '../types';
2
+
3
+ export default function getSchema(options: {
4
+ emitSchema?: boolean;
5
+ segmentName?: string;
6
+ controllers: Record<string, StaticClass>;
7
+ exposeValidation?: boolean;
8
+ }) {
9
+ const exposeValidation = options?.exposeValidation ?? true;
10
+ const emitSchema = options.emitSchema ?? true;
11
+ const schema: VovkSchema = {
12
+ emitSchema,
13
+ segmentName: options.segmentName ?? '',
14
+ controllers: {},
15
+ };
16
+
17
+ if (!emitSchema) return schema;
18
+
19
+ for (const [controllerName, controller] of Object.entries(options.controllers) as [string, VovkController][]) {
20
+ schema.controllers[controllerName] = {
21
+ controllerName: controllerName,
22
+ originalControllerName: controller.name,
23
+ prefix: controller._prefix ?? '',
24
+ handlers: {
25
+ ...(exposeValidation
26
+ ? controller._handlers
27
+ : Object.fromEntries(
28
+ Object.entries(controller._handlers ?? {}).map(([key, value]) => [key, { ...value, validation: {} }])
29
+ )),
30
+ },
31
+ };
32
+ }
33
+
34
+ return schema;
35
+ }
@@ -0,0 +1,160 @@
1
+ import type { KnownAny } from '../types';
2
+
3
+ /**
4
+ * Parse a bracket-based key (e.g. "z[d][0][x]" or "arr[]")
5
+ * into an array of path segments (strings or special push-markers).
6
+ *
7
+ * Example: "z[d][0][x]" => ["z", "d", "0", "x"]
8
+ * Example: "arr[]" => ["arr", "" ] // "" indicates "push" onto array
9
+ */
10
+ function parseKey(key: string): string[] {
11
+ // The first segment is everything up to the first '[' (or the entire key if no '[')
12
+ const segments: string[] = [];
13
+ const topKeyMatch = key.match(/^([^[\]]+)/);
14
+ if (topKeyMatch) {
15
+ segments.push(topKeyMatch[1]);
16
+ } else {
17
+ // If it starts with brackets, treat it as empty? (edge case)
18
+ segments.push('');
19
+ }
20
+
21
+ // Now capture all bracket parts: [something], [0], []
22
+ const bracketRegex = /\[([^[\]]*)\]/g;
23
+ let match: RegExpExecArray | null;
24
+ while ((match = bracketRegex.exec(key)) !== null) {
25
+ // match[1] is the content inside the brackets
26
+ segments.push(match[1]);
27
+ }
28
+
29
+ return segments;
30
+ }
31
+
32
+ /**
33
+ * Recursively set a value in a nested object/array, given a path of segments.
34
+ * - If segment is numeric => treat as array index
35
+ * - If segment is empty "" => push to array
36
+ * - Else => object property
37
+ */
38
+ function setValue(obj: Record<string, KnownAny>, path: string[], value: KnownAny): void {
39
+ let current: KnownAny = obj;
40
+
41
+ for (let i = 0; i < path.length; i++) {
42
+ const segment = path[i];
43
+
44
+ // If we're at the last segment, set the value
45
+ if (i === path.length - 1) {
46
+ if (segment === '') {
47
+ // Empty bracket => push
48
+ if (!Array.isArray(current)) {
49
+ current = [];
50
+ }
51
+ current.push(value);
52
+ } else if (!isNaN(Number(segment))) {
53
+ // Numeric segment => array index
54
+ const idx = Number(segment);
55
+ if (!Array.isArray(current)) {
56
+ current = [];
57
+ }
58
+ current[idx] = value;
59
+ } else {
60
+ // Object property
61
+ current[segment] = value;
62
+ }
63
+ } else {
64
+ // Not the last segment: descend into existing structure or create it
65
+ const nextSegment = path[i + 1];
66
+
67
+ if (segment === '') {
68
+ // Empty bracket => push
69
+ if (!Array.isArray(current)) {
70
+ // Convert the current node into an array, if not one
71
+ current = [];
72
+ }
73
+ // If we are not at the last path, we need a placeholder object or array
74
+ // for the next segment. We'll push something and move current to that.
75
+ if (current.length === 0) {
76
+ // nothing in array yet
77
+ current.push(typeof nextSegment === 'string' && !isNaN(Number(nextSegment)) ? [] : {});
78
+ } else if (typeof nextSegment === 'string' && !isNaN(Number(nextSegment))) {
79
+ // next is numeric => we want an array
80
+ if (!Array.isArray(current[current.length - 1])) {
81
+ current[current.length - 1] = [];
82
+ }
83
+ } else {
84
+ // next is not numeric => we want an object
85
+ if (typeof current[current.length - 1] !== 'object') {
86
+ current[current.length - 1] = {};
87
+ }
88
+ }
89
+ current = current[current.length - 1];
90
+ } else if (!isNaN(Number(segment))) {
91
+ // segment is numeric => array index
92
+ const idx = Number(segment);
93
+ if (!Array.isArray(current)) {
94
+ current = [];
95
+ }
96
+ if (current[idx] === undefined) {
97
+ // Create placeholder for next segment
98
+ current[idx] = typeof nextSegment === 'string' && !isNaN(Number(nextSegment)) ? [] : {};
99
+ }
100
+ current = current[idx];
101
+ } else {
102
+ // segment is an object key
103
+ if (current[segment] === undefined) {
104
+ // Create placeholder
105
+ current[segment] = typeof nextSegment === 'string' && !isNaN(Number(nextSegment)) ? [] : {};
106
+ }
107
+ current = current[segment];
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Deserialize a bracket-based query string into an object.
115
+ *
116
+ * Supports:
117
+ * - Key/value pairs with nested brackets (e.g. "a[b][0]=value")
118
+ * - Arrays with empty bracket (e.g. "arr[]=1&arr[]=2")
119
+ * - Mixed arrays of objects, etc.
120
+ *
121
+ * @example
122
+ * parseQuery("x=xx&y[0]=yy&y[1]=uu&z[f]=x&z[u][0]=uu&z[u][1]=xx&z[d][x]=ee")
123
+ * => {
124
+ * x: "xx",
125
+ * y: ["yy", "uu"],
126
+ * z: {
127
+ * f: "x",
128
+ * u: ["uu", "xx"],
129
+ * d: { x: "ee" }
130
+ * }
131
+ * }
132
+ *
133
+ * @param queryString - The raw query string (e.g. location.search.slice(1))
134
+ * @returns - A nested object representing the query params
135
+ */
136
+ export default function parseQuery(queryString: string): Record<string, KnownAny> {
137
+ const result: Record<string, KnownAny> = {};
138
+
139
+ if (!queryString) return result;
140
+
141
+ // Split into key=value pairs
142
+ const pairs = queryString
143
+ .replace(/^\?/, '') // Remove leading "?" if present
144
+ .split('&');
145
+
146
+ for (const pair of pairs) {
147
+ const [rawKey, rawVal = ''] = pair.split('=');
148
+
149
+ const decodedKey = decodeURIComponent(rawKey || '');
150
+ const decodedVal = decodeURIComponent(rawVal);
151
+
152
+ // Parse bracket notation
153
+ const pathSegments = parseKey(decodedKey);
154
+
155
+ // Insert into the result object
156
+ setValue(result, pathSegments, decodedVal);
157
+ }
158
+
159
+ return result;
160
+ }
@@ -0,0 +1,16 @@
1
+ import type { KnownAny, VovkRequest } from '../types';
2
+
3
+ const formMap = new WeakMap();
4
+
5
+ export default async function reqForm<T = KnownAny>(req: VovkRequest<KnownAny, KnownAny>): Promise<T> {
6
+ if (formMap.has(req)) {
7
+ return formMap.get(req) as T;
8
+ }
9
+
10
+ const body = await req.formData();
11
+ const formData = Object.fromEntries(body.entries()) as T;
12
+
13
+ formMap.set(req, formData);
14
+
15
+ return formData;
16
+ }
@@ -0,0 +1,16 @@
1
+ import type { KnownAny, VovkRequest } from '../types';
2
+
3
+ const metaMap = new WeakMap();
4
+
5
+ export default function reqMeta<T = Record<KnownAny, KnownAny>>(
6
+ req: VovkRequest<KnownAny, KnownAny>,
7
+ meta?: T | null
8
+ ): T {
9
+ if (meta) {
10
+ metaMap.set(req, { ...metaMap.get(req), ...meta });
11
+ } else if (meta === null) {
12
+ metaMap.delete(req);
13
+ }
14
+
15
+ return (metaMap.get(req) ?? {}) as T;
16
+ }
@@ -0,0 +1,6 @@
1
+ import type { KnownAny, VovkRequest } from '../types';
2
+ import parseQuery from './parseQuery';
3
+
4
+ export default function reqQuery<T extends object | undefined>(req: VovkRequest<KnownAny, T>): T {
5
+ return parseQuery(req.nextUrl.search) as NonNullable<T>;
6
+ }
@@ -0,0 +1,69 @@
1
+ import type { KnownAny } from '../types';
2
+
3
+ /**
4
+ * Recursively build query parameters from an object.
5
+ *
6
+ * @param key - The query key so far (e.g. 'user', 'user[0]', 'user[0][name]')
7
+ * @param value - The current value to serialize
8
+ * @returns - An array of `key=value` strings
9
+ */
10
+ function buildParams(key: string, value: KnownAny): string[] {
11
+ if (value === null || value === undefined) {
12
+ return []; // skip null/undefined values entirely
13
+ }
14
+
15
+ // If value is an object or array, we need to recurse
16
+ if (typeof value === 'object') {
17
+ // Array case
18
+ if (Array.isArray(value)) {
19
+ /**
20
+ * We use index-based bracket notation here:
21
+ * e.g. for value = ['aa', 'bb'] and key = 'foo'
22
+ * => "foo[0]=aa&foo[1]=bb"
23
+ *
24
+ * If you prefer "foo[]=aa&foo[]=bb" style, replace:
25
+ * `${key}[${i}]`
26
+ * with:
27
+ * `${key}[]`
28
+ */
29
+ return value.flatMap((v, i) => {
30
+ const newKey = `${key}[${i}]`;
31
+ return buildParams(newKey, v);
32
+ });
33
+ }
34
+
35
+ // Plain object case
36
+ return Object.keys(value).flatMap((k) => {
37
+ const newKey = `${key}[${k}]`;
38
+ return buildParams(newKey, value[k]);
39
+ });
40
+ }
41
+
42
+ return [`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`];
43
+ }
44
+
45
+ /**
46
+ * Serialize a nested object (including arrays, arrays of objects, etc.)
47
+ * into a bracket-based query string.
48
+ *
49
+ * @example
50
+ * serializeQuery({ x: 'xx', y: [1, 2], z: { f: 'x' } })
51
+ * => "x=xx&y[0]=1&y[1]=2&z[f]=x"
52
+ *
53
+ * @param obj - The input object to be serialized
54
+ * @returns - A bracket-based query string (without leading "?")
55
+ */
56
+ export default function serializeQuery(obj: Record<string, KnownAny>): string {
57
+ if (!obj || typeof obj !== 'object') return '';
58
+
59
+ // Collect query segments
60
+ const segments: string[] = [];
61
+ for (const key in obj) {
62
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
63
+ const value = obj[key];
64
+ segments.push(...buildParams(key, value));
65
+ }
66
+ }
67
+
68
+ return segments.join('&');
69
+ }
@@ -0,0 +1,35 @@
1
+ import type { KnownAny, VovkController, VovkHandlerSchema } from '../types';
2
+
3
+ export async function setHandlerValidation(
4
+ h: ((...args: KnownAny[]) => KnownAny) & { _onSettled?: (controller: VovkController) => void },
5
+ validation: Pick<Exclude<VovkHandlerSchema['validation'], undefined>, 'body' | 'query' | 'output'>
6
+ ) {
7
+ h._onSettled = (controller) => {
8
+ if (!controller) {
9
+ throw new Error(
10
+ 'Error setting client validators. Controller not found. Did you forget to use an HTTP decorator?'
11
+ );
12
+ }
13
+
14
+ const handlerName = Object.getOwnPropertyNames(controller).find(
15
+ (key) =>
16
+ (
17
+ controller[key] as {
18
+ _sourceMethod?: unknown;
19
+ }
20
+ )._sourceMethod === h
21
+ );
22
+
23
+ if (!handlerName) {
24
+ throw new Error('Error setting client validators. Handler not found.');
25
+ }
26
+
27
+ controller._handlers = {
28
+ ...controller._handlers,
29
+ [handlerName]: {
30
+ ...controller._handlers[handlerName],
31
+ validation,
32
+ },
33
+ };
34
+ };
35
+ }
@@ -0,0 +1,17 @@
1
+ if (typeof Symbol.dispose !== 'symbol') {
2
+ Object.defineProperty(Symbol, 'dispose', {
3
+ configurable: false,
4
+ enumerable: false,
5
+ writable: false,
6
+ value: Symbol.for('dispose'),
7
+ });
8
+ }
9
+
10
+ if (typeof Symbol.asyncDispose !== 'symbol') {
11
+ Object.defineProperty(Symbol, 'asyncDispose', {
12
+ configurable: false,
13
+ enumerable: false,
14
+ writable: false,
15
+ value: Symbol.for('asyncDispose'),
16
+ });
17
+ }
package/.npmignore DELETED
@@ -1,2 +0,0 @@
1
- !*
2
- tsconfig.*
@@ -1,4 +0,0 @@
1
- import { type _VovkControllerSchema as VovkControllerSchema, type _KnownAny as KnownAny } from '../types';
2
- import { type _VovkClientOptions as VovkClientOptions, type _VovkClient as VovkClient, type _VovkDefaultFetcherOptions as VovkDefaultFetcherOptions } from './types';
3
- export declare const ARRAY_QUERY_KEY = "_vovkarr";
4
- export declare const _clientizeController: <T, OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions>(givenController: VovkControllerSchema, segmentName?: string, options?: VovkClientOptions<OPTS>) => VovkClient<T, OPTS>;
@@ -1,4 +0,0 @@
1
- import type { _VovkDefaultFetcherOptions as VovkDefaultFetcherOptions, _VovkClientFetcher as VovkClientFetcher } from './types';
2
- export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultFetcher";
3
- declare const defaultFetcher: VovkClientFetcher<VovkDefaultFetcherOptions>;
4
- export default defaultFetcher;
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._defaultHandler = exports.DEFAULT_ERROR_MESSAGE = void 0;
4
- const HttpException_1 = require("../HttpException");
5
- exports.DEFAULT_ERROR_MESSAGE = 'Unknown error at defaultHandler';
6
- const _defaultHandler = async (response) => {
7
- let result;
8
- try {
9
- result = await response.json();
10
- }
11
- catch (e) {
12
- // handle parsing errors
13
- throw new HttpException_1._HttpException(response.status, e?.message ?? exports.DEFAULT_ERROR_MESSAGE);
14
- }
15
- if (!response.ok) {
16
- // handle server errors
17
- throw new HttpException_1._HttpException(response.status, result?.message ?? exports.DEFAULT_ERROR_MESSAGE);
18
- }
19
- return result;
20
- };
21
- exports._defaultHandler = _defaultHandler;
@@ -1,4 +0,0 @@
1
- import type { _StreamAsyncIterator as StreamAsyncIterator } from './types';
2
- import '../utils/shim';
3
- export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultStreamHandler";
4
- export declare const _defaultStreamHandler: (response: Response) => Promise<StreamAsyncIterator<unknown>>;
package/client/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import { _clientizeController as clientizeController } from './clientizeController';
2
- import type { _VovkClientFetcher as VovkClientFetcher, _VovkClientOptions as VovkClientOptions, _VovkDefaultFetcherOptions as VovkDefaultFetcherOptions } from './types';
3
- export { clientizeController };
4
- export type { VovkClientFetcher, VovkClientOptions, VovkDefaultFetcherOptions };
package/client/index.js DELETED
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.clientizeController = void 0;
4
- const clientizeController_1 = require("./clientizeController");
5
- Object.defineProperty(exports, "clientizeController", { enumerable: true, get: function () { return clientizeController_1._clientizeController; } });