@gandalan/weblibs 1.5.23 → 1.5.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.
@@ -26,6 +26,7 @@ const businessRootMarkerStart = "// BEGIN GENERATED ROOT BUSINESS TYPEDEFS";
26
26
  const businessRootMarkerEnd = "// END GENERATED ROOT BUSINESS TYPEDEFS";
27
27
 
28
28
  const simpleImportTypePattern = /^import\((?:"|').+(?:"|')\)\.[A-Za-z0-9_$]+$/;
29
+ const returnTypeOfCreateApiPattern = /^ReturnType<\s*typeof\s+(create[A-Za-z0-9_$]+Api)\s*>$/;
29
30
 
30
31
  const rawType = (js, ts = js) => ({ kind: "raw", js, ts });
31
32
  const refType = (name) => rawType(name);
@@ -356,6 +357,711 @@ function normalizeJSDocBlock(block) {
356
357
  .trim();
357
358
  }
358
359
 
360
+ function skipQuotedString(source, startIndex, quoteCharacter) {
361
+ let index = startIndex + 1;
362
+
363
+ while (index < source.length) {
364
+ const character = source[index];
365
+
366
+ if (character === "\\") {
367
+ index += 2;
368
+ continue;
369
+ }
370
+
371
+ if (character === quoteCharacter) {
372
+ return index + 1;
373
+ }
374
+
375
+ index += 1;
376
+ }
377
+
378
+ return source.length;
379
+ }
380
+
381
+ function skipBlockComment(source, startIndex) {
382
+ const endIndex = source.indexOf("*/", startIndex + 2);
383
+ return endIndex === -1 ? source.length : endIndex + 2;
384
+ }
385
+
386
+ function skipLineComment(source, startIndex) {
387
+ const endIndex = source.indexOf("\n", startIndex + 2);
388
+ return endIndex === -1 ? source.length : endIndex + 1;
389
+ }
390
+
391
+ function findMatchingDelimiter(source, startIndex, openCharacter, closeCharacter) {
392
+ let depth = 1;
393
+
394
+ for (let index = startIndex + 1; index < source.length; index += 1) {
395
+ const character = source[index];
396
+ const nextCharacter = source[index + 1];
397
+
398
+ if (character === "\"" || character === "'") {
399
+ index = skipQuotedString(source, index, character) - 1;
400
+ continue;
401
+ }
402
+
403
+ if (character === "`") {
404
+ index = skipTemplateLiteral(source, index) - 1;
405
+ continue;
406
+ }
407
+
408
+ if (character === "/" && nextCharacter === "*") {
409
+ index = skipBlockComment(source, index) - 1;
410
+ continue;
411
+ }
412
+
413
+ if (character === "/" && nextCharacter === "/") {
414
+ index = skipLineComment(source, index) - 1;
415
+ continue;
416
+ }
417
+
418
+ if (character === openCharacter) {
419
+ depth += 1;
420
+ continue;
421
+ }
422
+
423
+ if (character === closeCharacter) {
424
+ depth -= 1;
425
+
426
+ if (depth === 0) {
427
+ return index;
428
+ }
429
+ }
430
+ }
431
+
432
+ return -1;
433
+ }
434
+
435
+ function skipTemplateLiteral(source, startIndex) {
436
+ let index = startIndex + 1;
437
+
438
+ while (index < source.length) {
439
+ const character = source[index];
440
+
441
+ if (character === "\\") {
442
+ index += 2;
443
+ continue;
444
+ }
445
+
446
+ if (character === "`") {
447
+ return index + 1;
448
+ }
449
+
450
+ if (character === "$" && source[index + 1] === "{") {
451
+ const closingIndex = findMatchingDelimiter(source, index + 1, "{", "}");
452
+
453
+ if (closingIndex === -1) {
454
+ return source.length;
455
+ }
456
+
457
+ index = closingIndex + 1;
458
+ continue;
459
+ }
460
+
461
+ index += 1;
462
+ }
463
+
464
+ return source.length;
465
+ }
466
+
467
+ function skipWhitespace(value, startIndex) {
468
+ let index = startIndex;
469
+
470
+ while (index < value.length && /\s/u.test(value[index])) {
471
+ index += 1;
472
+ }
473
+
474
+ return index;
475
+ }
476
+
477
+ function skipWhitespaceAndComments(source, startIndex) {
478
+ let index = startIndex;
479
+
480
+ while (index < source.length) {
481
+ index = skipWhitespace(source, index);
482
+
483
+ if (source.startsWith("/**", index) || source.startsWith("/*", index)) {
484
+ index = skipBlockComment(source, index);
485
+ continue;
486
+ }
487
+
488
+ if (source.startsWith("//", index)) {
489
+ index = skipLineComment(source, index);
490
+ continue;
491
+ }
492
+
493
+ break;
494
+ }
495
+
496
+ return index;
497
+ }
498
+
499
+ function isIdentifierStart(character) {
500
+ return /[A-Za-z_$]/u.test(character);
501
+ }
502
+
503
+ function isIdentifierPart(character) {
504
+ return /[A-Za-z0-9_$]/u.test(character);
505
+ }
506
+
507
+ function buildFunctionTypeExpressionFromJSDoc(block) {
508
+ const normalizedBlock = normalizeJSDocBlock(block);
509
+ const params = extractParamEntries(normalizedBlock);
510
+ const returns = extractReturnsEntry(normalizedBlock)?.typeExpression ?? "void";
511
+
512
+ return `(${params.map((param) => `${param.name}${param.optional ? "?" : ""}: ${param.typeExpression}`).join(", ")}) => ${returns}`;
513
+ }
514
+
515
+ function buildObjectLiteralTypeExpression(properties) {
516
+ return `{ ${properties.map((property) => `${property.name}${property.optional ? "?" : ""}: ${property.typeExpression}`).join("; ")} }`;
517
+ }
518
+
519
+ function parseFunctionParameterTypes(jsDocBlock) {
520
+ if (!jsDocBlock) {
521
+ return new Map();
522
+ }
523
+
524
+ const normalizedBlock = normalizeJSDocBlock(jsDocBlock);
525
+ const params = extractParamEntries(normalizedBlock);
526
+
527
+ return new Map(params.map((param) => [param.name, param.typeExpression]));
528
+ }
529
+
530
+ function readLeadingJSDoc(source, startIndex) {
531
+ let index = startIndex;
532
+ let jsDocBlock = null;
533
+
534
+ while (index < source.length) {
535
+ index = skipWhitespace(source, index);
536
+
537
+ if (source.startsWith("/**", index)) {
538
+ const endIndex = skipBlockComment(source, index);
539
+ jsDocBlock = source.slice(index, endIndex);
540
+ index = endIndex;
541
+ continue;
542
+ }
543
+
544
+ if (source.startsWith("/*", index)) {
545
+ index = skipBlockComment(source, index);
546
+ continue;
547
+ }
548
+
549
+ if (source.startsWith("//", index)) {
550
+ index = skipLineComment(source, index);
551
+ continue;
552
+ }
553
+
554
+ break;
555
+ }
556
+
557
+ return {
558
+ jsDocBlock,
559
+ nextIndex: index
560
+ };
561
+ }
562
+
563
+ function findExpressionEnd(source, startIndex) {
564
+ let braceDepth = 0;
565
+ let bracketDepth = 0;
566
+ let parenDepth = 0;
567
+
568
+ for (let index = startIndex; index < source.length; index += 1) {
569
+ const character = source[index];
570
+ const nextCharacter = source[index + 1];
571
+
572
+ if (character === "\"" || character === "'") {
573
+ index = skipQuotedString(source, index, character) - 1;
574
+ continue;
575
+ }
576
+
577
+ if (character === "`") {
578
+ index = skipTemplateLiteral(source, index) - 1;
579
+ continue;
580
+ }
581
+
582
+ if (character === "/" && nextCharacter === "*") {
583
+ index = skipBlockComment(source, index) - 1;
584
+ continue;
585
+ }
586
+
587
+ if (character === "/" && nextCharacter === "/") {
588
+ index = skipLineComment(source, index) - 1;
589
+ continue;
590
+ }
591
+
592
+ if (character === "{") {
593
+ braceDepth += 1;
594
+ continue;
595
+ }
596
+
597
+ if (character === "}") {
598
+ if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
599
+ return index;
600
+ }
601
+
602
+ braceDepth = Math.max(0, braceDepth - 1);
603
+ continue;
604
+ }
605
+
606
+ if (character === "[") {
607
+ bracketDepth += 1;
608
+ continue;
609
+ }
610
+
611
+ if (character === "]") {
612
+ bracketDepth = Math.max(0, bracketDepth - 1);
613
+ continue;
614
+ }
615
+
616
+ if (character === "(") {
617
+ parenDepth += 1;
618
+ continue;
619
+ }
620
+
621
+ if (character === ")") {
622
+ parenDepth = Math.max(0, parenDepth - 1);
623
+ continue;
624
+ }
625
+
626
+ if (character === "," && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
627
+ return index;
628
+ }
629
+ }
630
+
631
+ return source.length;
632
+ }
633
+
634
+ function inferTypeExpressionFromInitializer(initializerSource, scopeTypeMap) {
635
+ const trimmedInitializer = initializerSource.trim();
636
+
637
+ if (!trimmedInitializer) {
638
+ return "any";
639
+ }
640
+
641
+ if (scopeTypeMap.has(trimmedInitializer)) {
642
+ return scopeTypeMap.get(trimmedInitializer);
643
+ }
644
+
645
+ if (trimmedInitializer === "null") {
646
+ return "null";
647
+ }
648
+
649
+ if (trimmedInitializer === "true" || trimmedInitializer === "false") {
650
+ return "boolean";
651
+ }
652
+
653
+ if (/^[-+]?\d+(?:\.\d+)?$/u.test(trimmedInitializer)) {
654
+ return "number";
655
+ }
656
+
657
+ if ((trimmedInitializer.startsWith("\"") && trimmedInitializer.endsWith("\"")) || (trimmedInitializer.startsWith("'") && trimmedInitializer.endsWith("'"))) {
658
+ return "string";
659
+ }
660
+
661
+ return "any";
662
+ }
663
+
664
+ function parseObjectLiteralProperties(objectSource, scopeTypeMap) {
665
+ if (!objectSource.startsWith("{")) {
666
+ throw new Error("Expected object literal source to start with '{'.");
667
+ }
668
+
669
+ const properties = [];
670
+ let index = 1;
671
+
672
+ while (index < objectSource.length) {
673
+ const { jsDocBlock, nextIndex } = readLeadingJSDoc(objectSource, index);
674
+ index = nextIndex;
675
+
676
+ if (objectSource[index] === "}") {
677
+ break;
678
+ }
679
+
680
+ let propertyName = null;
681
+
682
+ if (objectSource.startsWith("async", index) && !isIdentifierPart(objectSource[index + 5] ?? "")) {
683
+ index = skipWhitespace(objectSource, index + 5);
684
+ }
685
+
686
+ if (objectSource[index] === "\"" || objectSource[index] === "'") {
687
+ const quoteCharacter = objectSource[index];
688
+ const endIndex = skipQuotedString(objectSource, index, quoteCharacter);
689
+ propertyName = objectSource.slice(index + 1, endIndex - 1);
690
+ index = endIndex;
691
+ } else if (isIdentifierStart(objectSource[index] ?? "")) {
692
+ const nameStart = index;
693
+ index += 1;
694
+
695
+ while (index < objectSource.length && isIdentifierPart(objectSource[index])) {
696
+ index += 1;
697
+ }
698
+
699
+ propertyName = objectSource.slice(nameStart, index);
700
+ }
701
+
702
+ if (!propertyName) {
703
+ index += 1;
704
+ continue;
705
+ }
706
+
707
+ index = skipWhitespaceAndComments(objectSource, index);
708
+
709
+ let typeExpression = "any";
710
+ let optional = false;
711
+
712
+ if (objectSource[index] === ":") {
713
+ index = skipWhitespaceAndComments(objectSource, index + 1);
714
+
715
+ if (objectSource[index] === "{") {
716
+ const objectEndIndex = findMatchingDelimiter(objectSource, index, "{", "}");
717
+
718
+ if (objectEndIndex === -1) {
719
+ throw new Error(`Could not find end of nested object literal for property ${propertyName}`);
720
+ }
721
+
722
+ const nestedProperties = parseObjectLiteralProperties(objectSource.slice(index, objectEndIndex + 1), scopeTypeMap);
723
+ typeExpression = buildObjectLiteralTypeExpression(nestedProperties);
724
+ index = objectEndIndex + 1;
725
+ } else {
726
+ const expressionEndIndex = findExpressionEnd(objectSource, index);
727
+ const initializerSource = objectSource.slice(index, expressionEndIndex);
728
+ typeExpression = jsDocBlock ? buildFunctionTypeExpressionFromJSDoc(jsDocBlock) : inferTypeExpressionFromInitializer(initializerSource, scopeTypeMap);
729
+ index = expressionEndIndex;
730
+ }
731
+ } else if (objectSource[index] === "(") {
732
+ const paramsEndIndex = findMatchingDelimiter(objectSource, index, "(", ")");
733
+
734
+ if (paramsEndIndex === -1) {
735
+ throw new Error(`Could not find end of parameter list for property ${propertyName}`);
736
+ }
737
+
738
+ index = skipWhitespaceAndComments(objectSource, paramsEndIndex + 1);
739
+
740
+ if (objectSource[index] === "{") {
741
+ const methodEndIndex = findMatchingDelimiter(objectSource, index, "{", "}");
742
+
743
+ if (methodEndIndex === -1) {
744
+ throw new Error(`Could not find end of method body for property ${propertyName}`);
745
+ }
746
+
747
+ index = methodEndIndex + 1;
748
+ }
749
+
750
+ typeExpression = jsDocBlock ? buildFunctionTypeExpressionFromJSDoc(jsDocBlock) : "(...args: any[]) => any";
751
+ } else {
752
+ typeExpression = scopeTypeMap.get(propertyName) ?? "any";
753
+ }
754
+
755
+ properties.push({
756
+ name: propertyName,
757
+ optional,
758
+ typeExpression
759
+ });
760
+
761
+ index = skipWhitespaceAndComments(objectSource, index);
762
+
763
+ if (objectSource[index] === ",") {
764
+ index += 1;
765
+ }
766
+ }
767
+
768
+ return properties;
769
+ }
770
+
771
+ function getNearestLeadingJSDoc(source, startIndex) {
772
+ let cursor = startIndex;
773
+
774
+ while (cursor > 0 && /\s/u.test(source[cursor - 1])) {
775
+ cursor -= 1;
776
+ }
777
+
778
+ if (!source.startsWith("*/", cursor - 2)) {
779
+ return null;
780
+ }
781
+
782
+ const blockStartIndex = source.lastIndexOf("/**", cursor - 2);
783
+ return blockStartIndex === -1 ? null : source.slice(blockStartIndex, cursor);
784
+ }
785
+
786
+ function extractCreateFunctionInfo(source, createFunctionName) {
787
+ const functionPattern = new RegExp(`export\\s+(?:async\\s+)?function\\s+${createFunctionName}\\s*\\(`);
788
+ const functionMatch = functionPattern.exec(source);
789
+
790
+ if (functionMatch) {
791
+ const functionIndex = functionMatch.index;
792
+ const functionBodyStartIndex = source.indexOf("{", functionIndex);
793
+ const functionBodyEndIndex = functionBodyStartIndex === -1 ? -1 : findMatchingDelimiter(source, functionBodyStartIndex, "{", "}");
794
+
795
+ if (functionBodyStartIndex === -1 || functionBodyEndIndex === -1) {
796
+ return null;
797
+ }
798
+
799
+ return {
800
+ jsDocBlock: getNearestLeadingJSDoc(source, functionIndex),
801
+ bodySource: source.slice(functionBodyStartIndex + 1, functionBodyEndIndex)
802
+ };
803
+ }
804
+
805
+ const constPattern = new RegExp(`export\\s+const\\s+${createFunctionName}\\s*=\\s*(?:async\\s*)?\\([^)]*\\)\\s*=>\\s*\\{`);
806
+ const constMatch = constPattern.exec(source);
807
+
808
+ if (!constMatch) {
809
+ return null;
810
+ }
811
+
812
+ const arrowBodyStartIndex = source.indexOf("{", constMatch.index);
813
+ const arrowBodyEndIndex = arrowBodyStartIndex === -1 ? -1 : findMatchingDelimiter(source, arrowBodyStartIndex, "{", "}");
814
+
815
+ if (arrowBodyStartIndex === -1 || arrowBodyEndIndex === -1) {
816
+ return null;
817
+ }
818
+
819
+ return {
820
+ jsDocBlock: getNearestLeadingJSDoc(source, constMatch.index),
821
+ bodySource: source.slice(arrowBodyStartIndex + 1, arrowBodyEndIndex)
822
+ };
823
+ }
824
+
825
+ function extractTopLevelReturnedObjectSource(functionBodySource, createFunctionName) {
826
+ let braceDepth = 0;
827
+ let bracketDepth = 0;
828
+ let parenDepth = 0;
829
+
830
+ for (let index = 0; index < functionBodySource.length; index += 1) {
831
+ const character = functionBodySource[index];
832
+ const nextCharacter = functionBodySource[index + 1];
833
+
834
+ if (character === "\"" || character === "'") {
835
+ index = skipQuotedString(functionBodySource, index, character) - 1;
836
+ continue;
837
+ }
838
+
839
+ if (character === "`") {
840
+ index = skipTemplateLiteral(functionBodySource, index) - 1;
841
+ continue;
842
+ }
843
+
844
+ if (character === "/" && nextCharacter === "*") {
845
+ index = skipBlockComment(functionBodySource, index) - 1;
846
+ continue;
847
+ }
848
+
849
+ if (character === "/" && nextCharacter === "/") {
850
+ index = skipLineComment(functionBodySource, index) - 1;
851
+ continue;
852
+ }
853
+
854
+ if (character === "{") {
855
+ braceDepth += 1;
856
+ continue;
857
+ }
858
+
859
+ if (character === "}") {
860
+ braceDepth = Math.max(0, braceDepth - 1);
861
+ continue;
862
+ }
863
+
864
+ if (character === "[") {
865
+ bracketDepth += 1;
866
+ continue;
867
+ }
868
+
869
+ if (character === "]") {
870
+ bracketDepth = Math.max(0, bracketDepth - 1);
871
+ continue;
872
+ }
873
+
874
+ if (character === "(") {
875
+ parenDepth += 1;
876
+ continue;
877
+ }
878
+
879
+ if (character === ")") {
880
+ parenDepth = Math.max(0, parenDepth - 1);
881
+ continue;
882
+ }
883
+
884
+ if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0 && functionBodySource.startsWith("return", index) && !isIdentifierPart(functionBodySource[index - 1] ?? "") && !isIdentifierPart(functionBodySource[index + 6] ?? "")) {
885
+ const objectStartIndex = skipWhitespaceAndComments(functionBodySource, index + 6);
886
+
887
+ if (functionBodySource[objectStartIndex] !== "{") {
888
+ continue;
889
+ }
890
+
891
+ const objectEndIndex = findMatchingDelimiter(functionBodySource, objectStartIndex, "{", "}");
892
+
893
+ if (objectEndIndex === -1) {
894
+ throw new Error(`Could not find end of returned object literal for ${createFunctionName}`);
895
+ }
896
+
897
+ return functionBodySource.slice(objectStartIndex, objectEndIndex + 1);
898
+ }
899
+ }
900
+
901
+ throw new Error(`Could not find top-level returned object literal for ${createFunctionName}`);
902
+ }
903
+
904
+ function inferReturnTypeObjectEntry(source, filePath, typeName, createFunctionName) {
905
+ const functionInfo = extractCreateFunctionInfo(source, createFunctionName);
906
+
907
+ if (!functionInfo) {
908
+ return null;
909
+ }
910
+
911
+ const returnedObjectSource = extractTopLevelReturnedObjectSource(functionInfo.bodySource, createFunctionName);
912
+ const scopeTypeMap = parseFunctionParameterTypes(functionInfo.jsDocBlock);
913
+ const properties = parseObjectLiteralProperties(returnedObjectSource, scopeTypeMap);
914
+
915
+ return {
916
+ kind: "object",
917
+ name: typeName,
918
+ properties,
919
+ filePath
920
+ };
921
+ }
922
+
923
+ function parseBracketedType(rest) {
924
+ if (!rest.startsWith("{")) {
925
+ return null;
926
+ }
927
+
928
+ let braceDepth = 0;
929
+ let closingBraceIndex = -1;
930
+
931
+ for (let index = 0; index < rest.length; index += 1) {
932
+ const character = rest[index];
933
+
934
+ if (character === "{") {
935
+ braceDepth += 1;
936
+ } else if (character === "}") {
937
+ braceDepth -= 1;
938
+
939
+ if (braceDepth === 0) {
940
+ closingBraceIndex = index;
941
+ break;
942
+ }
943
+ }
944
+ }
945
+
946
+ if (closingBraceIndex === -1) {
947
+ return null;
948
+ }
949
+
950
+ return {
951
+ typeExpression: rest.slice(1, closingBraceIndex).trim(),
952
+ remainder: rest.slice(closingBraceIndex + 1).trim()
953
+ };
954
+ }
955
+
956
+ function parseTagWithType(line, tagName) {
957
+ const marker = `@${tagName}`;
958
+ const tagIndex = line.indexOf(marker);
959
+
960
+ if (tagIndex === -1) {
961
+ return null;
962
+ }
963
+
964
+ const parsed = parseBracketedType(line.slice(tagIndex + marker.length).trimStart());
965
+
966
+ if (!parsed) {
967
+ return null;
968
+ }
969
+
970
+ return parsed;
971
+ }
972
+
973
+ function parseNameToken(remainder) {
974
+ const nameMatch = remainder.match(/^(\[[^\]]+\]|[^\s-]+)/);
975
+
976
+ if (!nameMatch) {
977
+ return null;
978
+ }
979
+
980
+ const rawName = nameMatch[1];
981
+ const optional = rawName.startsWith("[") && rawName.endsWith("]");
982
+ const normalizedName = optional ? rawName.slice(1, -1).replace(/=.*/, "") : rawName;
983
+ const description = remainder.slice(nameMatch[0].length).trim().replace(/^-/u, "").trim();
984
+
985
+ return {
986
+ name: normalizedName,
987
+ optional,
988
+ description
989
+ };
990
+ }
991
+
992
+ function extractPropertyEntries(normalizedBlock) {
993
+ const properties = [];
994
+
995
+ for (const line of normalizedBlock.split("\n")) {
996
+ if (!line.includes("@property")) {
997
+ continue;
998
+ }
999
+
1000
+ const parsedTag = parseTagWithType(line, "property");
1001
+
1002
+ if (!parsedTag) {
1003
+ continue;
1004
+ }
1005
+
1006
+ const parsedName = parseNameToken(parsedTag.remainder);
1007
+
1008
+ if (!parsedName) {
1009
+ continue;
1010
+ }
1011
+
1012
+ properties.push({
1013
+ name: parsedName.name,
1014
+ optional: parsedName.optional,
1015
+ description: parsedName.description,
1016
+ typeExpression: parsedTag.typeExpression
1017
+ });
1018
+ }
1019
+
1020
+ return properties;
1021
+ }
1022
+
1023
+ function extractParamEntries(normalizedBlock) {
1024
+ const params = [];
1025
+
1026
+ for (const line of normalizedBlock.split("\n")) {
1027
+ if (!line.includes("@param")) {
1028
+ continue;
1029
+ }
1030
+
1031
+ const parsedTag = parseTagWithType(line, "param");
1032
+
1033
+ if (!parsedTag) {
1034
+ continue;
1035
+ }
1036
+
1037
+ const parsedName = parseNameToken(parsedTag.remainder);
1038
+
1039
+ if (!parsedName) {
1040
+ continue;
1041
+ }
1042
+
1043
+ params.push({
1044
+ name: parsedName.name,
1045
+ optional: parsedName.optional,
1046
+ typeExpression: parsedTag.typeExpression
1047
+ });
1048
+ }
1049
+
1050
+ return params;
1051
+ }
1052
+
1053
+ function extractReturnsEntry(normalizedBlock) {
1054
+ for (const line of normalizedBlock.split("\n")) {
1055
+ if (!line.includes("@returns") && !line.includes("@return")) {
1056
+ continue;
1057
+ }
1058
+
1059
+ return parseTagWithType(line, line.includes("@returns") ? "returns" : "return");
1060
+ }
1061
+
1062
+ return null;
1063
+ }
1064
+
359
1065
  function extractTypedefEntry(normalizedBlock) {
360
1066
  const typedefIndex = normalizedBlock.indexOf("@typedef");
361
1067
 
@@ -399,10 +1105,13 @@ function extractTypedefEntry(normalizedBlock) {
399
1105
  return null;
400
1106
  }
401
1107
 
1108
+ const properties = extractPropertyEntries(normalizedBlock);
1109
+
402
1110
  return {
403
- kind: "typedef",
1111
+ kind: typeExpression === "Object" && properties.length > 0 ? "object" : "typedef",
404
1112
  name: nameMatch[1],
405
- typeExpression
1113
+ typeExpression,
1114
+ properties
406
1115
  };
407
1116
  }
