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/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
- 'validateLessThanLength',
106
+ 'validateLessThan',
88
107
  'validateLessThanOrEqual',
89
- 'validateGreaterThanLength',
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
- unwrap(type) {
161
- if (type.endsWith("?")) {
162
- return type.substring(0, type.length - 1);
163
- }
164
- return type;
165
- }
166
- nullable(type) {
167
- return type.endsWith('?') ? type : `${type}?`;
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 = this.unwrap(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 = this.unwrap(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 ? this.nullable(type.name) : type.name,
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
- replaceReferences(references) {
345
- // The most important types are the ones with the most references
346
- const refCount = (t) => t.properties?.filter(p => this.result.types.find(x => x.name === p.type && p.namespace === 'MyApp')).length || 0;
347
- const importantTypes = this.result.types.sort((x, y) => refCount(y) - refCount(x));
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
- return requestAttrs.includes(nameLower) || requestPropAttrs.includes(nameLower);
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
- return modelAttrs.includes(nameLower) || modelPropAttrs.includes(nameLower);
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: `${this.nullable(pk.type)}`,
624
- attributes: onlyAttrs(pk.attributes, this.requestPropAttrs),
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 => ignorePropsWithAttrs.includes(a.name))).map(x => Object.assign({}, x, {
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: onlyAttrs(x.attributes, this.requestPropAttrs),
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
- prop.attributes.push({
689
- name: "ValidateNotEmpty",
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
- prop.attributes.push({
694
- name: "ValidateGreaterThan",
695
- constructorArgs: [{ name: "value", type: "int", value: "0" }]
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 === 'References')).map(x => Object.assign({}, x, {
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
- : `${this.nullable(x.type)}`,
738
- attributes: onlyAttrs(x.attributes?.filter(a => !ignoreAttrs.includes(a.name)), this.requestPropAttrs),
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: `${this.nullable(pk.type)}`,
791
- attributes: onlyAttrs(pk.attributes, this.requestPropAttrs),
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
- type.attributes = this.attrsFor("Model", type.attributes);
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
- // Hide Reference Properties from AutoQueryGrid Grid View
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
- if (prop.genericArgs && prop.genericArgs.includes('User')) {
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 function toMetadataTypes(ast) {
933
- const generator = new CSharpAst();
934
- return generator.parse(ast);
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
  }