@gandalan/weblibs 1.5.23 → 1.5.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gandalan/weblibs",
3
- "version": "1.5.23",
3
+ "version": "1.5.24",
4
4
  "description": "WebLibs for Gandalan JS/TS projects",
5
5
  "keywords": [
6
6
  "gandalan"
@@ -356,6 +356,148 @@ function normalizeJSDocBlock(block) {
356
356
  .trim();
357
357
  }
358
358
 
359
+ function parseBracketedType(rest) {
360
+ if (!rest.startsWith("{")) {
361
+ return null;
362
+ }
363
+
364
+ let braceDepth = 0;
365
+ let closingBraceIndex = -1;
366
+
367
+ for (let index = 0; index < rest.length; index += 1) {
368
+ const character = rest[index];
369
+
370
+ if (character === "{") {
371
+ braceDepth += 1;
372
+ } else if (character === "}") {
373
+ braceDepth -= 1;
374
+
375
+ if (braceDepth === 0) {
376
+ closingBraceIndex = index;
377
+ break;
378
+ }
379
+ }
380
+ }
381
+
382
+ if (closingBraceIndex === -1) {
383
+ return null;
384
+ }
385
+
386
+ return {
387
+ typeExpression: rest.slice(1, closingBraceIndex).trim(),
388
+ remainder: rest.slice(closingBraceIndex + 1).trim()
389
+ };
390
+ }
391
+
392
+ function parseTagWithType(line, tagName) {
393
+ const marker = `@${tagName}`;
394
+ const tagIndex = line.indexOf(marker);
395
+
396
+ if (tagIndex === -1) {
397
+ return null;
398
+ }
399
+
400
+ const parsed = parseBracketedType(line.slice(tagIndex + marker.length).trimStart());
401
+
402
+ if (!parsed) {
403
+ return null;
404
+ }
405
+
406
+ return parsed;
407
+ }
408
+
409
+ function parseNameToken(remainder) {
410
+ const nameMatch = remainder.match(/^(\[[^\]]+\]|[^\s-]+)/);
411
+
412
+ if (!nameMatch) {
413
+ return null;
414
+ }
415
+
416
+ const rawName = nameMatch[1];
417
+ const optional = rawName.startsWith("[") && rawName.endsWith("]");
418
+ const normalizedName = optional ? rawName.slice(1, -1).replace(/=.*/, "") : rawName;
419
+ const description = remainder.slice(nameMatch[0].length).trim().replace(/^-/u, "").trim();
420
+
421
+ return {
422
+ name: normalizedName,
423
+ optional,
424
+ description
425
+ };
426
+ }
427
+
428
+ function extractPropertyEntries(normalizedBlock) {
429
+ const properties = [];
430
+
431
+ for (const line of normalizedBlock.split("\n")) {
432
+ if (!line.includes("@property")) {
433
+ continue;
434
+ }
435
+
436
+ const parsedTag = parseTagWithType(line, "property");
437
+
438
+ if (!parsedTag) {
439
+ continue;
440
+ }
441
+
442
+ const parsedName = parseNameToken(parsedTag.remainder);
443
+
444
+ if (!parsedName) {
445
+ continue;
446
+ }
447
+
448
+ properties.push({
449
+ name: parsedName.name,
450
+ optional: parsedName.optional,
451
+ description: parsedName.description,
452
+ typeExpression: parsedTag.typeExpression
453
+ });
454
+ }
455
+
456
+ return properties;
457
+ }
458
+
459
+ function extractParamEntries(normalizedBlock) {
460
+ const params = [];
461
+
462
+ for (const line of normalizedBlock.split("\n")) {
463
+ if (!line.includes("@param")) {
464
+ continue;
465
+ }
466
+
467
+ const parsedTag = parseTagWithType(line, "param");
468
+
469
+ if (!parsedTag) {
470
+ continue;
471
+ }
472
+
473
+ const parsedName = parseNameToken(parsedTag.remainder);
474
+
475
+ if (!parsedName) {
476
+ continue;
477
+ }
478
+
479
+ params.push({
480
+ name: parsedName.name,
481
+ optional: parsedName.optional,
482
+ typeExpression: parsedTag.typeExpression
483
+ });
484
+ }
485
+
486
+ return params;
487
+ }
488
+
489
+ function extractReturnsEntry(normalizedBlock) {
490
+ for (const line of normalizedBlock.split("\n")) {
491
+ if (!line.includes("@returns") && !line.includes("@return")) {
492
+ continue;
493
+ }
494
+
495
+ return parseTagWithType(line, line.includes("@returns") ? "returns" : "return");
496
+ }
497
+
498
+ return null;
499
+ }
500
+
359
501
  function extractTypedefEntry(normalizedBlock) {
360
502
  const typedefIndex = normalizedBlock.indexOf("@typedef");
361
503
 
@@ -399,10 +541,13 @@ function extractTypedefEntry(normalizedBlock) {
399
541
  return null;
400
542
  }
401
543
 
544
+ const properties = extractPropertyEntries(normalizedBlock);
545
+
402
546
  return {
403
- kind: "typedef",
547
+ kind: typeExpression === "Object" && properties.length > 0 ? "object" : "typedef",
404
548
  name: nameMatch[1],
405
- typeExpression
549
+ typeExpression,
550
+ properties
406
551
  };
407
552
  }