408
1117
 
@@ -416,7 +1125,9 @@ function extractCallbackEntry(normalizedBlock) {
416
1125
  return {
417
1126
  kind: "callback",
418
1127
  name: callbackMatch[1],
419
- typeExpression: null
1128
+ typeExpression: null,
1129
+ params: extractParamEntries(normalizedBlock),
1130
+ returns: extractReturnsEntry(normalizedBlock)?.typeExpression ?? "void"
420
1131
  };
421
1132
  }
422
1133
 
@@ -431,6 +1142,17 @@ function extractPublicTypeEntries(source, filePath) {
431
1142
  const typedefEntry = extractTypedefEntry(block);
432
1143
 
433
1144
  if (typedefEntry) {
1145
+ const inferredCreateApiName = typedefEntry.typeExpression.match(returnTypeOfCreateApiPattern)?.[1] ?? null;
1146
+
1147
+ if (inferredCreateApiName) {
1148
+ const inferredObjectEntry = inferReturnTypeObjectEntry(source, filePath, typedefEntry.name, inferredCreateApiName);
1149
+
1150
+ if (inferredObjectEntry) {
1151
+ entries.push(inferredObjectEntry);
1152
+ continue;
1153
+ }
1154
+ }
1155
+
434
1156
  if (!isSimpleImportAlias(typedefEntry.typeExpression)) {
435
1157
  entries.push({ ...typedefEntry, filePath });
436
1158
  }
@@ -448,6 +1170,135 @@ function extractPublicTypeEntries(source, filePath) {
448
1170
  return entries;
449
1171
  }
450
1172
 
1173
+ function splitTopLevel(text, delimiterCharacter) {
1174
+ const parts = [];
1175
+ let current = "";
1176
+ let angleDepth = 0;
1177
+ let braceDepth = 0;
1178
+ let bracketDepth = 0;
1179
+ let parenDepth = 0;
1180
+
1181
+ for (const character of text) {
1182
+ if (character === "<") {
1183
+ angleDepth += 1;
1184
+ } else if (character === ">") {
1185
+ angleDepth = Math.max(0, angleDepth - 1);
1186
+ } else if (character === "{") {
1187
+ braceDepth += 1;
1188
+ } else if (character === "}") {
1189
+ braceDepth = Math.max(0, braceDepth - 1);
1190
+ } else if (character === "[") {
1191
+ bracketDepth += 1;
1192
+ } else if (character === "]") {
1193
+ bracketDepth = Math.max(0, bracketDepth - 1);
1194
+ } else if (character === "(") {
1195
+ parenDepth += 1;
1196
+ } else if (character === ")") {
1197
+ parenDepth = Math.max(0, parenDepth - 1);
1198
+ }
1199
+
1200
+ if (character === delimiterCharacter && angleDepth === 0 && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
1201
+ parts.push(current.trim());
1202
+ current = "";
1203
+ continue;
1204
+ }
1205
+
1206
+ current += character;
1207
+ }
1208
+
1209
+ if (current.trim()) {
1210
+ parts.push(current.trim());
1211
+ }
1212
+
1213
+ return parts;
1214
+ }
1215
+
1216
+ function replaceObjectGenerics(typeExpression, transformTypeExpression) {
1217
+ let result = "";
1218
+
1219
+ for (let index = 0; index < typeExpression.length; index += 1) {
1220
+ if (!typeExpression.startsWith("Object<", index)) {
1221
+ result += typeExpression[index];
1222
+ continue;
1223
+ }
1224
+
1225
+ let angleDepth = 0;
1226
+ let closingIndex = -1;
1227
+
1228
+ for (let cursor = index + "Object".length; cursor < typeExpression.length; cursor += 1) {
1229
+ const character = typeExpression[cursor];
1230
+
1231
+ if (character === "<") {
1232
+ angleDepth += 1;
1233
+ } else if (character === ">") {
1234
+ angleDepth -= 1;
1235
+
1236
+ if (angleDepth === 0) {
1237
+ closingIndex = cursor;
1238
+ break;
1239
+ }
1240
+ }
1241
+ }
1242
+
1243
+ if (closingIndex === -1) {
1244
+ result += typeExpression[index];
1245
+ continue;
1246
+ }
1247
+
1248
+ const genericContent = typeExpression.slice(index + "Object<".length, closingIndex);
1249
+ const genericParts = splitTopLevel(genericContent, ",");
1250
+
1251
+ if (genericParts.length === 2) {
1252
+ result += `Record<${transformTypeExpression(genericParts[0])}, ${transformTypeExpression(genericParts[1])}>`;
1253
+ } else {
1254
+ result += "object";
1255
+ }
1256
+
1257
+ index = closingIndex;
1258
+ }
1259
+
1260
+ return result;
1261
+ }
1262
+
1263
+ function transformTypeExpressionForDts(typeExpression, availableTypeNames) {
1264
+ const normalizedExpression = typeExpression.trim();
1265
+
1266
+ if (normalizedExpression === "*") {
1267
+ return "any";
1268
+ }
1269
+
1270
+ let transformedExpression = normalizedExpression.replace(
1271
+ /import\((?:"|')[^"']+(?:"|')\)\.([A-Za-z0-9_$]+)(\[[^\]]+\])?/g,
1272
+ (fullMatch, typeName, suffix = "") => availableTypeNames.has(typeName) ? `${typeName}${suffix}` : fullMatch
1273
+ );
1274
+
1275
+ transformedExpression = replaceObjectGenerics(transformedExpression, (innerExpression) => transformTypeExpressionForDts(innerExpression, availableTypeNames));
1276
+ transformedExpression = transformedExpression.replace(/\bObject\b/g, "object");
1277
+ transformedExpression = transformedExpression.replace(/\bfunction\b/g, "Function");
1278
+
1279
+ return transformedExpression;
1280
+ }
1281
+
1282
+ function renderTypeEntryAsDts(entry, availableTypeNames) {
1283
+ if (entry.kind === "object") {
1284
+ const lines = [`export type ${entry.name} = {`];
1285
+
1286
+ for (const property of entry.properties) {
1287
+ lines.push(` ${property.name}${property.optional ? "?" : ""}: ${transformTypeExpressionForDts(property.typeExpression, availableTypeNames)};`);
1288
+ }
1289
+
1290
+ lines.push("};", "");
1291
+ return lines;
1292
+ }
1293
+
1294
+ if (entry.kind === "callback") {
1295
+ const params = entry.params.map((param) => `${param.name}${param.optional ? "?" : ""}: ${transformTypeExpressionForDts(param.typeExpression, availableTypeNames)}`).join(", ");
1296
+ return [`export type ${entry.name} = (${params}) => ${transformTypeExpressionForDts(entry.returns, availableTypeNames)};`, ""];
1297
+ }
1298
+
1299
+ return [`export type ${entry.name} = ${transformTypeExpressionForDts(entry.typeExpression, availableTypeNames)};`, ""];
1300
+ }
1301
+
451
1302
  function buildTypeExportMap(typeEntries) {
452
1303
  const exportMap = new Map();
453
1304
 
@@ -647,61 +1498,20 @@ function replaceGeneratedBlock(source, startMarker, endMarker, generatedBlock, t
647
1498
  return `${before}${generatedBlock}${after}`;
648
1499
  }
649
1500
 
650
- function buildRootDts(dtoAliases, sourceByFilePath, allJsFilesByDirectory) {
651
- const dtoAliasNames = new Set(dtoAliases.map((alias) => alias.name));
1501
+ function buildRootDts(publicTypeEntries) {
652
1502
  const lines = [
653
1503
  "export * from \"./index.js\";",
654
1504
  ""
655
1505
  ];
656
1506
 
657
- const sourceFiles = [rootIndexPath];
658
-
659
- for (const directory of sourceDirectories) {
660
- sourceFiles.push(...allJsFilesByDirectory.get(directory));
661
- }
662
-
663
- const discoveredTypeEntries = [];
664
-
665
- for (const filePath of sourceFiles) {
666
- const source = sourceByFilePath.get(filePath);
667
- discoveredTypeEntries.push(...extractPublicTypeEntries(source, filePath));
668
- }
669
-
670
- const canonicalizedTypeEntries = discoveredTypeEntries.filter((entry) => {
671
- const relativeFilePath = toPosixRelativePath(entry.filePath);
672
- const isDtoLeafFile = relativeFilePath.startsWith("api/dtos/") && relativeFilePath !== "api/dtos/index.js";
673
-
674
- if (isDtoLeafFile && dtoAliasNames.has(entry.name)) {
675
- return false;
676
- }
677
-
678
- return true;
679
- });
680
-
681
- const typeExportMap = buildTypeExportMap(canonicalizedTypeEntries);
1507
+ const typeExportMap = buildTypeExportMap(publicTypeEntries);
682
1508
  const sortedEntries = [...typeExportMap.values()].sort((left, right) => left.name.localeCompare(right.name));
1509
+ const availableTypeNames = new Set(sortedEntries.map((entry) => entry.name));
683
1510
 
684
1511
  for (const entry of sortedEntries) {
685
- if (dtoAliasNames.has(entry.name)) {
686
- lines.push(`export type ${entry.name} = import("./api/dtos/index.js").${entry.name};`);
687
- dtoAliasNames.delete(entry.name);
688
- continue;
689
- }
690
-
691
- const relativeImportPath = `./${toPosixRelativePath(entry.filePath)}`;
692
- const dtsImportPath = toRelativeDtsImportPath(rootDtsPath, entry.filePath);
693
- lines.push(`export type ${entry.name} = import("${dtsImportPath}").${entry.name};`);
1512
+ lines.push(...renderTypeEntryAsDts(entry, availableTypeNames));
694
1513
  }
695
1514
 
696
- for (const alias of dtoAliases) {
697
- if (!dtoAliasNames.has(alias.name)) {
698
- continue;
699
- }
700
-
701
- lines.push(`export type ${alias.name} = import("./api/dtos/index.js").${alias.name};`);
702
- }
703
-
704
- lines.push("");
705
1515
  return `${lines.join("\n")}\n`;
706
1516
  }
707
1517
 
@@ -725,6 +1535,12 @@ for (const filePath of [rootIndexPath, ...dtoLeafFiles, ...businessLeafFiles, ..
725
1535
  const dtoEntries = buildTypeExportMap(dtoLeafFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)));
726
1536
  const dtoAliases = buildDtoAliases(dtoEntries);
727
1537
  const businessAliases = buildBusinessAliases(businessLeafFiles, sourceByFilePath);
1538
+ const publicTypeEntries = [
1539
+ ...dtoLeafFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)),
1540
+ ...businessLeafFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)),
1541
+ ...otherApiJsFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)),
1542
+ ...uiLeafFiles.filter((filePath) => path.extname(filePath) === ".js").flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath))
1543
+ ];
728
1544
 
729
1545
  const generatedDtoIndexSource = buildDtoIndexSource(dtoAliases);
730
1546
  const generatedBusinessIndexSource = buildBusinessIndexSource(businessAliases);
@@ -768,12 +1584,7 @@ const updatedRootIndexSource = replaceGeneratedBlock(rootIndexWithDtoAliases, bu
768
1584
  await writeFile(rootIndexPath, updatedRootIndexSource);
769
1585
  sourceByFilePath.set(rootIndexPath, updatedRootIndexSource);
770
1586
 
771
- const allJsFilesByDirectory = new Map([
772
- ["api", [...dtoLeafFiles, ...businessLeafFiles, ...otherApiJsFiles].sort()],
773
- ["ui", uiLeafFiles.filter((filePath) => path.extname(filePath) === ".js").sort()]
774
- ]);
775
-
776
- const generatedRootDts = buildRootDts(dtoAliases, sourceByFilePath, allJsFilesByDirectory);
1587
+ const generatedRootDts = buildRootDts(publicTypeEntries);
777
1588
  await writeFile(rootDtsPath, generatedRootDts);
778
1589
 
779
1590
  console.log(`Updated ${toPosixRelativePath(dtoIndexPath)} with ${dtoAliases.length} generated DTO aliases.`);