cabloy 5.1.236 → 5.1.239
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/.cabloy-version +1 -1
- package/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/repo-docs/backend/broadcast-guide.md +2 -0
- package/repo-docs/backend/queue-guide.md +31 -0
- package/repo-docs/backend/schedule-guide.md +23 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/resource/locale/appLocale.ts +2 -2
- package/vona/packages-vona/vona-core/src/lib/bean/resource/locale/utils.ts +46 -1
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +4 -0
- package/vona/packages-vona/vona-core/src/lib/module/locales.ts +41 -22
- package/vona/packages-vona/vona-core/src/lib/utils/zod-enhance.ts +1 -1
- package/vona/packages-vona/vona-core/src/types/config/config.ts +2 -0
- package/vona/packages-vona/vona-core/src/types/config/locale.ts +11 -4
- package/vona/packages-vona/vona-core/src/types/interface/module.ts +1 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/patches/{zova-core@5.1.94.patch → zova-core@5.1.95.patch} +2 -2
- package/vona/pnpm-lock.yaml +146 -21
- package/vona/pnpm-workspace.yaml +1 -1
- package/vona/src/backend/config/config/config.ts +9 -0
- package/vona/src/suite/a-home/modules/home-base/src/main.ts +1 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-hmr/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-hmr/src/service/watch.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-locale/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-locale/src/config/config.ts +2 -4
- package/vona/src/suite-vendor/a-vona/modules/a-locale/src/main.ts +12 -30
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/bean/bean.openapi.ts +6 -2
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/service/openapi.ts +107 -48
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/contract.ts +25 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/const/decorator.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/decorator/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/decorator/mcp.ts +28 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/types/api.ts +8 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/src/config/config.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/service/rateLimit.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/types/rateLimit.ts +4 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/core/component/locale.ts +13 -3
- package/zova/packages-zova/zova-core/src/core/sys/config.ts +28 -1
- package/zova/packages-zova/zova-core/src/core/sys/locale.ts +24 -0
- package/zova/packages-zova/zova-core/src/core/sys/module.ts +16 -4
- package/zova/packages-zova/zova-core/src/core/sys/sys.ts +7 -2
- package/zova/packages-zova/zova-core/src/types/interface/module.ts +1 -0
- package/zova/packages-zova/zova-core/src/utils/zod-enhance.ts +1 -1
- package/zova/pnpm-lock.yaml +7 -7
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +1 -0
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +1 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.locale.tsx +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +13 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssrNavigation.ts +24 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +8 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/test/lib/ssrNavigation.test.ts +54 -1
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RouteConfig } from '@cabloy/zod-to-openapi';
|
|
1
2
|
import type {
|
|
2
3
|
OpenAPIObject as OpenAPIObject30,
|
|
3
4
|
SchemaObject as SchemaObject30,
|
|
@@ -36,6 +37,11 @@ import {
|
|
|
36
37
|
import { SymbolRequestMappingHandler } from 'vona-module-a-web';
|
|
37
38
|
import { z } from 'zod';
|
|
38
39
|
|
|
40
|
+
import type {
|
|
41
|
+
IControllerActionContract,
|
|
42
|
+
IControllerActionRequest,
|
|
43
|
+
IGenerateJsonOfControllerActionOptions,
|
|
44
|
+
} from '../types/contract.ts';
|
|
39
45
|
import type { RouteHandlerArgumentMetaDecorator } from '../types/decorator.ts';
|
|
40
46
|
|
|
41
47
|
const __ArgumentTypes = ['param', 'query', 'body', 'headers', 'fields', 'field', 'files', 'file'];
|
|
@@ -180,6 +186,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
180
186
|
moduleName: string,
|
|
181
187
|
controller: Constructable,
|
|
182
188
|
actionKey?: string,
|
|
189
|
+
options: IGenerateJsonOfControllerActionOptions = {},
|
|
183
190
|
) {
|
|
184
191
|
// info
|
|
185
192
|
const info = ModuleInfo.parseInfo(moduleName)!;
|
|
@@ -193,14 +200,21 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
193
200
|
SymbolOpenApiOptions,
|
|
194
201
|
controller,
|
|
195
202
|
);
|
|
196
|
-
if (controllerOpenApiOptions?.exclude) return;
|
|
203
|
+
if (controllerOpenApiOptions?.exclude && !options.includeExcluded) return;
|
|
197
204
|
// descs
|
|
198
205
|
const descs = Object.getOwnPropertyDescriptors(controller.prototype);
|
|
199
206
|
const actionKeys = actionKey ? [actionKey] : Object.keys(descs);
|
|
200
207
|
for (const actionKey of actionKeys) {
|
|
201
|
-
|
|
208
|
+
let desc = descs[actionKey];
|
|
209
|
+
if (actionKey && !desc) {
|
|
210
|
+
let prototype = Object.getPrototypeOf(controller.prototype);
|
|
211
|
+
while (prototype && !desc) {
|
|
212
|
+
desc = Object.getOwnPropertyDescriptor(prototype, actionKey)!;
|
|
213
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
202
216
|
if (['constructor'].includes(actionKey)) continue;
|
|
203
|
-
if (!desc
|
|
217
|
+
if (!desc?.value || typeof desc.value !== 'function') continue;
|
|
204
218
|
this._registerControllerAction(
|
|
205
219
|
registry,
|
|
206
220
|
info,
|
|
@@ -211,6 +225,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
211
225
|
controllerOpenApiOptions,
|
|
212
226
|
actionKey,
|
|
213
227
|
desc,
|
|
228
|
+
options,
|
|
214
229
|
);
|
|
215
230
|
}
|
|
216
231
|
}
|
|
@@ -225,6 +240,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
225
240
|
controllerOpenApiOptions: IOpenapiOptions | undefined,
|
|
226
241
|
actionKey: string,
|
|
227
242
|
_desc: PropertyDescriptor,
|
|
243
|
+
options: IGenerateJsonOfControllerActionOptions,
|
|
228
244
|
) {
|
|
229
245
|
// app
|
|
230
246
|
const app = this.app;
|
|
@@ -235,7 +251,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
235
251
|
controller.prototype,
|
|
236
252
|
actionKey,
|
|
237
253
|
);
|
|
238
|
-
if (actionOpenApiOptions?.exclude) return;
|
|
254
|
+
if (actionOpenApiOptions?.exclude && !options.includeExcluded) return;
|
|
239
255
|
|
|
240
256
|
// actionPath/actionMethod
|
|
241
257
|
if (!appMetadata.hasMetadata(SymbolRequestMappingHandler, controller.prototype, actionKey))
|
|
@@ -302,12 +318,71 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
302
318
|
});
|
|
303
319
|
}
|
|
304
320
|
|
|
321
|
+
/** Resolve validators in the current context; do not retain across locale changes. */
|
|
322
|
+
public getControllerActionContract(
|
|
323
|
+
controller: Constructable,
|
|
324
|
+
actionKey: string,
|
|
325
|
+
): IControllerActionContract {
|
|
326
|
+
const beanOptions = appResource.getBean(controller);
|
|
327
|
+
if (!beanOptions) throw new Error('invalid controller');
|
|
328
|
+
const info = ModuleInfo.parseInfo(beanOptions.module)!;
|
|
329
|
+
const actionOpenApiOptions = appMetadata.getMetadata<IOpenapiOptions>(
|
|
330
|
+
SymbolOpenApiOptions,
|
|
331
|
+
controller.prototype,
|
|
332
|
+
actionKey,
|
|
333
|
+
);
|
|
334
|
+
const controllerOpenApiOptions = appMetadata.getMetadata<IOpenapiOptions>(
|
|
335
|
+
SymbolOpenApiOptions,
|
|
336
|
+
controller,
|
|
337
|
+
);
|
|
338
|
+
const { request } = this._groupRequestSchemas(
|
|
339
|
+
info,
|
|
340
|
+
controller,
|
|
341
|
+
actionKey,
|
|
342
|
+
actionOpenApiOptions,
|
|
343
|
+
controllerOpenApiOptions,
|
|
344
|
+
);
|
|
345
|
+
return {
|
|
346
|
+
request,
|
|
347
|
+
response: this._getBodySchema(controller, actionKey, actionOpenApiOptions),
|
|
348
|
+
contentType: actionOpenApiOptions?.contentType || 'application/json',
|
|
349
|
+
openapiOptions: actionOpenApiOptions,
|
|
350
|
+
argsMeta: this._getArgsMeta(controller, actionKey) ?? [],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
305
354
|
private _collectRequest(
|
|
306
355
|
info: ModuleInfo.IModuleInfo,
|
|
307
356
|
controller: Constructable,
|
|
308
357
|
actionKey: string,
|
|
309
358
|
actionOpenApiOptions: IOpenapiOptions | undefined,
|
|
310
359
|
controllerOpenApiOptions: IOpenapiOptions | undefined,
|
|
360
|
+
) {
|
|
361
|
+
const { request: schemas, isUpload } = this._groupRequestSchemas(
|
|
362
|
+
info,
|
|
363
|
+
controller,
|
|
364
|
+
actionKey,
|
|
365
|
+
actionOpenApiOptions,
|
|
366
|
+
controllerOpenApiOptions,
|
|
367
|
+
);
|
|
368
|
+
const { body, ...request } = schemas;
|
|
369
|
+
if (!body) return request as RouteConfig['request'];
|
|
370
|
+
const contentType = isUpload ? 'multipart/form-data' : 'application/json';
|
|
371
|
+
return {
|
|
372
|
+
...request,
|
|
373
|
+
body: {
|
|
374
|
+
required: isUpload || !body.safeParse(undefined).success,
|
|
375
|
+
content: { [contentType]: { schema: body } },
|
|
376
|
+
},
|
|
377
|
+
} as RouteConfig['request'];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private _groupRequestSchemas(
|
|
381
|
+
info: ModuleInfo.IModuleInfo,
|
|
382
|
+
controller: Constructable,
|
|
383
|
+
actionKey: string,
|
|
384
|
+
actionOpenApiOptions: IOpenapiOptions | undefined,
|
|
385
|
+
controllerOpenApiOptions: IOpenapiOptions | undefined,
|
|
311
386
|
) {
|
|
312
387
|
// meta
|
|
313
388
|
const argsMeta = this._prepareArgsMeta(
|
|
@@ -316,12 +391,11 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
316
391
|
actionOpenApiOptions,
|
|
317
392
|
controllerOpenApiOptions,
|
|
318
393
|
);
|
|
319
|
-
if (!argsMeta) return;
|
|
320
394
|
// args
|
|
321
395
|
const argsMapWithField: any = {};
|
|
322
396
|
const argsMapIsolate: any = {};
|
|
323
397
|
let isUpload;
|
|
324
|
-
for (const argMeta of argsMeta) {
|
|
398
|
+
for (const argMeta of argsMeta ?? []) {
|
|
325
399
|
if (!argMeta) continue;
|
|
326
400
|
if (!__ArgumentTypes.includes(argMeta.type)) continue;
|
|
327
401
|
if (['fields', 'field', 'files', 'file'].includes(argMeta.type)) {
|
|
@@ -339,7 +413,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
339
413
|
}
|
|
340
414
|
}
|
|
341
415
|
// request
|
|
342
|
-
const request:
|
|
416
|
+
const request: IControllerActionRequest = {};
|
|
343
417
|
if (isUpload) {
|
|
344
418
|
const schemaObj: any = {};
|
|
345
419
|
// not check argsMapIsolate.fields
|
|
@@ -348,16 +422,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
348
422
|
if (argsMapWithField.files) Object.assign(schemaObj, argsMapWithField.files);
|
|
349
423
|
if (argsMapWithField.file) Object.assign(schemaObj, argsMapWithField.file);
|
|
350
424
|
if (argsMapIsolate.files) schemaObj.blobs = argsMapIsolate.files;
|
|
351
|
-
|
|
352
|
-
// body
|
|
353
|
-
request.body = {
|
|
354
|
-
required: true,
|
|
355
|
-
content: {
|
|
356
|
-
'multipart/form-data': {
|
|
357
|
-
schema,
|
|
358
|
-
},
|
|
359
|
-
},
|
|
360
|
-
};
|
|
425
|
+
request.body = z.object(schemaObj);
|
|
361
426
|
} else {
|
|
362
427
|
for (const argumentType of __ArgumentTypes) {
|
|
363
428
|
let schema: z.ZodType | undefined = argsMapIsolate[argumentType];
|
|
@@ -375,26 +440,12 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
375
440
|
`Invalid Openapi argument type: ${info.relativeName}:${controller.name}.${actionKey}#${argumentType}`,
|
|
376
441
|
);
|
|
377
442
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
// body
|
|
381
|
-
request.body = {
|
|
382
|
-
required: !schema.safeParse(undefined).success,
|
|
383
|
-
content: {
|
|
384
|
-
'application/json': {
|
|
385
|
-
schema,
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
};
|
|
389
|
-
} else {
|
|
390
|
-
// others
|
|
391
|
-
const name = argumentType === 'param' ? 'params' : argumentType;
|
|
392
|
-
request[name] = schema;
|
|
393
|
-
}
|
|
443
|
+
const name = argumentType === 'param' ? 'params' : argumentType;
|
|
444
|
+
request[name] = schema;
|
|
394
445
|
}
|
|
395
446
|
}
|
|
396
447
|
|
|
397
|
-
return request;
|
|
448
|
+
return { request, isUpload: !!isUpload };
|
|
398
449
|
}
|
|
399
450
|
|
|
400
451
|
private _collectResponses(
|
|
@@ -431,14 +482,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
431
482
|
actionOpenApiOptions: IOpenapiOptions | undefined,
|
|
432
483
|
contentType: string,
|
|
433
484
|
) {
|
|
434
|
-
|
|
435
|
-
let bodySchema: z.ZodType;
|
|
436
|
-
if (actionOpenApiOptions?.bodySchema) {
|
|
437
|
-
bodySchema = actionOpenApiOptions.bodySchema;
|
|
438
|
-
} else {
|
|
439
|
-
const metaType = appMetadata.getDesignReturntype(controller.prototype, actionKey);
|
|
440
|
-
bodySchema = $schema(metaType as any);
|
|
441
|
-
}
|
|
485
|
+
const bodySchema = this._getBodySchema(controller, actionKey, actionOpenApiOptions);
|
|
442
486
|
// wrapper
|
|
443
487
|
if (contentType !== 'application/json') return bodySchema;
|
|
444
488
|
if (actionOpenApiOptions?.bodySchemaWrapper === false) return bodySchema;
|
|
@@ -446,18 +490,33 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
446
490
|
return wrapper(bodySchema);
|
|
447
491
|
}
|
|
448
492
|
|
|
449
|
-
private
|
|
493
|
+
private _getBodySchema(
|
|
450
494
|
controller: Constructable,
|
|
451
495
|
actionKey: string,
|
|
452
496
|
actionOpenApiOptions: IOpenapiOptions | undefined,
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
497
|
+
): z.ZodType {
|
|
498
|
+
if (actionOpenApiOptions?.bodySchema) return actionOpenApiOptions.bodySchema;
|
|
499
|
+
const metaType = appMetadata.getDesignReturntype(controller.prototype, actionKey);
|
|
500
|
+
return $schema(metaType as any);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private _getArgsMeta(controller: Constructable, actionKey: string) {
|
|
504
|
+
const argsMeta = appMetadata.getMetadata<RouteHandlerArgumentMetaDecorator[]>(
|
|
457
505
|
SymbolRouteHandlersArgumentsMeta,
|
|
458
506
|
controller.prototype,
|
|
459
507
|
actionKey,
|
|
460
508
|
);
|
|
509
|
+
return argsMeta?.map(item => item && { ...item, pipes: item.pipes?.slice() });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private _prepareArgsMeta(
|
|
513
|
+
controller: Constructable,
|
|
514
|
+
actionKey: string,
|
|
515
|
+
actionOpenApiOptions: IOpenapiOptions | undefined,
|
|
516
|
+
controllerOpenApiOptions: IOpenapiOptions | undefined,
|
|
517
|
+
) {
|
|
518
|
+
// meta
|
|
519
|
+
let argsMeta = this._getArgsMeta(controller, actionKey);
|
|
461
520
|
// headers
|
|
462
521
|
const objHeaders = Object.assign(
|
|
463
522
|
{} as any,
|
|
@@ -467,7 +526,7 @@ export class ServiceOpenapi extends BeanBase {
|
|
|
467
526
|
if (isEmptyObject(objHeaders)) return argsMeta;
|
|
468
527
|
// merge
|
|
469
528
|
if (!argsMeta) argsMeta = [];
|
|
470
|
-
let argHeaders = argsMeta.find(item => item
|
|
529
|
+
let argHeaders = argsMeta.find(item => item?.type === 'headers' && !item.field);
|
|
471
530
|
if (!argHeaders) {
|
|
472
531
|
argHeaders = { type: 'headers', field: undefined, schema: z.object(objHeaders) } as any;
|
|
473
532
|
argsMeta.push(argHeaders!);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IOpenapiOptions, TypeResponseContentType } from 'vona-module-a-openapiutils';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import type { RouteHandlerArgumentMetaDecorator } from './decorator.ts';
|
|
5
|
+
|
|
6
|
+
export interface IControllerActionRequest {
|
|
7
|
+
params?: z.ZodType;
|
|
8
|
+
query?: z.ZodType;
|
|
9
|
+
body?: z.ZodType;
|
|
10
|
+
headers?: z.ZodType;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IControllerActionContract {
|
|
14
|
+
request: IControllerActionRequest;
|
|
15
|
+
/** The controller return validator, without the HTTP response wrapper. */
|
|
16
|
+
response: z.ZodType;
|
|
17
|
+
contentType: TypeResponseContentType;
|
|
18
|
+
openapiOptions?: IOpenapiOptions;
|
|
19
|
+
/** Copies of the original executor metadata, without synthetic documentation headers. */
|
|
20
|
+
argsMeta: RouteHandlerArgumentMetaDecorator[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IGenerateJsonOfControllerActionOptions {
|
|
24
|
+
includeExcluded?: boolean;
|
|
25
|
+
}
|
|
@@ -2,6 +2,7 @@ export const SymbolRouteHandlersArgumentsMeta = Symbol('SymbolRouteHandlersArgum
|
|
|
2
2
|
export const SymbolRouteHandlersArgumentsValue = Symbol('SymbolRouteHandlersArgumentsValue');
|
|
3
3
|
|
|
4
4
|
export const SymbolOpenApiOptions = Symbol('SymbolOpenApiOptions');
|
|
5
|
+
export const SymbolMcpOptions = Symbol('SymbolMcpOptions');
|
|
5
6
|
export const SymbolControllerResource = Symbol('SymbolControllerResource');
|
|
6
7
|
export const SymbolDecoratorRule = Symbol('SymbolDecoratorRule');
|
|
7
8
|
export const SymbolDecoratorDtoOpenapi = Symbol('SymbolDecoratorDtoOpenapi');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MetadataKey } from 'vona';
|
|
2
|
+
|
|
3
|
+
import { appMetadata } from 'vona';
|
|
4
|
+
|
|
5
|
+
import type { IMcpToolOptions } from '../../types/api.ts';
|
|
6
|
+
|
|
7
|
+
import { SymbolMcpOptions } from '../const/decorator.ts';
|
|
8
|
+
|
|
9
|
+
function tool(options?: Omit<IMcpToolOptions, 'name'>): ClassDecorator & MethodDecorator;
|
|
10
|
+
function tool(options: IMcpToolOptions): MethodDecorator;
|
|
11
|
+
function tool(options: IMcpToolOptions = {}): ClassDecorator & MethodDecorator {
|
|
12
|
+
return function (target: object, prop?: MetadataKey, descriptor?: PropertyDescriptor) {
|
|
13
|
+
if (prop === undefined && options.name !== undefined) {
|
|
14
|
+
throw new Error('mcp.tool name is only valid on a controller action');
|
|
15
|
+
}
|
|
16
|
+
// An options object, including an empty one, is the explicit opt-in marker.
|
|
17
|
+
const metadata = appMetadata.getOwnMetadataMap(
|
|
18
|
+
true,
|
|
19
|
+
SymbolMcpOptions,
|
|
20
|
+
target,
|
|
21
|
+
prop,
|
|
22
|
+
) as IMcpToolOptions;
|
|
23
|
+
Object.assign(metadata, options);
|
|
24
|
+
return descriptor;
|
|
25
|
+
} as ClassDecorator & MethodDecorator;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const mcp = { tool };
|
|
@@ -34,6 +34,14 @@ export interface IOpenapiSchemaObject {
|
|
|
34
34
|
|
|
35
35
|
export type TypeSecuritySchemes = Record<string, ((this: VonaApplication) => object) | object>;
|
|
36
36
|
|
|
37
|
+
export interface IMcpToolOptions {
|
|
38
|
+
/** A public tool name override, valid only on an action. */
|
|
39
|
+
name?: string;
|
|
40
|
+
description?: string | ILocaleMagic;
|
|
41
|
+
readOnly?: boolean;
|
|
42
|
+
output?: { pick: string[] };
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
export interface IOpenapiOptions {
|
|
38
46
|
public?: boolean;
|
|
39
47
|
description?: string | ILocaleMagic;
|
|
@@ -34,7 +34,9 @@ export class ServiceRateLimit extends BeanBase {
|
|
|
34
34
|
const identity = this._getIdentity(policy);
|
|
35
35
|
const identityHash = createHash('sha256').update(identity).digest('base64url');
|
|
36
36
|
const policyName = policy.name ?? `${route.controllerBeanFullName}:${String(route.action)}`;
|
|
37
|
-
const routeName =
|
|
37
|
+
const routeName = policy.route
|
|
38
|
+
? `${policy.route.method}:${policy.route.path}`
|
|
39
|
+
: `${route.routeMethod}:${route.routePathRaw}`;
|
|
38
40
|
const windowStart = Math.floor(now / policy.windowMs) * policy.windowMs;
|
|
39
41
|
const instanceName = this.ctx.instanceName ?? 'default';
|
|
40
42
|
return [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-jsx",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.95",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "Zova JSX",
|
|
6
6
|
"keywords": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@cabloy/word-utils": "^2.1.14",
|
|
51
51
|
"typestyle": "^2.4.0",
|
|
52
52
|
"vue": "^3.5.38",
|
|
53
|
-
"zova-core": "^5.1.
|
|
53
|
+
"zova-core": "^5.1.95"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"clean-package": "^2.2.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.177",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A vue3 framework with ioc",
|
|
6
6
|
"keywords": [
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"postpack": "clean-package restore"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"zova-core": "^5.1.
|
|
49
|
-
"zova-suite-a-zova": "^5.1.
|
|
48
|
+
"zova-core": "^5.1.95",
|
|
49
|
+
"zova-suite-a-zova": "^5.1.176"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"clean-package": "^2.2.0",
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
import { BeanSimple } from '../../bean/beanSimple.ts';
|
|
13
13
|
import { LocaleModuleNameSeparator } from '../../bean/resource/locale/type.ts';
|
|
14
14
|
import { cast } from '../../types/index.ts';
|
|
15
|
+
import { normalizeLocale } from '../sys/config.ts';
|
|
15
16
|
|
|
16
17
|
const SymbolLocaleCurrent = Symbol('SymbolLocaleCurrent');
|
|
17
18
|
const SymbolTzCurrent = Symbol('SymbolTzCurrent');
|
|
@@ -31,12 +32,11 @@ export class AppLocale extends BeanSimple {
|
|
|
31
32
|
if (!locale) {
|
|
32
33
|
locale = this.metaCookie.getItem(this.sys.config.locale.storeKey);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
return locale as keyof ILocaleRecord;
|
|
35
|
+
return this.normalize(locale);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
set current(value: keyof ILocaleRecord | '' | undefined) {
|
|
39
|
-
|
|
39
|
+
value = this.normalize(value);
|
|
40
40
|
if (this[SymbolLocaleCurrent].value === value) return;
|
|
41
41
|
this[SymbolLocaleCurrent].value = value;
|
|
42
42
|
if (this._cookieEnabled()) {
|
|
@@ -44,6 +44,16 @@ export class AppLocale extends BeanSimple {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
public normalize(value: unknown): keyof ILocaleRecord {
|
|
48
|
+
return normalizeLocale(value, this.sys.config.locale);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public getItems(items?: Partial<Record<keyof ILocaleRecord, string>>) {
|
|
52
|
+
const enabled = this.sys.config.locale.items;
|
|
53
|
+
if (!items) return enabled;
|
|
54
|
+
return Object.fromEntries(Object.entries(items).filter(([key]) => Object.hasOwn(enabled, key)));
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
private _cookieEnabled() {
|
|
48
58
|
return !process.env.SERVER || cast(this.ctx.meta).$ssr.profileOptions.useCookie;
|
|
49
59
|
}
|
|
@@ -87,7 +87,7 @@ export interface ZovaConfig {
|
|
|
87
87
|
locale: {
|
|
88
88
|
default: keyof ILocaleRecord;
|
|
89
89
|
storeKey: string;
|
|
90
|
-
items: Record<keyof ILocaleRecord, string
|
|
90
|
+
items: Partial<Record<keyof ILocaleRecord, string>>;
|
|
91
91
|
};
|
|
92
92
|
tz: {
|
|
93
93
|
storeKey: string;
|
|
@@ -105,3 +105,30 @@ export interface ZovaConfig {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export type ZovaConfigOptional = PowerPartial<ZovaConfig>;
|
|
108
|
+
|
|
109
|
+
export function validateLocaleConfig(locale: ZovaConfig['locale']) {
|
|
110
|
+
if (!locale?.items || Object.keys(locale.items).length === 0) {
|
|
111
|
+
throw new Error('config.locale.items must contain at least one enabled locale');
|
|
112
|
+
}
|
|
113
|
+
for (const [key, label] of Object.entries(locale.items)) {
|
|
114
|
+
if (
|
|
115
|
+
!key ||
|
|
116
|
+
key.trim().toLowerCase().replaceAll('_', '-') !== key ||
|
|
117
|
+
typeof label !== 'string'
|
|
118
|
+
) {
|
|
119
|
+
throw new Error(`config.locale.items contains an invalid locale: ${key}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (!Object.hasOwn(locale.items, locale.default)) {
|
|
123
|
+
throw new Error('config.locale.default must be an enabled own key of config.locale.items');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function normalizeLocale(value: unknown, locale: ZovaConfig['locale']): keyof ILocaleRecord {
|
|
128
|
+
validateLocaleConfig(locale);
|
|
129
|
+
const canonical =
|
|
130
|
+
typeof value === 'string' ? value.trim().toLowerCase().replaceAll('_', '-') : '';
|
|
131
|
+
return Object.hasOwn(locale.items, canonical)
|
|
132
|
+
? (canonical as keyof ILocaleRecord)
|
|
133
|
+
: locale.default;
|
|
134
|
+
}
|
|
@@ -36,6 +36,30 @@ export class SysLocale extends AppLocale {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** @internal */
|
|
40
|
+
public _registerProviderLocales(
|
|
41
|
+
locales: TypeModuleResourceLocales,
|
|
42
|
+
modules: Record<string, unknown>,
|
|
43
|
+
) {
|
|
44
|
+
if (!locales) return;
|
|
45
|
+
for (const [locale, resource] of Object.entries(locales)) {
|
|
46
|
+
const { modules: targets, ...globals } = resource as {
|
|
47
|
+
modules?: Record<string, Record<string, string>>;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
};
|
|
50
|
+
// Provider envelopes are data, never a message or a provider-owned scope.
|
|
51
|
+
const strings = Object.fromEntries(
|
|
52
|
+
Object.entries(globals).filter(([, value]) => typeof value === 'string'),
|
|
53
|
+
);
|
|
54
|
+
this.locales[locale] = Object.assign({}, strings, this.locales[locale]);
|
|
55
|
+
for (const [moduleName, dictionary] of Object.entries(targets ?? {})) {
|
|
56
|
+
// Membership, not installation: a translated target must remain lazy.
|
|
57
|
+
if (!Object.hasOwn(modules, moduleName)) continue;
|
|
58
|
+
this._registerLocale(moduleName, locale, dictionary);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
39
63
|
private _registerLocale(moduleName: string, locale: string, moduleLocales: object) {
|
|
40
64
|
// locales
|
|
41
65
|
this.locales[locale] = Object.assign({}, moduleLocales, this.locales[locale]);
|
|
@@ -29,6 +29,7 @@ export class SysModule extends BeanSimple {
|
|
|
29
29
|
public async initialize(modulesMeta: PluginZovaModulesMeta) {
|
|
30
30
|
this.modulesMeta = modulesMeta;
|
|
31
31
|
await this._loadAllMonkeysAndSyncsAndPreloads();
|
|
32
|
+
await this._requireAllSpecifics('locale');
|
|
32
33
|
await this._requireAllSpecifics('preload');
|
|
33
34
|
await this._requireAllSpecifics('monkey');
|
|
34
35
|
await this._requireAllSpecifics('sync');
|
|
@@ -123,7 +124,8 @@ export class SysModule extends BeanSimple {
|
|
|
123
124
|
process.env.SERVER ||
|
|
124
125
|
info.capabilities?.monkey ||
|
|
125
126
|
info.capabilities?.sync ||
|
|
126
|
-
info.capabilities?.preload
|
|
127
|
+
info.capabilities?.preload ||
|
|
128
|
+
info.capabilities?.locale;
|
|
127
129
|
if (shouldLoad) {
|
|
128
130
|
const moduleResource = module.resource as any;
|
|
129
131
|
if (typeof moduleResource === 'function') {
|
|
@@ -159,7 +161,7 @@ export class SysModule extends BeanSimple {
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
private async _requireAllSpecifics(capabilityName: 'preload' | 'monkey' | 'sync') {
|
|
164
|
+
private async _requireAllSpecifics(capabilityName: 'locale' | 'preload' | 'monkey' | 'sync') {
|
|
163
165
|
const modulesMeta = this._ensureModulesMeta();
|
|
164
166
|
const moduleNames = modulesMeta.moduleNames.filter(moduleName => {
|
|
165
167
|
const module = modulesMeta.modules[moduleName];
|
|
@@ -182,7 +184,10 @@ export class SysModule extends BeanSimple {
|
|
|
182
184
|
const module = modulesMeta.modules[moduleName];
|
|
183
185
|
const info = module.info;
|
|
184
186
|
const shouldInstall =
|
|
185
|
-
!info.capabilities?.
|
|
187
|
+
!info.capabilities?.locale &&
|
|
188
|
+
!info.capabilities?.monkey &&
|
|
189
|
+
!info.capabilities?.sync &&
|
|
190
|
+
!info.capabilities?.preload;
|
|
186
191
|
if (shouldInstall) {
|
|
187
192
|
await this._install(moduleName, module);
|
|
188
193
|
}
|
|
@@ -257,7 +262,14 @@ export class SysModule extends BeanSimple {
|
|
|
257
262
|
}
|
|
258
263
|
|
|
259
264
|
private _registerLocales(module: IModule) {
|
|
260
|
-
|
|
265
|
+
if (module.info.capabilities?.locale) {
|
|
266
|
+
this.sys.meta.locale._registerProviderLocales(
|
|
267
|
+
module.resource.localesProvider,
|
|
268
|
+
this._ensureModulesMeta().modules,
|
|
269
|
+
);
|
|
270
|
+
} else {
|
|
271
|
+
this.sys.meta.locale._registerLocales(module.info.relativeName, module.resource.locales);
|
|
272
|
+
}
|
|
261
273
|
}
|
|
262
274
|
|
|
263
275
|
private _registerConstants(module: IModule) {
|