vona-module-a-openapi 5.0.72 → 5.1.2
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/LICENSE +0 -0
- package/dist/.metadata/index.d.ts +1 -0
- package/dist/.metadata/index.d.ts.map +1 -0
- package/dist/.metadata/this.d.ts +1 -0
- package/dist/.metadata/this.d.ts.map +1 -0
- package/dist/bean/bean.openapi.d.ts +1 -0
- package/dist/bean/bean.openapi.d.ts.map +1 -0
- package/dist/bean/summerCache.json.d.ts +1 -0
- package/dist/bean/summerCache.json.d.ts.map +1 -0
- package/dist/config/config.d.ts +1 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/zod/index.d.ts +1 -0
- package/dist/lib/zod/index.d.ts.map +1 -0
- package/dist/lib/zod/schemaRefCustomAdapter.d.ts +1 -0
- package/dist/lib/zod/schemaRefCustomAdapter.d.ts.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/service/openapi.d.ts +1 -0
- package/dist/service/openapi.d.ts.map +1 -0
- package/dist/types/actions.d.ts +1 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/behavior.d.ts +1 -0
- package/dist/types/behavior.d.ts.map +1 -0
- package/dist/types/captcha.d.ts +3 -2
- package/dist/types/captcha.d.ts.map +1 -0
- package/dist/types/component.d.ts +1 -0
- package/dist/types/component.d.ts.map +1 -0
- package/dist/types/date.d.ts +1 -0
- package/dist/types/date.d.ts.map +1 -0
- package/dist/types/decorator.d.ts +1 -0
- package/dist/types/decorator.d.ts.map +1 -0
- package/dist/types/formProvider.d.ts +1 -0
- package/dist/types/formProvider.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/openapi.d.ts +1 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/permissions.d.ts +1 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/resource.d.ts +1 -0
- package/dist/types/resource.d.ts.map +1 -0
- package/dist/types/resourceMeta.d.ts +1 -0
- package/dist/types/resourceMeta.d.ts.map +1 -0
- package/dist/types/resourcePicker.d.ts +11 -0
- package/dist/types/resourcePicker.d.ts.map +1 -0
- package/dist/types/rest.d.ts +18 -7
- package/dist/types/rest.d.ts.map +1 -0
- package/dist/types/select.d.ts +16 -0
- package/dist/types/select.d.ts.map +1 -0
- package/dist/types/table.d.ts +23 -0
- package/dist/types/table.d.ts.map +1 -0
- package/dist/types/tableProvider.d.ts +1 -0
- package/dist/types/tableProvider.d.ts.map +1 -0
- package/dist/types/textarea.d.ts +11 -0
- package/dist/types/textarea.d.ts.map +1 -0
- package/dist/types/toggle.d.ts +4 -0
- package/dist/types/toggle.d.ts.map +1 -0
- package/package.json +33 -29
- package/src/.metadata/index.ts +139 -0
- package/src/.metadata/this.ts +2 -0
- package/src/bean/bean.openapi.ts +84 -0
- package/src/bean/summerCache.json.ts +17 -0
- package/src/config/config.ts +35 -0
- package/src/index.ts +3 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/zod/index.ts +1 -0
- package/src/lib/zod/schemaRefCustomAdapter.ts +29 -0
- package/src/main.ts +14 -0
- package/src/service/openapi.ts +400 -0
- package/src/types/actions.ts +18 -0
- package/src/types/behavior.ts +11 -0
- package/src/types/captcha.ts +5 -0
- package/src/types/component.ts +14 -0
- package/src/types/date.ts +25 -0
- package/src/types/decorator.ts +45 -0
- package/src/types/formProvider.ts +21 -0
- package/src/types/index.ts +20 -0
- package/src/types/openapi.ts +7 -0
- package/src/types/permissions.ts +18 -0
- package/src/types/resource.ts +1 -0
- package/src/types/resourceMeta.ts +38 -0
- package/src/types/resourcePicker.ts +11 -0
- package/src/types/rest.ts +133 -0
- package/src/types/select.ts +16 -0
- package/src/types/table.ts +27 -0
- package/src/types/tableProvider.ts +10 -0
- package/src/types/textarea.ts +10 -0
- package/src/types/toggle.ts +3 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import type { OpenAPIObject as OpenAPIObject30, SchemaObject as SchemaObject30, SecurityRequirementObject } from 'openapi3-ts/oas30';
|
|
2
|
+
import type { OpenAPIObject as OpenAPIObject31, SchemaObject as SchemaObject31 } from 'openapi3-ts/oas31';
|
|
3
|
+
import type { Constructable, IDecoratorBeanOptionsBase } from 'vona';
|
|
4
|
+
import type { IOpenapiHeader, IOpenapiOptions, TypeGenerateJsonScene } from 'vona-module-a-openapiutils';
|
|
5
|
+
import type { IDecoratorControllerOptions, RequestMappingMetadata, TypeRequestMethod } from 'vona-module-a-web';
|
|
6
|
+
|
|
7
|
+
import * as ModuleInfo from '@cabloy/module-info';
|
|
8
|
+
import { isEmptyObject, isNil } from '@cabloy/utils';
|
|
9
|
+
import { toUpperCaseFirstChar } from '@cabloy/word-utils';
|
|
10
|
+
import { translateError } from '@cabloy/zod-errors-custom';
|
|
11
|
+
import { getInnerTypeName } from '@cabloy/zod-query';
|
|
12
|
+
import { OpenAPIRegistry } from '@cabloy/zod-to-openapi';
|
|
13
|
+
import { appMetadata, appResource, BeanBase, cast } from 'vona';
|
|
14
|
+
import { Service } from 'vona-module-a-bean';
|
|
15
|
+
import { $schema, bodySchemaWrapperDefault, SymbolOpenApiOptions, SymbolRouteHandlersArgumentsMeta } from 'vona-module-a-openapiutils';
|
|
16
|
+
import { SymbolRequestMappingHandler } from 'vona-module-a-web';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
|
|
19
|
+
import type { RouteHandlerArgumentMetaDecorator } from '../types/decorator.ts';
|
|
20
|
+
|
|
21
|
+
const __ArgumentTypes = ['param', 'query', 'body', 'headers', 'fields', 'field', 'files', 'file'];
|
|
22
|
+
|
|
23
|
+
@Service()
|
|
24
|
+
export class ServiceOpenapi extends BeanBase {
|
|
25
|
+
public translate(apiObj: OpenAPIObject30 | OpenAPIObject31, generateJsonScene: TypeGenerateJsonScene) {
|
|
26
|
+
// paths
|
|
27
|
+
if (apiObj.paths) {
|
|
28
|
+
for (const key in apiObj.paths) {
|
|
29
|
+
const pathObj = apiObj.paths[key];
|
|
30
|
+
for (const method in pathObj) {
|
|
31
|
+
const methodObj = pathObj[method];
|
|
32
|
+
// parameters
|
|
33
|
+
for (const parameterObj of methodObj.parameters || []) {
|
|
34
|
+
if (parameterObj.schema) {
|
|
35
|
+
parameterObj.schema = this._translateSchema(parameterObj.schema, generateJsonScene);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// requestBody
|
|
39
|
+
if (methodObj.requestBody?.content?.['application/json']?.schema) {
|
|
40
|
+
methodObj.requestBody.content['application/json'].schema = this._translateSchema(
|
|
41
|
+
methodObj.requestBody.content['application/json'].schema,
|
|
42
|
+
generateJsonScene,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// components
|
|
49
|
+
if (apiObj.components?.schemas) {
|
|
50
|
+
for (const key in apiObj.components.schemas) {
|
|
51
|
+
const schema = apiObj.components.schemas[key];
|
|
52
|
+
if (schema) {
|
|
53
|
+
apiObj.components.schemas[key] = this._translateSchema(schema, generateJsonScene);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private _translateSchema(schema: any, generateJsonScene: TypeGenerateJsonScene) {
|
|
60
|
+
if (schema.type === 'object' && schema.required === undefined) schema.required = [];
|
|
61
|
+
// serializerTransforms
|
|
62
|
+
delete schema.serializerTransforms;
|
|
63
|
+
// filter
|
|
64
|
+
if (schema.filter) {
|
|
65
|
+
const filterCapabilities = schema.filter.capabilities;
|
|
66
|
+
if (filterCapabilities) {
|
|
67
|
+
schema.filter = { capabilities: filterCapabilities };
|
|
68
|
+
} else {
|
|
69
|
+
delete schema.filter;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// schema
|
|
73
|
+
if (generateJsonScene === 'api' && !schema.description && schema.title) {
|
|
74
|
+
schema.description = schema.title;
|
|
75
|
+
delete schema.title;
|
|
76
|
+
}
|
|
77
|
+
// errorMessage
|
|
78
|
+
this._translateErrorMessages(schema);
|
|
79
|
+
// properties
|
|
80
|
+
const properties = cast<SchemaObject30 | SchemaObject31>(schema).properties;
|
|
81
|
+
if (properties && typeof properties === 'object') {
|
|
82
|
+
for (const prop in properties) {
|
|
83
|
+
const propObj = properties[prop];
|
|
84
|
+
if (propObj) {
|
|
85
|
+
properties[prop] = this._translateSchema(propObj, generateJsonScene);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// items
|
|
90
|
+
const items = cast<SchemaObject30 | SchemaObject31>(schema).items;
|
|
91
|
+
if (items && typeof items === 'object') {
|
|
92
|
+
cast<SchemaObject30 | SchemaObject31>(schema).items = this._translateSchema(items, generateJsonScene);
|
|
93
|
+
}
|
|
94
|
+
// ok
|
|
95
|
+
return schema;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private _translateErrorMessages(obj: any) {
|
|
99
|
+
if (!obj.errorMessage) return;
|
|
100
|
+
for (const key in obj.errorMessage) {
|
|
101
|
+
let error = obj.errorMessage[key];
|
|
102
|
+
if (typeof error === 'function') {
|
|
103
|
+
error = error();
|
|
104
|
+
}
|
|
105
|
+
const scope = Object.assign({}, obj);
|
|
106
|
+
if (!isNil(obj.minLength)) scope.minimum = obj.minLength;
|
|
107
|
+
if (!isNil(obj.exclusiveMinimum)) scope.minimum = obj.exclusiveMinimum;
|
|
108
|
+
if (!isNil(obj.maxLength)) scope.maximum = obj.maxLength;
|
|
109
|
+
if (!isNil(obj.exclusiveMaximum)) scope.maximum = obj.exclusiveMaximum;
|
|
110
|
+
obj.errorMessage[key] = translateError(
|
|
111
|
+
(text: string, ...args: any[]) => {
|
|
112
|
+
return this.app.meta.text(text, ...args);
|
|
113
|
+
},
|
|
114
|
+
error,
|
|
115
|
+
scope,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public collectRegistry() {
|
|
121
|
+
const registry = new OpenAPIRegistry();
|
|
122
|
+
// securitySchemes
|
|
123
|
+
const configSecuritySchemes = this.scope.config.securitySchemes;
|
|
124
|
+
for (const key in configSecuritySchemes) {
|
|
125
|
+
let securityScheme = configSecuritySchemes[key];
|
|
126
|
+
if (typeof securityScheme === 'function') {
|
|
127
|
+
securityScheme = (securityScheme as any).call(this.app);
|
|
128
|
+
}
|
|
129
|
+
registry.registerComponent('securitySchemes', key, securityScheme as any);
|
|
130
|
+
}
|
|
131
|
+
// schema: independent
|
|
132
|
+
for (const sceneName of ['dto', 'entity']) {
|
|
133
|
+
const onionSlices = this.bean.onion[sceneName].getOnionsEnabledCached();
|
|
134
|
+
for (const onionSlice of onionSlices) {
|
|
135
|
+
if (onionSlice.beanOptions.options?.independent) {
|
|
136
|
+
const schema = $schema(onionSlice.beanOptions.beanClass);
|
|
137
|
+
registry.register(onionSlice.beanOptions.beanFullName, schema);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// controller
|
|
142
|
+
for (const controller of this.bean.onion.controller.getOnionsEnabledCached()) {
|
|
143
|
+
this.collectController(registry, controller.beanOptions.module, controller.beanOptions.beanClass);
|
|
144
|
+
}
|
|
145
|
+
return registry;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public collectController(registry: OpenAPIRegistry, moduleName: string, controller: Constructable, actionKey?: string) {
|
|
149
|
+
// info
|
|
150
|
+
const info = ModuleInfo.parseInfo(moduleName)!;
|
|
151
|
+
// controller options
|
|
152
|
+
const beanOptions = appResource.getBean(controller);
|
|
153
|
+
if (!beanOptions) return;
|
|
154
|
+
const controllerBeanFullName = beanOptions.beanFullName;
|
|
155
|
+
const controllerOptions = beanOptions.options as IDecoratorControllerOptions;
|
|
156
|
+
const controllerPath = controllerOptions.path;
|
|
157
|
+
const controllerOpenApiOptions = appMetadata.getMetadata<IOpenapiOptions>(SymbolOpenApiOptions, controller);
|
|
158
|
+
if (controllerOpenApiOptions?.exclude) return;
|
|
159
|
+
// descs
|
|
160
|
+
const descs = Object.getOwnPropertyDescriptors(controller.prototype);
|
|
161
|
+
const actionKeys = actionKey ? [actionKey] : Object.keys(descs);
|
|
162
|
+
for (const actionKey of actionKeys) {
|
|
163
|
+
const desc = descs[actionKey];
|
|
164
|
+
if (['constructor'].includes(actionKey)) continue;
|
|
165
|
+
if (!desc.value || typeof desc.value !== 'function') continue;
|
|
166
|
+
this._registerControllerAction(
|
|
167
|
+
registry,
|
|
168
|
+
info,
|
|
169
|
+
controller,
|
|
170
|
+
beanOptions,
|
|
171
|
+
controllerBeanFullName,
|
|
172
|
+
controllerPath,
|
|
173
|
+
controllerOpenApiOptions,
|
|
174
|
+
actionKey,
|
|
175
|
+
desc,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private _registerControllerAction(
|
|
181
|
+
registry: OpenAPIRegistry,
|
|
182
|
+
info: ModuleInfo.IModuleInfo,
|
|
183
|
+
controller: Constructable,
|
|
184
|
+
beanOptions: IDecoratorBeanOptionsBase,
|
|
185
|
+
_controllerBeanFullName: string,
|
|
186
|
+
controllerPath: string | undefined,
|
|
187
|
+
controllerOpenApiOptions: IOpenapiOptions | undefined,
|
|
188
|
+
actionKey: string,
|
|
189
|
+
_desc: PropertyDescriptor,
|
|
190
|
+
) {
|
|
191
|
+
// app
|
|
192
|
+
const app = this.app;
|
|
193
|
+
|
|
194
|
+
// action options: should not extend controllerOpenApiOptions
|
|
195
|
+
const actionOpenApiOptions = appMetadata.getMetadata<IOpenapiOptions>(SymbolOpenApiOptions, controller.prototype, actionKey);
|
|
196
|
+
if (actionOpenApiOptions?.exclude) return;
|
|
197
|
+
|
|
198
|
+
// actionPath/actionMethod
|
|
199
|
+
if (!appMetadata.hasMetadata(SymbolRequestMappingHandler, controller.prototype, actionKey)) return;
|
|
200
|
+
const handlerMetadata = appMetadata.getMetadata<RequestMappingMetadata>(SymbolRequestMappingHandler, controller.prototype, actionKey)!;
|
|
201
|
+
const actionPath: string = handlerMetadata.path || '';
|
|
202
|
+
const actionMethod: TypeRequestMethod = handlerMetadata.method || 'get';
|
|
203
|
+
|
|
204
|
+
// routePath
|
|
205
|
+
const routePath = app.util.combineApiPathControllerAndAction(info.relativeName, controllerPath, actionPath, true, true);
|
|
206
|
+
// :id -> {id}
|
|
207
|
+
const routePath2 = routePath.replace(/:([^/]+)/g, '{$1}');
|
|
208
|
+
|
|
209
|
+
// tags
|
|
210
|
+
let tags: string[] | undefined = actionOpenApiOptions?.tags ?? controllerOpenApiOptions?.tags;
|
|
211
|
+
if (!tags || tags.length === 0) {
|
|
212
|
+
tags = [toUpperCaseFirstChar(this.app.util.combineResourceName(beanOptions.name, info.relativeName, true, true))];
|
|
213
|
+
}
|
|
214
|
+
// operationId
|
|
215
|
+
let operationId = actionOpenApiOptions?.operationId ?? actionKey;
|
|
216
|
+
operationId = `${tags[0]}_${operationId}`;
|
|
217
|
+
// security
|
|
218
|
+
const _public: boolean | undefined = actionOpenApiOptions?.public ?? controllerOpenApiOptions?.public;
|
|
219
|
+
let security: SecurityRequirementObject[] | undefined;
|
|
220
|
+
if (!_public) {
|
|
221
|
+
security = [
|
|
222
|
+
{
|
|
223
|
+
bearerAuth: [],
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
// registerPath
|
|
228
|
+
registry.registerPath({
|
|
229
|
+
tags,
|
|
230
|
+
method: actionMethod,
|
|
231
|
+
path: routePath2,
|
|
232
|
+
operationId,
|
|
233
|
+
security,
|
|
234
|
+
description: actionOpenApiOptions?.description as string,
|
|
235
|
+
summary: actionOpenApiOptions?.summary as string,
|
|
236
|
+
request: this._collectRequest(info, controller, actionKey, actionOpenApiOptions, controllerOpenApiOptions),
|
|
237
|
+
responses: this._collectResponses(controller, actionKey, actionOpenApiOptions),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private _collectRequest(
|
|
242
|
+
info: ModuleInfo.IModuleInfo,
|
|
243
|
+
controller: Constructable,
|
|
244
|
+
actionKey: string,
|
|
245
|
+
actionOpenApiOptions: IOpenapiOptions | undefined,
|
|
246
|
+
controllerOpenApiOptions: IOpenapiOptions | undefined,
|
|
247
|
+
) {
|
|
248
|
+
// meta
|
|
249
|
+
const argsMeta = this._prepareArgsMeta(controller, actionKey, actionOpenApiOptions, controllerOpenApiOptions);
|
|
250
|
+
if (!argsMeta) return;
|
|
251
|
+
// args
|
|
252
|
+
const argsMapWithField: any = {};
|
|
253
|
+
const argsMapIsolate: any = {};
|
|
254
|
+
let isUpload;
|
|
255
|
+
for (const argMeta of argsMeta) {
|
|
256
|
+
if (!argMeta) continue;
|
|
257
|
+
if (!__ArgumentTypes.includes(argMeta.type)) continue;
|
|
258
|
+
if (['fields', 'field', 'files', 'file'].includes(argMeta.type)) {
|
|
259
|
+
isUpload = true;
|
|
260
|
+
}
|
|
261
|
+
if (argMeta.field) {
|
|
262
|
+
if (!argsMapWithField[argMeta.type]) {
|
|
263
|
+
argsMapWithField[argMeta.type] = {};
|
|
264
|
+
}
|
|
265
|
+
argsMapWithField[argMeta.type][argMeta.field] = argMeta.schema;
|
|
266
|
+
} else {
|
|
267
|
+
if (!argsMapIsolate[argMeta.type]) {
|
|
268
|
+
argsMapIsolate[argMeta.type] = argMeta.schema;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// request
|
|
273
|
+
const request: any = {};
|
|
274
|
+
if (isUpload) {
|
|
275
|
+
const schemaObj: any = {};
|
|
276
|
+
// not check argsMapIsolate.fields
|
|
277
|
+
if (argsMapWithField.fields) Object.assign(schemaObj, argsMapWithField.fields);
|
|
278
|
+
if (argsMapWithField.field) Object.assign(schemaObj, argsMapWithField.field);
|
|
279
|
+
if (argsMapWithField.files) Object.assign(schemaObj, argsMapWithField.files);
|
|
280
|
+
if (argsMapWithField.file) Object.assign(schemaObj, argsMapWithField.file);
|
|
281
|
+
if (argsMapIsolate.files) schemaObj.blobs = argsMapIsolate.files;
|
|
282
|
+
const schema = z.object(schemaObj);
|
|
283
|
+
// body
|
|
284
|
+
request.body = {
|
|
285
|
+
required: true,
|
|
286
|
+
content: {
|
|
287
|
+
'multipart/form-data': {
|
|
288
|
+
schema,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
} else {
|
|
293
|
+
for (const argumentType of __ArgumentTypes) {
|
|
294
|
+
let schema: z.ZodType | undefined = argsMapIsolate[argumentType];
|
|
295
|
+
if (argsMapWithField[argumentType]) {
|
|
296
|
+
if (!schema) {
|
|
297
|
+
schema = z.object(argsMapWithField[argumentType]);
|
|
298
|
+
} else {
|
|
299
|
+
schema = (schema as any).extend(argsMapWithField[argumentType]);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!schema) continue;
|
|
303
|
+
// check schema
|
|
304
|
+
if (getInnerTypeName(schema) === 'any') {
|
|
305
|
+
throw new Error(`Invalid Openapi argument type: ${info.relativeName}:${controller.name}.${actionKey}#${argumentType}`);
|
|
306
|
+
}
|
|
307
|
+
// record
|
|
308
|
+
if (argumentType === 'body') {
|
|
309
|
+
// body
|
|
310
|
+
request.body = {
|
|
311
|
+
required: !schema.safeParse(undefined).success,
|
|
312
|
+
content: {
|
|
313
|
+
'application/json': {
|
|
314
|
+
schema,
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
} else {
|
|
319
|
+
// others
|
|
320
|
+
const name = argumentType === 'param' ? 'params' : argumentType;
|
|
321
|
+
request[name] = schema;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return request;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private _collectResponses(controller: Constructable, actionKey: string, actionOpenApiOptions: IOpenapiOptions | undefined) {
|
|
330
|
+
// contentType
|
|
331
|
+
const contentType = actionOpenApiOptions?.contentType || 'application/json';
|
|
332
|
+
// body schema
|
|
333
|
+
const bodySchema = this._parseBodySchema(controller, actionKey, actionOpenApiOptions, contentType);
|
|
334
|
+
// response
|
|
335
|
+
const response = {
|
|
336
|
+
description: '',
|
|
337
|
+
content: {
|
|
338
|
+
[contentType]: {
|
|
339
|
+
schema: bodySchema,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
// responses
|
|
344
|
+
const responses = { 200: response };
|
|
345
|
+
return responses;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private _parseBodySchema(controller: Constructable, actionKey: string, actionOpenApiOptions: IOpenapiOptions | undefined, contentType: string) {
|
|
349
|
+
// bodySchema
|
|
350
|
+
let bodySchema: z.ZodType;
|
|
351
|
+
if (actionOpenApiOptions?.bodySchema) {
|
|
352
|
+
bodySchema = actionOpenApiOptions.bodySchema;
|
|
353
|
+
} else {
|
|
354
|
+
const metaType = appMetadata.getDesignReturntype(controller.prototype, actionKey);
|
|
355
|
+
bodySchema = $schema(metaType as any);
|
|
356
|
+
}
|
|
357
|
+
// wrapper
|
|
358
|
+
if (contentType !== 'application/json') return bodySchema;
|
|
359
|
+
if (actionOpenApiOptions?.bodySchemaWrapper === false) return bodySchema;
|
|
360
|
+
const wrapper = actionOpenApiOptions?.bodySchemaWrapper ?? bodySchemaWrapperDefault;
|
|
361
|
+
return wrapper(bodySchema);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private _prepareArgsMeta(
|
|
365
|
+
controller: Constructable,
|
|
366
|
+
actionKey: string,
|
|
367
|
+
actionOpenApiOptions: IOpenapiOptions | undefined,
|
|
368
|
+
controllerOpenApiOptions: IOpenapiOptions | undefined,
|
|
369
|
+
) {
|
|
370
|
+
// meta
|
|
371
|
+
let argsMeta = appMetadata.getMetadata<RouteHandlerArgumentMetaDecorator[]>(SymbolRouteHandlersArgumentsMeta, controller.prototype, actionKey);
|
|
372
|
+
// headers
|
|
373
|
+
const objHeaders = Object.assign(
|
|
374
|
+
{} as any,
|
|
375
|
+
this._combineArgHeaders(controllerOpenApiOptions?.headers),
|
|
376
|
+
this._combineArgHeaders(actionOpenApiOptions?.headers),
|
|
377
|
+
);
|
|
378
|
+
if (isEmptyObject(objHeaders)) return argsMeta;
|
|
379
|
+
// merge
|
|
380
|
+
if (!argsMeta) argsMeta = [];
|
|
381
|
+
let argHeaders = argsMeta.find(item => item.type === 'headers' && !item.field);
|
|
382
|
+
if (!argHeaders) {
|
|
383
|
+
argHeaders = { type: 'headers', field: undefined, schema: z.object(objHeaders) } as any;
|
|
384
|
+
argsMeta.push(argHeaders!);
|
|
385
|
+
} else {
|
|
386
|
+
if (!(argHeaders.schema as any).extend) throw new Error(`headers schema is not valid: ${actionKey}`);
|
|
387
|
+
argHeaders.schema = (argHeaders.schema as any).extend(objHeaders);
|
|
388
|
+
}
|
|
389
|
+
return argsMeta;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private _combineArgHeaders(headers?: IOpenapiHeader[] | undefined) {
|
|
393
|
+
if (!headers) return;
|
|
394
|
+
const objHeaders = {};
|
|
395
|
+
for (const header of headers) {
|
|
396
|
+
objHeaders[header.name] = z.string().openapi({ description: header.description });
|
|
397
|
+
}
|
|
398
|
+
return objHeaders;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface IResourceActionTableOptions {}
|
|
2
|
+
export interface IResourceActionRowOptions {}
|
|
3
|
+
|
|
4
|
+
export interface TypeResourceActionTableRecord {
|
|
5
|
+
create: IResourceActionTableOptions;
|
|
6
|
+
operationsTable: IResourceActionTableOptions;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TypeResourceActionRowRecord {
|
|
10
|
+
view: IResourceActionRowOptions;
|
|
11
|
+
update: IResourceActionRowOptions;
|
|
12
|
+
delete: IResourceActionRowOptions;
|
|
13
|
+
operationsRow: IResourceActionRowOptions;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TypeResourceActionRowRecordRender = {
|
|
17
|
+
[key in keyof TypeResourceActionRowRecord as `action${Capitalize<key>}`]: TypeResourceActionRowRecord[key];
|
|
18
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface IBehaviorRecord {}
|
|
2
|
+
|
|
3
|
+
export type TypeBehaviorRecordSelector<PREFIX extends string> = {
|
|
4
|
+
[K in keyof IBehaviorRecord as K extends `${string}:${PREFIX}${string}` ? K : never]: IBehaviorRecord[K];
|
|
5
|
+
};
|
|
6
|
+
export type TypeBehaviorRecordSelectorKeys<PREFIX extends string> = keyof TypeBehaviorRecordSelector<PREFIX>;
|
|
7
|
+
|
|
8
|
+
export type TypeBehaviorRecordSelectorStrict<PREFIX extends string> = {
|
|
9
|
+
[K in keyof IBehaviorRecord as K extends `${string}:${PREFIX}` ? K : never]: IBehaviorRecord[K];
|
|
10
|
+
};
|
|
11
|
+
export type TypeBehaviorRecordSelectorKeysStrict<PREFIX extends string> = keyof TypeBehaviorRecordSelectorStrict<PREFIX>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface IComponentRecord {}
|
|
2
|
+
export interface ITableCellComponentRecord {}
|
|
3
|
+
|
|
4
|
+
export type TypeComponentRecordSelector<PREFIX extends string> = {
|
|
5
|
+
[K in keyof IComponentRecord as K extends `${string}:${PREFIX}${string}` ? K : never]: IComponentRecord[K];
|
|
6
|
+
};
|
|
7
|
+
export type TypeComponentRecordSelectorKeys<PREFIX extends string> = keyof TypeComponentRecordSelector<PREFIX>;
|
|
8
|
+
|
|
9
|
+
export type TypeComponentRecordSelectorStrict<PREFIX extends string> = {
|
|
10
|
+
[K in keyof IComponentRecord as K extends `${string}:${PREFIX}` ? K : never]: IComponentRecord[K];
|
|
11
|
+
};
|
|
12
|
+
export type TypeComponentRecordSelectorKeysStrict<PREFIX extends string> = keyof TypeComponentRecordSelectorStrict<PREFIX>;
|
|
13
|
+
|
|
14
|
+
export type TypeComponentLayoutRecord = TypeComponentRecordSelector<'layout'>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type TypeDateFormatPreset =
|
|
2
|
+
| 'DATE_SHORT'
|
|
3
|
+
| 'DATE_MED'
|
|
4
|
+
| 'DATE_MED_WITH_WEEKDAY'
|
|
5
|
+
| 'DATE_FULL'
|
|
6
|
+
| 'DATE_HUGE'
|
|
7
|
+
| 'TIME_SIMPLE'
|
|
8
|
+
| 'TIME_WITH_SECONDS'
|
|
9
|
+
| 'TIME_WITH_SHORT_OFFSET'
|
|
10
|
+
| 'TIME_WITH_LONG_OFFSET'
|
|
11
|
+
| 'TIME_24_SIMPLE'
|
|
12
|
+
| 'TIME_24_WITH_SECONDS'
|
|
13
|
+
| 'TIME_24_WITH_SHORT_OFFSET'
|
|
14
|
+
| 'TIME_24_WITH_LONG_OFFSET'
|
|
15
|
+
| 'DATETIME_SHORT'
|
|
16
|
+
| 'DATETIME_MED'
|
|
17
|
+
| 'DATETIME_MED_WITH_WEEKDAY'
|
|
18
|
+
| 'DATETIME_FULL'
|
|
19
|
+
| 'DATETIME_HUGE'
|
|
20
|
+
| 'DATETIME_SHORT_WITH_SECONDS'
|
|
21
|
+
| 'DATETIME_MED_WITH_SECONDS'
|
|
22
|
+
| 'DATETIME_FULL_WITH_SECONDS'
|
|
23
|
+
| 'DATETIME_HUGE_WITH_SECONDS';
|
|
24
|
+
|
|
25
|
+
export type TypeDateFormat = { preset: TypeDateFormatPreset } | string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Constructable, Type, VonaContext } from 'vona';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export interface ISchemaObjectOptions {
|
|
5
|
+
loose?: boolean;
|
|
6
|
+
strict?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RouteHandlerArgumentMetaDecorator {
|
|
10
|
+
index: number;
|
|
11
|
+
type: RouteHandlerArgumentType;
|
|
12
|
+
field?: string;
|
|
13
|
+
pipes: Function[];
|
|
14
|
+
schema: z.ZodType;
|
|
15
|
+
extractValue?: TypeExtractValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RouteHandlerArgumentMeta {
|
|
19
|
+
type: RouteHandlerArgumentType;
|
|
20
|
+
field?: string;
|
|
21
|
+
metaType?: Type<any>;
|
|
22
|
+
controller: Constructable;
|
|
23
|
+
method: string;
|
|
24
|
+
index: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type RouteHandlerArgumentType =
|
|
28
|
+
| 'request'
|
|
29
|
+
| 'response'
|
|
30
|
+
| 'body'
|
|
31
|
+
| 'query'
|
|
32
|
+
| 'param'
|
|
33
|
+
| 'headers'
|
|
34
|
+
| 'session'
|
|
35
|
+
| 'fields'
|
|
36
|
+
| 'field'
|
|
37
|
+
| 'files'
|
|
38
|
+
| 'file'
|
|
39
|
+
| 'host'
|
|
40
|
+
| 'ip'
|
|
41
|
+
| 'rawBody'
|
|
42
|
+
| 'string'
|
|
43
|
+
| 'user';
|
|
44
|
+
|
|
45
|
+
export type TypeExtractValue = (ctx: VonaContext, argMeta: RouteHandlerArgumentMetaDecorator) => Promise<any>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TypeBehaviorRecordSelectorKeys, TypeBehaviorRecordSelectorKeysStrict } from './behavior.ts';
|
|
2
|
+
import type { TypeComponentRecordSelectorKeysStrict } from './component.ts';
|
|
3
|
+
import type { TypeFormFieldRenderComponentProvider } from './rest.ts';
|
|
4
|
+
|
|
5
|
+
export interface IFormProviderBehaviors {
|
|
6
|
+
formField?: TypeBehaviorRecordSelectorKeysStrict<'formField'>;
|
|
7
|
+
formFieldModel?: TypeBehaviorRecordSelectorKeys<'formFieldModel'>;
|
|
8
|
+
formFieldLayout?: TypeBehaviorRecordSelectorKeys<'formFieldLayout'>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IFormProviderComponents {
|
|
12
|
+
formField?: TypeComponentRecordSelectorKeysStrict<'formField'>;
|
|
13
|
+
text?: TypeFormFieldRenderComponentProvider;
|
|
14
|
+
password?: TypeFormFieldRenderComponentProvider;
|
|
15
|
+
currency?: TypeFormFieldRenderComponentProvider;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IFormProvider {
|
|
19
|
+
components?: IFormProviderComponents;
|
|
20
|
+
behaviors?: IFormProviderBehaviors;
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import '@cabloy/zod-query';
|
|
2
|
+
|
|
3
|
+
export * from './actions.ts';
|
|
4
|
+
export * from './behavior.ts';
|
|
5
|
+
export * from './captcha.ts';
|
|
6
|
+
export * from './component.ts';
|
|
7
|
+
export * from './date.ts';
|
|
8
|
+
export * from './decorator.ts';
|
|
9
|
+
export * from './formProvider.ts';
|
|
10
|
+
export * from './openapi.ts';
|
|
11
|
+
export * from './permissions.ts';
|
|
12
|
+
export * from './resource.ts';
|
|
13
|
+
export * from './resourceMeta.ts';
|
|
14
|
+
export * from './resourcePicker.ts';
|
|
15
|
+
export * from './rest.ts';
|
|
16
|
+
export * from './select.ts';
|
|
17
|
+
export * from './table.ts';
|
|
18
|
+
export * from './tableProvider.ts';
|
|
19
|
+
export * from './textarea.ts';
|
|
20
|
+
export * from './toggle.ts';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IRoleIdRecord, IRoleNameRecord } from 'vona-module-a-user';
|
|
2
|
+
|
|
3
|
+
export interface IOpenapiActionRecord {
|
|
4
|
+
create: boolean;
|
|
5
|
+
view: boolean;
|
|
6
|
+
update: boolean;
|
|
7
|
+
delete: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type IOpenapiPermissionModeActionActions = {
|
|
11
|
+
[K in keyof IOpenapiActionRecord]?: IOpenapiActionRecord[K];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface IOpenapiPermissions {
|
|
15
|
+
roleIds?: (keyof IRoleIdRecord)[];
|
|
16
|
+
roleNames?: (keyof IRoleNameRecord)[];
|
|
17
|
+
actions?: IOpenapiPermissionModeActionActions;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export interface IResourceRecord {}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { IOpenapiPermissions, TypeComponentRecordSelectorKeysStrict, TypeRenderComponentJsx } from 'vona-module-a-openapi';
|
|
2
|
+
|
|
3
|
+
import type { IFormProvider } from './formProvider.ts';
|
|
4
|
+
import type { ITableProvider } from './tableProvider.ts';
|
|
5
|
+
|
|
6
|
+
export type TypeOpenapiPermissions = IOpenapiPermissions | boolean;
|
|
7
|
+
|
|
8
|
+
export interface IOpenapiOptionsResourceMeta {
|
|
9
|
+
/**
|
|
10
|
+
* false: disallow
|
|
11
|
+
* true: public
|
|
12
|
+
* undefined: by api
|
|
13
|
+
* IOpenapiPermissions: specific
|
|
14
|
+
*/
|
|
15
|
+
permissions?: TypeOpenapiPermissions;
|
|
16
|
+
provider?: IOpenapiOptionsResourceMetaProvider;
|
|
17
|
+
form?: IOpenapiOptionsResourceMetaForm;
|
|
18
|
+
table?: IOpenapiOptionsResourceMetaTable;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IOpenapiOptionsResourceMetaForm {
|
|
22
|
+
provider?: IFormProvider;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IOpenapiOptionsResourceMetaTable {
|
|
26
|
+
provider?: ITableProvider;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IOpenapiOptionsResourceMetaProvider {
|
|
30
|
+
components?: IOpenapiOptionsResourceMetaProviderComponents;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IOpenapiOptionsResourceMetaProviderComponents {
|
|
34
|
+
restPage?: TypeComponentRecordSelectorKeysStrict<'restPage'> | TypeRenderComponentJsx;
|
|
35
|
+
restPageEntry?: TypeComponentRecordSelectorKeysStrict<'restPageEntry'> | TypeRenderComponentJsx;
|
|
36
|
+
table?: TypeComponentRecordSelectorKeysStrict<'table'>;
|
|
37
|
+
form?: TypeComponentRecordSelectorKeysStrict<'form'>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IResourceRecord } from './resource.ts';
|
|
2
|
+
import type { ISelectOptions } from './select.ts';
|
|
3
|
+
import type { ITableQuery } from './table.ts';
|
|
4
|
+
|
|
5
|
+
export interface IResourcePickerOptions {
|
|
6
|
+
resource?: keyof IResourceRecord;
|
|
7
|
+
actionPath?: string;
|
|
8
|
+
query?: ITableQuery;
|
|
9
|
+
selectOptions?: ISelectOptions;
|
|
10
|
+
relation?: string;
|
|
11
|
+
}
|