408
553
 
@@ -416,7 +561,9 @@ function extractCallbackEntry(normalizedBlock) {
416
561
  return {
417
562
  kind: "callback",
418
563
  name: callbackMatch[1],
419
- typeExpression: null
564
+ typeExpression: null,
565
+ params: extractParamEntries(normalizedBlock),
566
+ returns: extractReturnsEntry(normalizedBlock)?.typeExpression ?? "void"
420
567
  };
421
568
  }
422
569
 
@@ -448,6 +595,135 @@ function extractPublicTypeEntries(source, filePath) {
448
595
  return entries;
449
596
  }
450
597
 
598
+ function splitTopLevel(text, delimiterCharacter) {
599
+ const parts = [];
600
+ let current = "";
601
+ let angleDepth = 0;
602
+ let braceDepth = 0;
603
+ let bracketDepth = 0;
604
+ let parenDepth = 0;
605
+
606
+ for (const character of text) {
607
+ if (character === "<") {
608
+ angleDepth += 1;
609
+ } else if (character === ">") {
610
+ angleDepth = Math.max(0, angleDepth - 1);
611
+ } else if (character === "{") {
612
+ braceDepth += 1;
613
+ } else if (character === "}") {
614
+ braceDepth = Math.max(0, braceDepth - 1);
615
+ } else if (character === "[") {
616
+ bracketDepth += 1;
617
+ } else if (character === "]") {
618
+ bracketDepth = Math.max(0, bracketDepth - 1);
619
+ } else if (character === "(") {
620
+ parenDepth += 1;
621
+ } else if (character === ")") {
622
+ parenDepth = Math.max(0, parenDepth - 1);
623
+ }
624
+
625
+ if (character === delimiterCharacter && angleDepth === 0 && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
626
+ parts.push(current.trim());
627
+ current = "";
628
+ continue;
629
+ }
630
+
631
+ current += character;
632
+ }
633
+
634
+ if (current.trim()) {
635
+ parts.push(current.trim());
636
+ }
637
+
638
+ return parts;
639
+ }
640
+
641
+ function replaceObjectGenerics(typeExpression, transformTypeExpression) {
642
+ let result = "";
643
+
644
+ for (let index = 0; index < typeExpression.length; index += 1) {
645
+ if (!typeExpression.startsWith("Object<", index)) {
646
+ result += typeExpression[index];
647
+ continue;
648
+ }
649
+
650
+ let angleDepth = 0;
651
+ let closingIndex = -1;
652
+
653
+ for (let cursor = index + "Object".length; cursor < typeExpression.length; cursor += 1) {
654
+ const character = typeExpression[cursor];
655
+
656
+ if (character === "<") {
657
+ angleDepth += 1;
658
+ } else if (character === ">") {
659
+ angleDepth -= 1;
660
+
661
+ if (angleDepth === 0) {
662
+ closingIndex = cursor;
663
+ break;
664
+ }
665
+ }
666
+ }
667
+
668
+ if (closingIndex === -1) {
669
+ result += typeExpression[index];
670
+ continue;
671
+ }
672
+
673
+ const genericContent = typeExpression.slice(index + "Object<".length, closingIndex);
674
+ const genericParts = splitTopLevel(genericContent, ",");
675
+
676
+ if (genericParts.length === 2) {
677
+ result += `Record<${transformTypeExpression(genericParts[0])}, ${transformTypeExpression(genericParts[1])}>`;
678
+ } else {
679
+ result += "object";
680
+ }
681
+
682
+ index = closingIndex;
683
+ }
684
+
685
+ return result;
686
+ }
687
+
688
+ function transformTypeExpressionForDts(typeExpression, availableTypeNames) {
689
+ const normalizedExpression = typeExpression.trim();
690
+
691
+ if (normalizedExpression === "*") {
692
+ return "any";
693
+ }
694
+
695
+ let transformedExpression = normalizedExpression.replace(
696
+ /import\((?:"|')[^"']+(?:"|')\)\.([A-Za-z0-9_$]+)(\[[^\]]+\])?/g,
697
+ (fullMatch, typeName, suffix = "") => availableTypeNames.has(typeName) ? `${typeName}${suffix}` : fullMatch
698
+ );
699
+
700
+ transformedExpression = replaceObjectGenerics(transformedExpression, (innerExpression) => transformTypeExpressionForDts(innerExpression, availableTypeNames));
701
+ transformedExpression = transformedExpression.replace(/\bObject\b/g, "object");
702
+ transformedExpression = transformedExpression.replace(/\bfunction\b/g, "Function");
703
+
704
+ return transformedExpression;
705
+ }
706
+
707
+ function renderTypeEntryAsDts(entry, availableTypeNames) {
708
+ if (entry.kind === "object") {
709
+ const lines = [`export type ${entry.name} = {`];
710
+
711
+ for (const property of entry.properties) {
712
+ lines.push(` ${property.name}${property.optional ? "?" : ""}: ${transformTypeExpressionForDts(property.typeExpression, availableTypeNames)};`);
713
+ }
714
+
715
+ lines.push("};", "");
716
+ return lines;
717
+ }
718
+
719
+ if (entry.kind === "callback") {
720
+ const params = entry.params.map((param) => `${param.name}${param.optional ? "?" : ""}: ${transformTypeExpressionForDts(param.typeExpression, availableTypeNames)}`).join(", ");
721
+ return [`export type ${entry.name} = (${params}) => ${transformTypeExpressionForDts(entry.returns, availableTypeNames)};`, ""];
722
+ }
723
+
724
+ return [`export type ${entry.name} = ${transformTypeExpressionForDts(entry.typeExpression, availableTypeNames)};`, ""];
725
+ }
726
+
451
727
  function buildTypeExportMap(typeEntries) {
452
728
  const exportMap = new Map();
453
729
 
@@ -647,61 +923,20 @@ function replaceGeneratedBlock(source, startMarker, endMarker, generatedBlock, t
647
923
  return `${before}${generatedBlock}${after}`;
648
924
  }
649
925
 
650
- function buildRootDts(dtoAliases, sourceByFilePath, allJsFilesByDirectory) {
651
- const dtoAliasNames = new Set(dtoAliases.map((alias) => alias.name));
926
+ function buildRootDts(publicTypeEntries) {
652
927
  const lines = [
653
928
  "export * from \"./index.js\";",
654
929
  ""
655
930
  ];
656
931
 
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);
932
+ const typeExportMap = buildTypeExportMap(publicTypeEntries);
682
933
  const sortedEntries = [...typeExportMap.values()].sort((left, right) => left.name.localeCompare(right.name));
934
+ const availableTypeNames = new Set(sortedEntries.map((entry) => entry.name));
683
935
 
684
936
  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};`);
694
- }
695
-
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};`);
937
+ lines.push(...renderTypeEntryAsDts(entry, availableTypeNames));
702
938
  }
