vovk 3.0.4 → 3.1.1
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/dist/core/compose.d.ts +38 -0
- package/dist/core/compose.js +31 -0
- package/dist/core/controllersToStaticParams.js +1 -1
- package/dist/core/decorate.d.ts +43 -0
- package/dist/core/decorate.js +24 -0
- package/dist/core/decorators.js +1 -1
- package/dist/core/getSchema.js +1 -1
- package/dist/core/initSegment.js +28 -1
- package/dist/core/vovkApp.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/core.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { KnownAny } from '../types/utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Metadata stored on a handler by HTTP decorators and custom decorators when used outside decorator context (via compose).
|
|
4
|
+
*/
|
|
5
|
+
export type ComposeMetadata = {
|
|
6
|
+
httpMethod?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
options?: KnownAny;
|
|
9
|
+
decoratorAppliers?: ((controller: KnownAny, propertyKey: string) => void)[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Composes decorators and a handler/class into a single value.
|
|
13
|
+
*
|
|
14
|
+
* For method-level composition, decorators are stored and applied later by initSegment.
|
|
15
|
+
* For class-level composition, decorators like prefix() and cloneControllerMetadata()
|
|
16
|
+
* are applied immediately to the class in reverse order (matching stacked decorator semantics).
|
|
17
|
+
*
|
|
18
|
+
* @example Method-level
|
|
19
|
+
* ```ts
|
|
20
|
+
* static handleParams = compose(
|
|
21
|
+
* put('x/{foo}/{bar}/y'),
|
|
22
|
+
* authGuard(null),
|
|
23
|
+
* procedure({ params: z.object({ foo: z.string(), bar: z.string() }) })
|
|
24
|
+
* .handle(async (req) => req.vovk.params())
|
|
25
|
+
* );
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example Class-level
|
|
29
|
+
* ```ts
|
|
30
|
+
* const MyController = compose(
|
|
31
|
+
* prefix('users'),
|
|
32
|
+
* cloneControllerMetadata(),
|
|
33
|
+
* class extends ParentController {}
|
|
34
|
+
* );
|
|
35
|
+
* export default MyController;
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function compose<T>(...args: [...unknown[], T]): T;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function compose(...args) {
|
|
2
|
+
if (args.length === 0)
|
|
3
|
+
throw new Error('compose() requires at least one argument');
|
|
4
|
+
const last = args[args.length - 1];
|
|
5
|
+
const decoratorFns = args.slice(0, -1);
|
|
6
|
+
if (typeof last !== 'function') {
|
|
7
|
+
throw new Error('The last argument to compose() must be a function, handler, or class');
|
|
8
|
+
}
|
|
9
|
+
for (const decoratorFn of decoratorFns) {
|
|
10
|
+
if (typeof decoratorFn !== 'function') {
|
|
11
|
+
throw new Error('All arguments to compose() except the last must be decorator functions');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Detect class: native ES class constructors' toString() starts with "class"
|
|
15
|
+
if (last.toString().startsWith('class ')) {
|
|
16
|
+
// Apply class decorators in reverse order (bottom-up, matching stacked decorator semantics).
|
|
17
|
+
// Decorators mutate and return the same class, so .name is preserved.
|
|
18
|
+
for (let i = decoratorFns.length - 1; i >= 0; i--) {
|
|
19
|
+
decoratorFns[i](last);
|
|
20
|
+
}
|
|
21
|
+
return last;
|
|
22
|
+
}
|
|
23
|
+
// Method-level compose: store decorator appliers for deferred execution by initSegment
|
|
24
|
+
const handler = last;
|
|
25
|
+
handler._composeMetadata = handler._composeMetadata ?? {};
|
|
26
|
+
handler._composeMetadata.decoratorAppliers = handler._composeMetadata.decoratorAppliers ?? [];
|
|
27
|
+
for (const decoratorFn of decoratorFns) {
|
|
28
|
+
handler._composeMetadata.decoratorAppliers.push(decoratorFn);
|
|
29
|
+
}
|
|
30
|
+
return handler;
|
|
31
|
+
}
|
|
@@ -13,7 +13,7 @@ export function controllersToStaticParams(c, slug = 'vovk') {
|
|
|
13
13
|
{ [slug]: ['_schema_'] },
|
|
14
14
|
...Object.values(controllers).flatMap((controller) => {
|
|
15
15
|
const handlers = controller._handlers;
|
|
16
|
-
const splitPrefix = controller.
|
|
16
|
+
const splitPrefix = controller.prefix?.split('/') ?? [];
|
|
17
17
|
return Object.entries(handlers ?? {}).flatMap(([name, handler]) => {
|
|
18
18
|
const staticParams = controller._handlersMetadata?.[name]?.staticParams;
|
|
19
19
|
if (staticParams?.length) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { KnownAny } from '../types/utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Metadata stored on a handler by HTTP decorators and custom decorators when used outside decorator context (via decorate).
|
|
4
|
+
*/
|
|
5
|
+
export type DecorateMetadata = {
|
|
6
|
+
httpMethod?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
options?: KnownAny;
|
|
9
|
+
decoratorAppliers?: ((controller: KnownAny, propertyKey: string) => void)[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Applies decorators to a handler without using decorator syntax.
|
|
13
|
+
* Returns an object with `.handle()` to register the handler function.
|
|
14
|
+
*
|
|
15
|
+
* When the last argument is a procedure result (has `.handle`), its `.handle()` is proxied.
|
|
16
|
+
* Otherwise, `.handle()` wraps a plain handler directly.
|
|
17
|
+
*
|
|
18
|
+
* @example With procedure
|
|
19
|
+
* ```ts
|
|
20
|
+
* static handleParams = decorate(
|
|
21
|
+
* put('x/{foo}/{bar}/y'),
|
|
22
|
+
* authGuard(null),
|
|
23
|
+
* procedure({ params: z.object({ foo: z.string(), bar: z.string() }) })
|
|
24
|
+
* ).handle(async (req) => req.vovk.params());
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example Without procedure
|
|
28
|
+
* ```ts
|
|
29
|
+
* static getMethod = decorate(
|
|
30
|
+
* get(),
|
|
31
|
+
* ).handle(async () => {
|
|
32
|
+
* return { method: 'get' };
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function decorate<H extends {
|
|
37
|
+
handle: (...args: KnownAny[]) => KnownAny;
|
|
38
|
+
}>(...args: [...unknown[], H]): {
|
|
39
|
+
handle: H['handle'];
|
|
40
|
+
};
|
|
41
|
+
export declare function decorate(...args: unknown[]): {
|
|
42
|
+
handle: <T extends (...args: KnownAny[]) => KnownAny>(fn: T) => T;
|
|
43
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function decorate(...args) {
|
|
2
|
+
if (args.length === 0)
|
|
3
|
+
throw new Error('decorate() requires at least one argument');
|
|
4
|
+
const last = args[args.length - 1];
|
|
5
|
+
const hasProcedure = typeof last === 'function' && 'handle' in last && typeof last.handle === 'function';
|
|
6
|
+
const procedureResult = hasProcedure ? last : null;
|
|
7
|
+
const decoratorFns = (hasProcedure ? args.slice(0, -1) : args);
|
|
8
|
+
for (const decoratorFn of decoratorFns) {
|
|
9
|
+
if (typeof decoratorFn !== 'function') {
|
|
10
|
+
throw new Error('All decorator arguments to decorate() must be functions');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
handle(fn) {
|
|
15
|
+
const handler = procedureResult ? procedureResult.handle(fn) : fn;
|
|
16
|
+
handler._decorateMetadata = handler._decorateMetadata ?? {};
|
|
17
|
+
handler._decorateMetadata.decoratorAppliers = handler._decorateMetadata.decoratorAppliers ?? [];
|
|
18
|
+
for (const decoratorFn of decoratorFns) {
|
|
19
|
+
handler._decorateMetadata.decoratorAppliers.push(decoratorFn);
|
|
20
|
+
}
|
|
21
|
+
return handler;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/core/decorators.js
CHANGED
package/dist/core/getSchema.js
CHANGED
package/dist/core/initSegment.js
CHANGED
|
@@ -4,12 +4,39 @@ import { getSchema } from './getSchema.js';
|
|
|
4
4
|
export const initSegment = (options) => {
|
|
5
5
|
const segmentName = trimPath(options.segmentName ?? '');
|
|
6
6
|
options.segmentName = segmentName;
|
|
7
|
-
|
|
7
|
+
const controllerEntries = Object.entries(options.controllers ?? {});
|
|
8
|
+
const controllerSet = new Set(controllerEntries.map(([, c]) => c));
|
|
9
|
+
// Sort so parent controllers are initialized before their children
|
|
10
|
+
controllerEntries.sort(([, a], [, b]) => {
|
|
11
|
+
return (Number(controllerSet.has(Object.getPrototypeOf(a))) -
|
|
12
|
+
Number(controllerSet.has(Object.getPrototypeOf(b))));
|
|
13
|
+
});
|
|
14
|
+
for (const [rpcModuleName, controller] of controllerEntries) {
|
|
8
15
|
controller._segmentName = segmentName;
|
|
9
16
|
controller._rpcModuleName = rpcModuleName;
|
|
10
17
|
controller._onError = options?.onError;
|
|
11
18
|
controller._onSuccess = options?.onSuccess;
|
|
12
19
|
controller._onBefore = options?.onBefore;
|
|
20
|
+
// Apply deferred decorate() decorator appliers in reverse order (bottom-up, matching stacked decorator semantics)
|
|
21
|
+
for (const key of Object.getOwnPropertyNames(controller)) {
|
|
22
|
+
const appliers = controller[key]?._decorateMetadata
|
|
23
|
+
?.decoratorAppliers;
|
|
24
|
+
if (appliers) {
|
|
25
|
+
for (let i = appliers.length - 1; i >= 0; i--) {
|
|
26
|
+
appliers[i](controller, key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Re-clone metadata if this controller extends another registered controller
|
|
31
|
+
// (cloneControllerMetadata() runs at class-definition time, before decorate() metadata is applied)
|
|
32
|
+
const parent = Object.getPrototypeOf(controller);
|
|
33
|
+
if (controllerSet.has(parent) && parent._handlers) {
|
|
34
|
+
controller._handlers = { ...parent._handlers, ...controller._handlers };
|
|
35
|
+
controller._handlersMetadata = { ...parent._handlersMetadata, ...controller._handlersMetadata };
|
|
36
|
+
for (const methods of Object.values(vovkApp.routes)) {
|
|
37
|
+
methods.set(controller, { ...(methods.get(parent) ?? {}), ...methods.get(controller) });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
13
40
|
}
|
|
14
41
|
async function GET_DEV(req, data) {
|
|
15
42
|
const params = await data.params;
|
package/dist/core/vovkApp.js
CHANGED
|
@@ -194,7 +194,7 @@ class VovkApp {
|
|
|
194
194
|
controllers.forEach((staticMethods, controller) => {
|
|
195
195
|
if (segmentName !== controller._segmentName)
|
|
196
196
|
return;
|
|
197
|
-
const prefix = controller.
|
|
197
|
+
const prefix = controller.prefix ?? '';
|
|
198
198
|
Object.entries(staticMethods ?? {}).forEach(([path, staticMethod]) => {
|
|
199
199
|
const fullPath = [prefix, path].filter(Boolean).join('/');
|
|
200
200
|
handlers[fullPath] = { staticMethod, controller };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { multitenant } from './core/multitenant.js';
|
|
|
5
5
|
export { JSONLinesResponder } from './core/JSONLinesResponder.js';
|
|
6
6
|
export { toDownloadResponse } from './core/toDownloadResponse.js';
|
|
7
7
|
export { get, post, put, patch, del, head, options, prefix, cloneControllerMetadata } from './core/decorators.js';
|
|
8
|
+
export { decorate } from './core/decorate.js';
|
|
8
9
|
export { progressive } from './client/progressive.js';
|
|
9
10
|
export { fetcher, createFetcher } from './client/fetcher.js';
|
|
10
11
|
export { initSegment } from './core/initSegment.js';
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { multitenant } from './core/multitenant.js';
|
|
|
6
6
|
export { JSONLinesResponder } from './core/JSONLinesResponder.js';
|
|
7
7
|
export { toDownloadResponse } from './core/toDownloadResponse.js';
|
|
8
8
|
export { get, post, put, patch, del, head, options, prefix, cloneControllerMetadata } from './core/decorators.js';
|
|
9
|
+
export { decorate } from './core/decorate.js';
|
|
9
10
|
// client
|
|
10
11
|
export { progressive } from './client/progressive.js';
|
|
11
12
|
export { fetcher, createFetcher } from './client/fetcher.js';
|
package/dist/types/core.d.ts
CHANGED
|
@@ -86,7 +86,7 @@ export type StreamAbortMessage = {
|
|
|
86
86
|
export type VovkControllerInternal = {
|
|
87
87
|
_segmentName: string;
|
|
88
88
|
_rpcModuleName?: VovkControllerSchema['rpcModuleName'];
|
|
89
|
-
|
|
89
|
+
prefix?: VovkControllerSchema['prefix'];
|
|
90
90
|
_handlers: VovkControllerSchema['handlers'];
|
|
91
91
|
_handlersMetadata?: Record<string, {
|
|
92
92
|
staticParams?: Record<string, string>[];
|