okai 0.0.23 → 0.0.25
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/api.d.ts +16 -5
- package/dist/cs-apis.js +6 -9
- package/dist/cs-ast.js +432 -306
- package/dist/cs-gen.js +15 -9
- package/dist/cs-migrations.js +14 -5
- package/dist/index.js +80 -38
- package/dist/info.js +76 -0
- package/dist/ts-ast.js +32 -1
- package/dist/ts-once.js +240 -0
- package/dist/ts-parser.js +36 -27
- package/dist/tsd-gen.js +18 -81
- package/dist/utils.js +26 -1
- package/package.json +1 -1
package/dist/cs-ast.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { leftPart, plural, rightPart, toPascalCase } from "./utils.js";
|
1
|
+
import { getGroupName, leftPart, plural, rightPart, splitCase, toPascalCase } from "./utils.js";
|
2
2
|
import { Icons } from "./icons.js";
|
3
3
|
const sys = (name, genericArgs) => ({ name, namespace: "System", genericArgs });
|
4
4
|
const sysObj = sys("object");
|
@@ -15,6 +15,25 @@ export class CSharpAst {
|
|
15
15
|
"undefined": sys("string"),
|
16
16
|
"bigint": sys("long"),
|
17
17
|
"any": sysObj,
|
18
|
+
"ArrayBuffer": sys("byte[]"),
|
19
|
+
"SharedArrayBuffer": sys("byte[]"),
|
20
|
+
"Int8Array": sys("sbyte[]"),
|
21
|
+
"UInt8Array": sys("byte[]"),
|
22
|
+
"Uint8ClampedArray": sys("byte[]"),
|
23
|
+
"Int16Array": sys("short[]"),
|
24
|
+
"UInt16Array": sys("ushort[]"),
|
25
|
+
"Int32Array": sys("int[]"),
|
26
|
+
"UInt32Array": sys("uint[]"),
|
27
|
+
"Float16Array": sys("float[]"),
|
28
|
+
"Float32Array": sys("float[]"),
|
29
|
+
"Float64Array": sys("double[]"),
|
30
|
+
"BigInt64Array": sys("long[]"),
|
31
|
+
"BigUInt64Array": sys("long[]"),
|
32
|
+
"Array": { name: "List", genericArgs: [], namespace: "System.Collections.Generic" },
|
33
|
+
"Map": { name: "Dictionary", genericArgs: [], namespace: "System.Collections.Generic" },
|
34
|
+
"WeakMap": { name: "Dictionary", genericArgs: [], namespace: "System.Collections.Generic" },
|
35
|
+
"Set": { name: "HashSet", genericArgs: [], namespace: "System.Collections.Generic" },
|
36
|
+
"WeakSet": { name: "HashSet", genericArgs: [], namespace: "System.Collections.Generic" },
|
18
37
|
};
|
19
38
|
decimalTypeProps = [
|
20
39
|
"price", "cost", "amount", "total", "salary", "balance", "rate", "discount", "tax", "fee"
|
@@ -84,9 +103,9 @@ export class CSharpAst {
|
|
84
103
|
'validateExactLength',
|
85
104
|
'validateMinimumLength',
|
86
105
|
'validateMaximumLength',
|
87
|
-
'
|
106
|
+
'validateLessThan',
|
88
107
|
'validateLessThanOrEqual',
|
89
|
-
'
|
108
|
+
'validateGreaterThan',
|
90
109
|
'validateGreaterThanOrEqual',
|
91
110
|
'validateScalePrecision',
|
92
111
|
'validateRegularExpression',
|
@@ -110,6 +129,9 @@ export class CSharpAst {
|
|
110
129
|
'fieldCss',
|
111
130
|
'uploadTo',
|
112
131
|
].map(x => x.toLowerCase());
|
132
|
+
get requestPropAttrsWithoutValidators() {
|
133
|
+
return this.requestPropAttrs.filter(x => !x.startsWith('validate'));
|
134
|
+
}
|
113
135
|
modelPropAttrs = [
|
114
136
|
'alias',
|
115
137
|
'meta',
|
@@ -157,24 +179,36 @@ export class CSharpAst {
|
|
157
179
|
'intlDateTime',
|
158
180
|
'intlRelativeTime',
|
159
181
|
].map(x => x.toLowerCase());
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
182
|
+
// Ignore properties with these attributes on APIs
|
183
|
+
ignoreCreateProps = [
|
184
|
+
'autoIncrement',
|
185
|
+
'references',
|
186
|
+
'compute',
|
187
|
+
'computed',
|
188
|
+
].map(x => x.toLowerCase());
|
189
|
+
ignoreUpdateProps = [
|
190
|
+
'references',
|
191
|
+
'compute',
|
192
|
+
'computed',
|
193
|
+
].map(x => x.toLowerCase());
|
194
|
+
// Validators that should be on Create but not optional Update APIs
|
195
|
+
ignoreUpdateValidators = [
|
196
|
+
'validateNull',
|
197
|
+
'validateNotNull',
|
198
|
+
'validateEmpty',
|
199
|
+
'validateNotEmpty',
|
200
|
+
].map(x => x.toLowerCase());
|
201
|
+
ignoreReadValidators = this.requestPropAttrs.filter(x => x.startsWith('validate')).map(x => x.toLowerCase());
|
202
|
+
ignoreDeleteValidators = this.requestPropAttrs.filter(x => x.startsWith('validate')).map(x => x.toLowerCase());
|
169
203
|
isEnum(type) {
|
170
|
-
type =
|
204
|
+
type = unwrap(type);
|
171
205
|
return this.ast.enums.some(x => x.name === type) || this.result.types.find(x => x.name === type && x.isEnum);
|
172
206
|
}
|
173
207
|
isValueType(type) {
|
174
|
-
type =
|
208
|
+
type = unwrap(type);
|
175
209
|
return this.valueTypes.includes(type) || this.isEnum(type);
|
176
210
|
}
|
177
|
-
ast = { classes: [], interfaces: [], enums: [] };
|
211
|
+
ast = { references: [], classes: [], interfaces: [], enums: [] };
|
178
212
|
result = {
|
179
213
|
config: {},
|
180
214
|
namespaces: [],
|
@@ -189,6 +223,13 @@ export class CSharpAst {
|
|
189
223
|
const elType = this.csharpType(type.substring(0, type.length - 2), propName);
|
190
224
|
return Object.assign({}, elType, { name: elType.name + '[]' });
|
191
225
|
}
|
226
|
+
if (type.endsWith('>')) {
|
227
|
+
const start = type.indexOf('<');
|
228
|
+
const genericArgs = type.substring(start + 1, type.length - 1).split(',').map(x => this.csharpType(x.trim()).name);
|
229
|
+
const baseType = this.csharpType(type.substring(0, start), propName);
|
230
|
+
const ret = Object.assign({}, baseType, { genericArgs });
|
231
|
+
return ret;
|
232
|
+
}
|
192
233
|
if (propName) {
|
193
234
|
if (type === "number") {
|
194
235
|
if (this.decimalTypeProps.some(x => propName.toLowerCase().includes(x))) {
|
@@ -245,16 +286,20 @@ export class CSharpAst {
|
|
245
286
|
const type = this.csharpType(p.type, p.name);
|
246
287
|
const prop = {
|
247
288
|
name: this.toCsName(p.name),
|
248
|
-
type: p.optional ?
|
289
|
+
type: p.optional ? nullable(type.name) : type.name,
|
249
290
|
};
|
250
291
|
if (type.namespace) {
|
251
292
|
prop.namespace = type.namespace;
|
252
293
|
}
|
294
|
+
if (type.genericArgs) {
|
295
|
+
prop.genericArgs = type.genericArgs;
|
296
|
+
}
|
253
297
|
if (p.comment) {
|
254
298
|
prop.description = p.comment;
|
255
299
|
}
|
256
300
|
if (prop.name === 'Id') {
|
257
301
|
prop.isPrimaryKey = true;
|
302
|
+
prop.isRequired = true;
|
258
303
|
}
|
259
304
|
const valueType = this.isValueType(prop.type);
|
260
305
|
if (valueType) {
|
@@ -341,202 +386,36 @@ export class CSharpAst {
|
|
341
386
|
get typesWithReferences() {
|
342
387
|
return this.result.types.filter(x => !x.isEnum && !x.isInterface && x.properties?.some(x => x.attributes?.some(x => x.name === 'Reference')));
|
343
388
|
}
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
for (const type of this.result.types) {
|
349
|
-
if (references.includes(type.name)) {
|
350
|
-
const importantType = importantTypes.find(x => x.properties?.some(p => p.type === type.name));
|
351
|
-
if (importantType) {
|
352
|
-
const newName = `${importantType.name}${type.name}`;
|
353
|
-
this.replaceReference(type.name, newName);
|
354
|
-
}
|
355
|
-
}
|
356
|
-
}
|
357
|
-
}
|
358
|
-
replaceReference(fromType, toType) {
|
359
|
-
for (const type of this.result.types) {
|
360
|
-
if (type.name === fromType) {
|
361
|
-
type.name = toType;
|
362
|
-
}
|
363
|
-
if (type.properties) {
|
364
|
-
for (const prop of type.properties) {
|
365
|
-
if (prop.type === fromType) {
|
366
|
-
prop.type = toType;
|
367
|
-
}
|
368
|
-
if (prop.name === fromType) {
|
369
|
-
prop.name = toType;
|
370
|
-
}
|
371
|
-
if (prop.name === `${fromType}Id`) {
|
372
|
-
prop.name = `${toType}Id`;
|
373
|
-
}
|
374
|
-
}
|
375
|
-
}
|
376
|
-
}
|
377
|
-
}
|
378
|
-
replaceIds() {
|
379
|
-
for (const type of this.classes) {
|
380
|
-
const idProp = type.properties?.find(x => x.name === `${type.name}Id`);
|
381
|
-
if (idProp) {
|
382
|
-
type.properties?.forEach(x => delete x.isPrimaryKey);
|
383
|
-
idProp.name = 'Id';
|
384
|
-
idProp.isPrimaryKey = true;
|
385
|
-
}
|
386
|
-
// If using a shortened id for the type e.g. (PerformanceReview, ReviewId)
|
387
|
-
const firstProp = type.properties?.[0];
|
388
|
-
if (firstProp?.name.endsWith('Id') && type.name.includes(firstProp.name.substring(0, firstProp.name.length - 2))) {
|
389
|
-
firstProp.name = 'Id';
|
390
|
-
firstProp.isPrimaryKey = true;
|
391
|
-
}
|
392
|
-
}
|
393
|
-
const anyIntPks = this.classes.some(x => x.properties?.some(p => p.isPrimaryKey && this.integerTypes.includes(p.type)));
|
394
|
-
if (!anyIntPks) {
|
395
|
-
for (const type of this.classes) {
|
396
|
-
const idProp = type.properties?.find(x => x.isPrimaryKey);
|
397
|
-
if (idProp) {
|
398
|
-
idProp.type = 'int';
|
399
|
-
}
|
400
|
-
}
|
401
|
-
}
|
402
|
-
}
|
403
|
-
convertReferenceTypes() {
|
404
|
-
for (const type of this.classes) {
|
405
|
-
for (let i = 0; i < type.properties.length; i++) {
|
406
|
-
const p = type.properties[i];
|
407
|
-
const refType = this.result.types.find(x => x.name === p.type && x.namespace === 'MyApp' && !x.isEnum);
|
408
|
-
if (refType) {
|
409
|
-
const fkId = `${p.name}Id`;
|
410
|
-
let idProp = refType.properties?.find(x => x.name === 'Id');
|
411
|
-
if (!idProp) {
|
412
|
-
idProp = { name: 'Id', type: 'int', isPrimaryKey: true, isValueType: true, namespace: 'System' };
|
413
|
-
refType.properties?.unshift(idProp);
|
414
|
-
}
|
415
|
-
// Only add if FK Id prop does not already exist
|
416
|
-
if (!type.properties.find(x => x.name === fkId)) {
|
417
|
-
const fkProp = {
|
418
|
-
name: fkId,
|
419
|
-
type: idProp.type,
|
420
|
-
namespace: idProp.namespace,
|
421
|
-
attributes: [{
|
422
|
-
name: "References",
|
423
|
-
constructorArgs: [{
|
424
|
-
name: "type",
|
425
|
-
type: "Type",
|
426
|
-
value: `typeof(${p.type})`
|
427
|
-
}],
|
428
|
-
args: []
|
429
|
-
}]
|
430
|
-
};
|
431
|
-
type.properties.splice(i, 0, fkProp);
|
432
|
-
}
|
433
|
-
if (!p.attributes)
|
434
|
-
p.attributes = [];
|
435
|
-
p.attributes.push({ name: "Reference" });
|
436
|
-
i++; // Skip over added fk prop
|
437
|
-
}
|
438
|
-
}
|
439
|
-
}
|
440
|
-
}
|
441
|
-
convertArrayReferenceTypes() {
|
442
|
-
for (const type of this.classes) {
|
443
|
-
for (const prop of type.properties) {
|
444
|
-
if (prop.type.endsWith('[]')) {
|
445
|
-
const elType = prop.type.substring(0, prop.type.length - 2);
|
446
|
-
const refType = this.result.types.find(x => x.name === elType && x.namespace === 'MyApp' && !x.isEnum);
|
447
|
-
if (refType && refType.properties?.find(x => x.name === 'Id' || x.isPrimaryKey)) {
|
448
|
-
prop.namespace = 'System.Collections.Generic';
|
449
|
-
prop.genericArgs = [elType];
|
450
|
-
prop.type = 'List`1';
|
451
|
-
if (!prop.attributes)
|
452
|
-
prop.attributes = [];
|
453
|
-
prop.attributes.push({ name: "Reference" });
|
454
|
-
let fkProp = refType.properties.find(x => x.name === `${type.name}Id`);
|
455
|
-
if (!fkProp) {
|
456
|
-
fkProp = {
|
457
|
-
name: `${type.name}Id`,
|
458
|
-
type: 'int',
|
459
|
-
isValueType: true,
|
460
|
-
namespace: 'System',
|
461
|
-
attributes: [{
|
462
|
-
name: "References",
|
463
|
-
constructorArgs: [{
|
464
|
-
name: "type",
|
465
|
-
type: "Type",
|
466
|
-
value: `typeof(${type.name})`
|
467
|
-
}]
|
468
|
-
}]
|
469
|
-
};
|
470
|
-
// Insert fk prop after last `*Id` prop
|
471
|
-
const lastIdPropIndex = refType.properties.findLastIndex(x => x.name.endsWith('Id'));
|
472
|
-
if (lastIdPropIndex >= 0) {
|
473
|
-
refType.properties.splice(lastIdPropIndex + 1, 0, fkProp);
|
474
|
-
}
|
475
|
-
else {
|
476
|
-
refType.properties.push(fkProp);
|
477
|
-
}
|
478
|
-
}
|
479
|
-
}
|
480
|
-
}
|
481
|
-
}
|
482
|
-
}
|
483
|
-
}
|
484
|
-
convertArraysToLists() {
|
485
|
-
for (const type of this.classes) {
|
486
|
-
for (const prop of type.properties) {
|
487
|
-
const optional = prop.type.endsWith('?');
|
488
|
-
let propType = this.unwrap(prop.type);
|
489
|
-
if (propType.endsWith('[]')) {
|
490
|
-
const elType = propType.substring(0, propType.length - 2);
|
491
|
-
prop.namespace = 'System.Collections.Generic';
|
492
|
-
prop.genericArgs = [elType];
|
493
|
-
prop.type = 'List`1' + (optional ? '?' : '');
|
494
|
-
}
|
495
|
-
}
|
496
|
-
}
|
497
|
-
}
|
498
|
-
addMissingReferencesToForeignKeyProps() {
|
499
|
-
for (const type of this.typesWithPrimaryKeys) {
|
500
|
-
for (const prop of type.properties) {
|
501
|
-
if (prop.name.endsWith('Id') && !prop.isPrimaryKey && !prop.attributes?.some(x => x.name.startsWith('Reference'))) {
|
502
|
-
const refTypeName = prop.name.substring(0, prop.name.length - 2);
|
503
|
-
const refType = this.result.types.find(x => x.name === refTypeName && x.namespace === 'MyApp' && !x.isEnum);
|
504
|
-
if (refType) {
|
505
|
-
if (!prop.attributes)
|
506
|
-
prop.attributes = [];
|
507
|
-
prop.attributes.push({
|
508
|
-
name: "References",
|
509
|
-
constructorArgs: [{
|
510
|
-
name: "type",
|
511
|
-
type: "Type",
|
512
|
-
value: `typeof(${refTypeName})`
|
513
|
-
}]
|
514
|
-
});
|
515
|
-
}
|
516
|
-
}
|
517
|
-
}
|
518
|
-
}
|
519
|
-
}
|
520
|
-
addAutoIncrementAttrs() {
|
521
|
-
for (const type of this.classes) {
|
522
|
-
for (const prop of type.properties) {
|
523
|
-
if (prop.isPrimaryKey) {
|
524
|
-
if (prop.type === 'int' || prop.type === 'long' || prop.type === 'Int32' || prop.type === 'Int64') {
|
525
|
-
if (!prop.attributes)
|
526
|
-
prop.attributes = [];
|
527
|
-
const attr = { name: "AutoIncrement" };
|
528
|
-
prop.attributes.push(attr);
|
529
|
-
}
|
530
|
-
}
|
531
|
-
}
|
532
|
-
}
|
389
|
+
onlyAttrs(attrs, only) {
|
390
|
+
if (!attrs)
|
391
|
+
return;
|
392
|
+
return attrs.filter(x => only.includes(x.name.toLowerCase()));
|
533
393
|
}
|
534
|
-
attrsFor(dtoType, attrs) {
|
394
|
+
attrsFor(dtoType, attrType, attrs) {
|
535
395
|
const requestAttrs = this.requestAttrs;
|
536
396
|
const requestPropAttrs = this.requestPropAttrs;
|
537
397
|
const modelAttrs = this.modelAttrs;
|
538
398
|
const modelPropAttrs = this.modelPropAttrs;
|
399
|
+
const isRequest = ["Read", "Create", "Update", "Delete"].includes(dtoType);
|
400
|
+
const validAttrs = attrType === "Type"
|
401
|
+
? isRequest
|
402
|
+
? requestAttrs
|
403
|
+
: modelAttrs
|
404
|
+
: isRequest
|
405
|
+
? requestPropAttrs
|
406
|
+
: modelPropAttrs;
|
407
|
+
const ignoreValidators = attrType === "Prop"
|
408
|
+
? dtoType === "Update"
|
409
|
+
? this.ignoreUpdateValidators
|
410
|
+
: dtoType === "Read"
|
411
|
+
? this.ignoreReadValidators
|
412
|
+
: dtoType === "Delete"
|
413
|
+
? this.ignoreDeleteValidators
|
414
|
+
: []
|
415
|
+
: [];
|
539
416
|
function shouldInclude(attr, dtoType) {
|
417
|
+
if (!validAttrs.includes(attr.name.toLowerCase()))
|
418
|
+
return false;
|
540
419
|
const ns = attr.namespace;
|
541
420
|
if (ns) {
|
542
421
|
if (ns == "All")
|
@@ -551,15 +430,27 @@ export class CSharpAst {
|
|
551
430
|
return dtoType == "Delete";
|
552
431
|
if (ns == "Write")
|
553
432
|
return ["Create", "Update", "Delete"].includes(dtoType);
|
433
|
+
return false;
|
554
434
|
}
|
555
435
|
else {
|
556
|
-
const isRequest = dtoType != "Model";
|
557
436
|
const nameLower = attr.name.toLowerCase();
|
558
437
|
if (isRequest) {
|
559
|
-
|
438
|
+
if (attrType === "Type") {
|
439
|
+
return requestAttrs.includes(nameLower);
|
440
|
+
}
|
441
|
+
else if (attrType === "Prop") {
|
442
|
+
if (ignoreValidators.length && ignoreValidators.includes(nameLower))
|
443
|
+
return false;
|
444
|
+
return requestPropAttrs.includes(nameLower);
|
445
|
+
}
|
560
446
|
}
|
561
447
|
else {
|
562
|
-
|
448
|
+
if (attrType === "Type") {
|
449
|
+
return modelAttrs.includes(nameLower);
|
450
|
+
}
|
451
|
+
else if (attrType === "Prop") {
|
452
|
+
return modelPropAttrs.includes(nameLower);
|
453
|
+
}
|
563
454
|
}
|
564
455
|
}
|
565
456
|
return true;
|
@@ -573,6 +464,8 @@ export class CSharpAst {
|
|
573
464
|
return to;
|
574
465
|
}
|
575
466
|
createAutoCrudApis() {
|
467
|
+
const groupName = getGroupName(this.result);
|
468
|
+
const friendlyGroupName = splitCase(groupName);
|
576
469
|
for (const type of this.classes) {
|
577
470
|
const hasPk = type.properties?.some(x => x.isPrimaryKey);
|
578
471
|
if (!hasPk)
|
@@ -583,6 +476,12 @@ export class CSharpAst {
|
|
583
476
|
const pk = type.properties?.find(x => x.isPrimaryKey);
|
584
477
|
const dataModel = { name: type.name, namespace: type.name };
|
585
478
|
const isAuditBase = type.inherits?.name === 'AuditBase';
|
479
|
+
const existingTag = type.attributes?.find(x => x.name.toLowerCase() === 'tag');
|
480
|
+
const tags = !existingTag ? [friendlyGroupName] : undefined;
|
481
|
+
const emptyTag = existingTag?.constructorArgs?.[0]?.value === '';
|
482
|
+
if (emptyTag) {
|
483
|
+
type.attributes = type.attributes.filter(x => x !== existingTag);
|
484
|
+
}
|
586
485
|
const inputTagAttrs = [{
|
587
486
|
name: "Input",
|
588
487
|
args: [{ name: "Type", type: "string", value: "tag" }]
|
@@ -591,11 +490,6 @@ export class CSharpAst {
|
|
591
490
|
name: "FieldCss",
|
592
491
|
args: [{ name: "Field", type: "string", value: "col-span-12" }]
|
593
492
|
}];
|
594
|
-
function onlyAttrs(attrs, only) {
|
595
|
-
if (!attrs)
|
596
|
-
return;
|
597
|
-
return attrs.filter(x => only.includes(x.name));
|
598
|
-
}
|
599
493
|
const idsProp = pk
|
600
494
|
? {
|
601
495
|
name: `${pk.name}s`,
|
@@ -620,13 +514,13 @@ export class CSharpAst {
|
|
620
514
|
properties: pk
|
621
515
|
? [
|
622
516
|
Object.assign({}, pk, {
|
623
|
-
type:
|
624
|
-
attributes:
|
517
|
+
type: nullable(pk.type),
|
518
|
+
attributes: this.attrsFor("Read", "Prop", pk.attributes),
|
625
519
|
}),
|
626
520
|
idsProp
|
627
521
|
]
|
628
522
|
: [],
|
629
|
-
attributes: this.attrsFor("Read", type.attributes),
|
523
|
+
attributes: this.attrsFor("Read", "Type", type.attributes),
|
630
524
|
},
|
631
525
|
returnType: {
|
632
526
|
name: "QueryResponse`1",
|
@@ -653,7 +547,6 @@ export class CSharpAst {
|
|
653
547
|
let createName = `Create${type.name}`;
|
654
548
|
let createApi = this.result.operations.find(x => x.request.name === createName);
|
655
549
|
if (!createApi) {
|
656
|
-
const ignorePropsWithAttrs = ['AutoIncrement', 'Reference'];
|
657
550
|
createApi = {
|
658
551
|
method: "POST",
|
659
552
|
actions: ["ANY"],
|
@@ -666,13 +559,13 @@ export class CSharpAst {
|
|
666
559
|
genericArgs: [type.name]
|
667
560
|
}],
|
668
561
|
properties: type.properties
|
669
|
-
.filter(x => !x.attributes?.some(a =>
|
562
|
+
.filter(x => !x.attributes?.some(a => this.ignoreCreateProps.includes(a.name.toLowerCase()))).map(x => Object.assign({}, x, {
|
670
563
|
type: x.isPrimaryKey
|
671
564
|
? x.type
|
672
565
|
: `${x.type}`,
|
673
|
-
attributes:
|
566
|
+
attributes: this.attrsFor("Create", "Prop", x.attributes),
|
674
567
|
})),
|
675
|
-
attributes: this.attrsFor("Create", type.attributes),
|
568
|
+
attributes: this.attrsFor("Create", "Type", type.attributes),
|
676
569
|
},
|
677
570
|
returnType: {
|
678
571
|
name: "IdResponse",
|
@@ -684,20 +577,29 @@ export class CSharpAst {
|
|
684
577
|
if (prop.isRequired) {
|
685
578
|
if (!prop.attributes)
|
686
579
|
prop.attributes = [];
|
580
|
+
var hasAnyValidateAttrs = prop.attributes.some(x => x.name.toLowerCase().startsWith('validate'));
|
687
581
|
if (prop.type === 'string') {
|
688
|
-
|
689
|
-
|
690
|
-
|
582
|
+
if (!hasAnyValidateAttrs) {
|
583
|
+
prop.attributes.push({
|
584
|
+
name: "ValidateNotEmpty",
|
585
|
+
});
|
586
|
+
}
|
691
587
|
}
|
692
588
|
else if (this.integerTypes.includes(prop.type)) {
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
589
|
+
if (!hasAnyValidateAttrs) {
|
590
|
+
prop.attributes.push({
|
591
|
+
name: "ValidateGreaterThan",
|
592
|
+
constructorArgs: [{ name: "value", type: "int", value: "0" }]
|
593
|
+
});
|
594
|
+
}
|
697
595
|
}
|
698
596
|
else if (prop.type === 'List`1' && this.commonValueTypes.includes(prop.genericArgs[0])) {
|
699
597
|
prop.attributes.push(...inputTagAttrs);
|
700
598
|
}
|
599
|
+
const emptyValidateAttr = prop.attributes.find(x => x.name.toLowerCase() === 'validate');
|
600
|
+
if (emptyValidateAttr && emptyValidateAttr.constructorArgs?.[0]?.value === '') {
|
601
|
+
prop.attributes = prop.attributes.filter(x => x !== emptyValidateAttr);
|
602
|
+
}
|
701
603
|
}
|
702
604
|
}
|
703
605
|
if (isAuditBase) {
|
@@ -719,7 +621,6 @@ export class CSharpAst {
|
|
719
621
|
let updateName = `Update${type.name}`;
|
720
622
|
let updateApi = this.result.operations.find(x => x.request.name === updateName);
|
721
623
|
if (!updateApi) {
|
722
|
-
const ignoreAttrs = ['AutoIncrement'];
|
723
624
|
updateApi = {
|
724
625
|
method: "PATCH",
|
725
626
|
actions: ["ANY"],
|
@@ -731,13 +632,13 @@ export class CSharpAst {
|
|
731
632
|
namespace: "ServiceStack",
|
732
633
|
genericArgs: [type.name]
|
733
634
|
}],
|
734
|
-
properties: type.properties?.filter(x => !x.attributes?.some(x => x.name
|
635
|
+
properties: type.properties?.filter(x => !x.attributes?.some(x => this.ignoreUpdateProps.includes(x.name.toLowerCase()))).map(x => Object.assign({}, x, {
|
735
636
|
type: x.isPrimaryKey
|
736
637
|
? x.type
|
737
|
-
: `${
|
738
|
-
attributes:
|
638
|
+
: `${nullable(x.type)}`,
|
639
|
+
attributes: this.attrsFor("Update", "Prop", x.attributes),
|
739
640
|
})),
|
740
|
-
attributes: this.attrsFor("Update", type.attributes),
|
641
|
+
attributes: this.attrsFor("Update", "Type", type.attributes),
|
741
642
|
},
|
742
643
|
returnType: {
|
743
644
|
name: "IdResponse",
|
@@ -787,13 +688,13 @@ export class CSharpAst {
|
|
787
688
|
properties: pk
|
788
689
|
? [
|
789
690
|
Object.assign({}, pk, {
|
790
|
-
type:
|
791
|
-
attributes:
|
691
|
+
type: nullable(pk.type),
|
692
|
+
attributes: this.attrsFor("Delete", "Prop", pk.attributes),
|
792
693
|
}),
|
793
694
|
idsProp
|
794
695
|
]
|
795
696
|
: [],
|
796
|
-
attributes: this.attrsFor("Delete", type.attributes),
|
697
|
+
attributes: this.attrsFor("Delete", "Type", type.attributes),
|
797
698
|
},
|
798
699
|
returnsVoid: true,
|
799
700
|
dataModel,
|
@@ -815,67 +716,20 @@ export class CSharpAst {
|
|
815
716
|
this.result.operations.push(deleteApi);
|
816
717
|
}
|
817
718
|
}
|
719
|
+
this.filterModelAttributes();
|
720
|
+
return this.result;
|
818
721
|
}
|
819
722
|
filterModelAttributes() {
|
820
723
|
for (const type of this.classes) {
|
821
|
-
|
822
|
-
|
823
|
-
}
|
824
|
-
// Add Icon for BuiltIn UIs and AutoQueryGrid to known type names
|
825
|
-
addIconsToKnownTypes() {
|
826
|
-
for (const type of this.typesWithPrimaryKeys) {
|
827
|
-
const icon = Icons[type.name];
|
828
|
-
if (icon) {
|
829
|
-
if (!type.attributes)
|
830
|
-
type.attributes = [];
|
831
|
-
if (type.attributes.some(x => x.name === 'Icon'))
|
832
|
-
continue;
|
833
|
-
type.attributes.push({
|
834
|
-
name: "Icon",
|
835
|
-
args: [{ name: "Svg", type: "string", value: icon }]
|
836
|
-
});
|
724
|
+
if (type.attributes?.length) {
|
725
|
+
type.attributes = this.attrsFor("Model", "Type", type.attributes);
|
837
726
|
}
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
hideReferenceProperties() {
|
842
|
-
for (const type of this.typesWithReferences) {
|
843
|
-
for (const prop of type.properties.filter(x => x.attributes?.some(x => x.name === 'Reference'))) {
|
844
|
-
if (!prop.attributes)
|
845
|
-
prop.attributes = [];
|
846
|
-
//[Format(FormatMethods.Hidden)]
|
847
|
-
prop.attributes.push({
|
848
|
-
name: "Format",
|
849
|
-
constructorArgs: [{ name: "method", type: "constant", value: "FormatMethods.Hidden" }]
|
850
|
-
});
|
851
|
-
}
|
852
|
-
}
|
853
|
-
}
|
854
|
-
// Replace User Tables and FKs with AuditBase tables and
|
855
|
-
replaceUserReferencesWithAuditTables() {
|
856
|
-
for (const type of this.typesWithPrimaryKeys) {
|
857
|
-
const removeProps = [];
|
858
|
-
for (const prop of type.properties) {
|
859
|
-
if (prop.name === 'UserId') {
|
860
|
-
removeProps.push(prop.name);
|
861
|
-
}
|
862
|
-
if (prop.attributes?.some(a => a.name === 'Reference' && a.constructorArgs?.some(x => x.value === 'typeof(User)'))) {
|
863
|
-
removeProps.push(prop.name);
|
864
|
-
}
|
865
|
-
if (prop.type === 'User') {
|
866
|
-
removeProps.push(prop.name);
|
727
|
+
type.properties?.forEach(p => {
|
728
|
+
if (p.attributes?.length) {
|
729
|
+
p.attributes = this.attrsFor("Model", "Prop", p.attributes);
|
867
730
|
}
|
868
|
-
|
869
|
-
removeProps.push(prop.name);
|
870
|
-
}
|
871
|
-
}
|
872
|
-
if (removeProps.length) {
|
873
|
-
type.properties = type.properties.filter(x => !removeProps.includes(x.name));
|
874
|
-
type.inherits = { name: "AuditBase", namespace: "ServiceStack" };
|
875
|
-
}
|
731
|
+
});
|
876
732
|
}
|
877
|
-
// Remove User Table
|
878
|
-
this.result.types = this.result.types.filter(x => x.name !== 'User');
|
879
733
|
}
|
880
734
|
parseTypes() {
|
881
735
|
this.ast.classes.forEach(c => {
|
@@ -890,23 +744,12 @@ export class CSharpAst {
|
|
890
744
|
return;
|
891
745
|
this.addMetadataEnum(e);
|
892
746
|
});
|
893
|
-
this.replaceReferences(['Service']);
|
894
|
-
this.replaceIds();
|
895
|
-
this.convertReferenceTypes();
|
896
|
-
this.convertArrayReferenceTypes();
|
897
|
-
this.convertArraysToLists();
|
898
|
-
this.addMissingReferencesToForeignKeyProps();
|
899
|
-
this.addAutoIncrementAttrs();
|
900
|
-
this.addIconsToKnownTypes();
|
901
|
-
this.hideReferenceProperties();
|
902
|
-
this.replaceUserReferencesWithAuditTables();
|
903
|
-
this.createAutoCrudApis();
|
904
|
-
this.filterModelAttributes();
|
905
747
|
}
|
906
748
|
parse(ast) {
|
907
749
|
const classes = ast.classes.concat(ast.interfaces);
|
908
750
|
const enums = ast.enums;
|
909
751
|
this.ast = {
|
752
|
+
references: [],
|
910
753
|
classes: classes,
|
911
754
|
interfaces: [],
|
912
755
|
enums: enums ?? [],
|
@@ -929,7 +772,290 @@ export class CSharpAst {
|
|
929
772
|
return this.result;
|
930
773
|
}
|
931
774
|
}
|
932
|
-
export
|
933
|
-
|
934
|
-
|
775
|
+
export const Transforms = {
|
776
|
+
Default: [
|
777
|
+
convertReferenceTypes,
|
778
|
+
convertArrayReferenceTypes,
|
779
|
+
convertArraysToLists,
|
780
|
+
addMissingReferencesToForeignKeyProps,
|
781
|
+
addAutoIncrementAttrs,
|
782
|
+
addIconsToKnownTypes,
|
783
|
+
hideReferenceProperties,
|
784
|
+
],
|
785
|
+
};
|
786
|
+
export function toMetadataTypes(ast, transforms) {
|
787
|
+
const gen = new CSharpAst();
|
788
|
+
gen.parse(ast);
|
789
|
+
if (!transforms)
|
790
|
+
transforms = Transforms.Default;
|
791
|
+
for (const transform of transforms) {
|
792
|
+
transform(gen);
|
793
|
+
}
|
794
|
+
return gen.createAutoCrudApis();
|
795
|
+
}
|
796
|
+
export function unwrap(type) {
|
797
|
+
if (type.endsWith("?")) {
|
798
|
+
return type.substring(0, type.length - 1);
|
799
|
+
}
|
800
|
+
return type;
|
801
|
+
}
|
802
|
+
export function nullable(type) {
|
803
|
+
return type.endsWith('?') ? type : `${type}?`;
|
804
|
+
}
|
805
|
+
export function replaceReference(gen, fromType, toType) {
|
806
|
+
for (const type of gen.result.types) {
|
807
|
+
if (type.name === fromType) {
|
808
|
+
type.name = toType;
|
809
|
+
}
|
810
|
+
if (type.properties) {
|
811
|
+
for (const prop of type.properties) {
|
812
|
+
if (prop.type === fromType) {
|
813
|
+
prop.type = toType;
|
814
|
+
}
|
815
|
+
if (prop.name === fromType) {
|
816
|
+
prop.name = toType;
|
817
|
+
}
|
818
|
+
if (prop.name === `${fromType}Id`) {
|
819
|
+
prop.name = `${toType}Id`;
|
820
|
+
}
|
821
|
+
}
|
822
|
+
}
|
823
|
+
}
|
824
|
+
}
|
825
|
+
export function replaceServiceReferences(gen) {
|
826
|
+
replaceReferences(gen, ['Service']);
|
827
|
+
}
|
828
|
+
export function replaceReferences(gen, references) {
|
829
|
+
// The most important types are the ones with the most references
|
830
|
+
const refCount = (t) => t.properties?.filter(p => gen.result.types.find(x => x.name === p.type && p.namespace === 'MyApp')).length || 0;
|
831
|
+
const importantTypes = gen.result.types.sort((x, y) => refCount(y) - refCount(x));
|
832
|
+
for (const type of gen.result.types) {
|
833
|
+
if (references.includes(type.name)) {
|
834
|
+
const importantType = importantTypes.find(x => x.properties?.some(p => p.type === type.name));
|
835
|
+
if (importantType) {
|
836
|
+
const newName = `${importantType.name}${type.name}`;
|
837
|
+
replaceReference(gen, type.name, newName);
|
838
|
+
}
|
839
|
+
}
|
840
|
+
}
|
841
|
+
}
|
842
|
+
export function replaceIds(gen) {
|
843
|
+
for (const type of gen.classes) {
|
844
|
+
const idProp = type.properties?.find(x => x.name === `${type.name}Id`);
|
845
|
+
if (idProp) {
|
846
|
+
type.properties?.forEach(x => delete x.isPrimaryKey);
|
847
|
+
idProp.name = 'Id';
|
848
|
+
idProp.isPrimaryKey = true;
|
849
|
+
}
|
850
|
+
// If using a shortened id for the type e.g. (PerformanceReview, ReviewId)
|
851
|
+
const firstProp = type.properties?.[0];
|
852
|
+
if (firstProp?.name.endsWith('Id') && type.name.includes(firstProp.name.substring(0, firstProp.name.length - 2))) {
|
853
|
+
firstProp.name = 'Id';
|
854
|
+
firstProp.isPrimaryKey = true;
|
855
|
+
}
|
856
|
+
}
|
857
|
+
// Replace all string Ids with int Ids
|
858
|
+
const anyIntPks = gen.classes.some(x => x.properties?.some(p => p.isPrimaryKey && gen.integerTypes.includes(p.type)));
|
859
|
+
if (!anyIntPks) {
|
860
|
+
for (const type of gen.classes) {
|
861
|
+
const idProp = type.properties?.find(x => x.isPrimaryKey);
|
862
|
+
if (idProp) {
|
863
|
+
idProp.type = 'int';
|
864
|
+
}
|
865
|
+
}
|
866
|
+
}
|
867
|
+
}
|
868
|
+
export function convertReferenceTypes(gen) {
|
869
|
+
for (const type of gen.classes) {
|
870
|
+
for (let i = 0; i < type.properties.length; i++) {
|
871
|
+
const p = type.properties[i];
|
872
|
+
const refType = gen.result.types.find(x => x.name === p.type && x.namespace === 'MyApp' && !x.isEnum);
|
873
|
+
if (refType) {
|
874
|
+
const fkId = `${p.name}Id`;
|
875
|
+
let idProp = refType.properties?.find(x => x.name === 'Id');
|
876
|
+
if (!idProp) {
|
877
|
+
idProp = { name: 'Id', type: 'int', isPrimaryKey: true, isRequired: true, isValueType: true, namespace: 'System' };
|
878
|
+
refType.properties?.unshift(idProp);
|
879
|
+
}
|
880
|
+
// Only add if FK Id prop does not already exist
|
881
|
+
if (!type.properties.find(x => x.name === fkId)) {
|
882
|
+
const fkProp = {
|
883
|
+
name: fkId,
|
884
|
+
type: idProp.type,
|
885
|
+
namespace: idProp.namespace,
|
886
|
+
attributes: [{
|
887
|
+
name: "References",
|
888
|
+
constructorArgs: [{
|
889
|
+
name: "type",
|
890
|
+
type: "Type",
|
891
|
+
value: `typeof(${p.type})`
|
892
|
+
}],
|
893
|
+
args: []
|
894
|
+
}]
|
895
|
+
};
|
896
|
+
type.properties.splice(i, 0, fkProp);
|
897
|
+
}
|
898
|
+
if (!p.attributes)
|
899
|
+
p.attributes = [];
|
900
|
+
p.attributes.push({ name: "Reference" });
|
901
|
+
i++; // Skip over added fk prop
|
902
|
+
}
|
903
|
+
}
|
904
|
+
}
|
905
|
+
}
|
906
|
+
export function convertArrayReferenceTypes(gen) {
|
907
|
+
for (const type of gen.classes) {
|
908
|
+
for (const prop of type.properties) {
|
909
|
+
if (prop.type.endsWith('[]')) {
|
910
|
+
const elType = prop.type.substring(0, prop.type.length - 2);
|
911
|
+
const refType = gen.result.types.find(x => x.name === elType && x.namespace === 'MyApp' && !x.isEnum);
|
912
|
+
if (refType && refType.properties?.find(x => x.name === 'Id' || x.isPrimaryKey)) {
|
913
|
+
prop.namespace = 'System.Collections.Generic';
|
914
|
+
prop.genericArgs = [elType];
|
915
|
+
prop.type = 'List`1';
|
916
|
+
if (!prop.attributes)
|
917
|
+
prop.attributes = [];
|
918
|
+
prop.attributes.push({ name: "Reference" });
|
919
|
+
let fkProp = refType.properties.find(x => x.name === `${type.name}Id`);
|
920
|
+
if (!fkProp) {
|
921
|
+
fkProp = {
|
922
|
+
name: `${type.name}Id`,
|
923
|
+
type: 'int',
|
924
|
+
isValueType: true,
|
925
|
+
namespace: 'System',
|
926
|
+
attributes: [{
|
927
|
+
name: "References",
|
928
|
+
constructorArgs: [{
|
929
|
+
name: "type",
|
930
|
+
type: "Type",
|
931
|
+
value: `typeof(${type.name})`
|
932
|
+
}]
|
933
|
+
}]
|
934
|
+
};
|
935
|
+
// Insert fk prop after last `*Id` prop
|
936
|
+
const lastIdPropIndex = refType.properties.findLastIndex(x => x.name.endsWith('Id'));
|
937
|
+
if (lastIdPropIndex >= 0) {
|
938
|
+
refType.properties.splice(lastIdPropIndex + 1, 0, fkProp);
|
939
|
+
}
|
940
|
+
else {
|
941
|
+
refType.properties.push(fkProp);
|
942
|
+
}
|
943
|
+
}
|
944
|
+
}
|
945
|
+
}
|
946
|
+
}
|
947
|
+
}
|
948
|
+
}
|
949
|
+
export function convertArraysToLists(gen) {
|
950
|
+
for (const type of gen.classes) {
|
951
|
+
for (const prop of type.properties) {
|
952
|
+
const optional = prop.type.endsWith('?');
|
953
|
+
let propType = unwrap(prop.type);
|
954
|
+
if (propType.endsWith('[]')) {
|
955
|
+
const elType = propType.substring(0, propType.length - 2);
|
956
|
+
prop.namespace = 'System.Collections.Generic';
|
957
|
+
prop.genericArgs = [elType];
|
958
|
+
prop.type = 'List`1' + (optional ? '?' : '');
|
959
|
+
}
|
960
|
+
}
|
961
|
+
}
|
962
|
+
}
|
963
|
+
export function addMissingReferencesToForeignKeyProps(gen) {
|
964
|
+
for (const type of gen.typesWithPrimaryKeys) {
|
965
|
+
for (const prop of type.properties) {
|
966
|
+
if (prop.name.endsWith('Id') && !prop.isPrimaryKey && !prop.attributes?.some(x => x.name.startsWith('Reference'))) {
|
967
|
+
const refTypeName = prop.name.substring(0, prop.name.length - 2);
|
968
|
+
const refType = gen.result.types.find(x => x.name === refTypeName && x.namespace === 'MyApp' && !x.isEnum);
|
969
|
+
if (refType) {
|
970
|
+
if (!prop.attributes)
|
971
|
+
prop.attributes = [];
|
972
|
+
prop.attributes.push({
|
973
|
+
name: "References",
|
974
|
+
constructorArgs: [{
|
975
|
+
name: "type",
|
976
|
+
type: "Type",
|
977
|
+
value: `typeof(${refTypeName})`
|
978
|
+
}]
|
979
|
+
});
|
980
|
+
}
|
981
|
+
}
|
982
|
+
}
|
983
|
+
}
|
984
|
+
}
|
985
|
+
export function addAutoIncrementAttrs(gen) {
|
986
|
+
for (const type of gen.classes) {
|
987
|
+
for (const prop of type.properties) {
|
988
|
+
const hasPkAttr = prop.attributes?.some(x => ['primarykey', 'autoincrement', 'autoid'].includes(x.name.toLowerCase()));
|
989
|
+
if (prop.isPrimaryKey && !hasPkAttr) {
|
990
|
+
if (prop.type === 'int' || prop.type === 'long' || prop.type === 'Int32' || prop.type === 'Int64') {
|
991
|
+
if (!prop.attributes)
|
992
|
+
prop.attributes = [];
|
993
|
+
const attr = { name: "AutoIncrement" };
|
994
|
+
prop.attributes.push(attr);
|
995
|
+
}
|
996
|
+
}
|
997
|
+
}
|
998
|
+
}
|
999
|
+
}
|
1000
|
+
// Add Icon for BuiltIn UIs and AutoQueryGrid to known type names
|
1001
|
+
export function addIconsToKnownTypes(gen) {
|
1002
|
+
for (const type of gen.typesWithPrimaryKeys) {
|
1003
|
+
const icon = Icons[type.name];
|
1004
|
+
if (icon) {
|
1005
|
+
if (!type.attributes)
|
1006
|
+
type.attributes = [];
|
1007
|
+
const existingIcon = type.attributes.find(x => x.name === 'Icon');
|
1008
|
+
if (existingIcon) {
|
1009
|
+
// remove empty icon
|
1010
|
+
if (existingIcon.constructorArgs?.[0]?.value === '') {
|
1011
|
+
type.attributes = type.attributes.filter(x => x !== existingIcon);
|
1012
|
+
}
|
1013
|
+
return;
|
1014
|
+
}
|
1015
|
+
type.attributes.push({
|
1016
|
+
name: "Icon",
|
1017
|
+
args: [{ name: "Svg", type: "string", value: icon }]
|
1018
|
+
});
|
1019
|
+
}
|
1020
|
+
}
|
1021
|
+
}
|
1022
|
+
// Hide Reference Properties from AutoQueryGrid Grid View
|
1023
|
+
export function hideReferenceProperties(gen) {
|
1024
|
+
for (const type of gen.typesWithReferences) {
|
1025
|
+
for (const prop of type.properties.filter(x => x.attributes?.some(x => x.name === 'Reference'))) {
|
1026
|
+
if (!prop.attributes)
|
1027
|
+
prop.attributes = [];
|
1028
|
+
//[Format(FormatMethods.Hidden)]
|
1029
|
+
prop.attributes.push({
|
1030
|
+
name: "Format",
|
1031
|
+
constructorArgs: [{ name: "method", type: "constant", value: "FormatMethods.Hidden" }]
|
1032
|
+
});
|
1033
|
+
}
|
1034
|
+
}
|
1035
|
+
}
|
1036
|
+
// Replace User Tables and FKs with AuditBase tables and
|
1037
|
+
export function replaceUserReferencesWithAuditTables(gen) {
|
1038
|
+
for (const type of gen.typesWithPrimaryKeys) {
|
1039
|
+
const removeProps = [];
|
1040
|
+
for (const prop of type.properties) {
|
1041
|
+
if (prop.name === 'UserId') {
|
1042
|
+
removeProps.push(prop.name);
|
1043
|
+
}
|
1044
|
+
if (prop.attributes?.some(a => a.name === 'Reference' && a.constructorArgs?.some(x => x.value === 'typeof(User)'))) {
|
1045
|
+
removeProps.push(prop.name);
|
1046
|
+
}
|
1047
|
+
if (prop.type === 'User') {
|
1048
|
+
removeProps.push(prop.name);
|
1049
|
+
}
|
1050
|
+
if (prop.genericArgs && prop.genericArgs.includes('User')) {
|
1051
|
+
removeProps.push(prop.name);
|
1052
|
+
}
|
1053
|
+
}
|
1054
|
+
if (removeProps.length) {
|
1055
|
+
type.properties = type.properties.filter(x => !removeProps.includes(x.name));
|
1056
|
+
type.inherits = { name: "AuditBase", namespace: "ServiceStack" };
|
1057
|
+
}
|
1058
|
+
}
|
1059
|
+
// Remove User Table
|
1060
|
+
gen.result.types = gen.result.types.filter(x => x.name !== 'User');
|
935
1061
|
}
|