703
939
 
704
- lines.push("");
705
940
  return `${lines.join("\n")}\n`;
706
941
  }
707
942
 
@@ -725,6 +960,12 @@ for (const filePath of [rootIndexPath, ...dtoLeafFiles, ...businessLeafFiles, ..
725
960
  const dtoEntries = buildTypeExportMap(dtoLeafFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)));
726
961
  const dtoAliases = buildDtoAliases(dtoEntries);
727
962
  const businessAliases = buildBusinessAliases(businessLeafFiles, sourceByFilePath);
963
+ const publicTypeEntries = [
964
+ ...dtoLeafFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)),
965
+ ...businessLeafFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)),
966
+ ...otherApiJsFiles.flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath)),
967
+ ...uiLeafFiles.filter((filePath) => path.extname(filePath) === ".js").flatMap((filePath) => extractPublicTypeEntries(sourceByFilePath.get(filePath), filePath))
968
+ ];
728
969
 
729
970
  const generatedDtoIndexSource = buildDtoIndexSource(dtoAliases);
730
971
  const generatedBusinessIndexSource = buildBusinessIndexSource(businessAliases);
@@ -768,12 +1009,7 @@ const updatedRootIndexSource = replaceGeneratedBlock(rootIndexWithDtoAliases, bu
768
1009
  await writeFile(rootIndexPath, updatedRootIndexSource);
769
1010
  sourceByFilePath.set(rootIndexPath, updatedRootIndexSource);
770
1011
 
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);
1012
+ const generatedRootDts = buildRootDts(publicTypeEntries);
777
1013
  await writeFile(rootDtsPath, generatedRootDts);
778
1014
 
779
1015
  console.log(`Updated ${toPosixRelativePath(dtoIndexPath)} with ${dtoAliases.length} generated DTO aliases.`);