@openpkg-ts/extract 0.11.4 → 0.12.0

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/bin/tspec.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  extract
4
- } from "../shared/chunk-twaykyxs.js";
4
+ } from "../shared/chunk-wddga8ye.js";
5
5
 
6
6
  // src/cli/spec.ts
7
7
  import * as fs from "node:fs";
@@ -1,4 +1,6 @@
1
1
  // src/ast/registry.ts
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
2
4
  import ts from "typescript";
3
5
  var PRIMITIVES = new Set([
4
6
  "string",
@@ -86,6 +88,54 @@ function isGenericTypeParameter(name) {
86
88
  return true;
87
89
  return false;
88
90
  }
91
+ function isExternalType(decl) {
92
+ const sourceFile = decl.getSourceFile();
93
+ if (!sourceFile)
94
+ return false;
95
+ return sourceFile.fileName.includes("node_modules");
96
+ }
97
+ function extractPackageName(filePath) {
98
+ const nmIndex = filePath.lastIndexOf("node_modules");
99
+ if (nmIndex === -1)
100
+ return;
101
+ const afterNm = filePath.slice(nmIndex + "node_modules/".length);
102
+ const parts = afterNm.split("/");
103
+ if (parts[0].startsWith("@") && parts.length >= 2) {
104
+ return `${parts[0]}/${parts[1]}`;
105
+ }
106
+ return parts[0];
107
+ }
108
+ function getPackageVersion(filePath, packageName) {
109
+ const nmIndex = filePath.lastIndexOf("node_modules");
110
+ if (nmIndex === -1)
111
+ return;
112
+ const nmDir = filePath.slice(0, nmIndex + "node_modules".length);
113
+ const pkgJsonPath = path.join(nmDir, packageName, "package.json");
114
+ try {
115
+ if (fs.existsSync(pkgJsonPath)) {
116
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
117
+ return pkg.version;
118
+ }
119
+ } catch {}
120
+ return;
121
+ }
122
+ function buildExternalSource(decl) {
123
+ const sourceFile = decl.getSourceFile();
124
+ if (!sourceFile)
125
+ return;
126
+ const filePath = sourceFile.fileName;
127
+ if (!filePath.includes("node_modules"))
128
+ return;
129
+ const packageName = extractPackageName(filePath);
130
+ if (!packageName)
131
+ return;
132
+ const version = getPackageVersion(filePath, packageName);
133
+ return {
134
+ file: filePath,
135
+ package: packageName,
136
+ version
137
+ };
138
+ }
89
139
 
90
140
  class TypeRegistry {
91
141
  types = new Map;
@@ -141,6 +191,7 @@ class TypeRegistry {
141
191
  const name = symbol.getName();
142
192
  const decl = symbol.declarations?.[0];
143
193
  let kind = "type";
194
+ const external = decl ? isExternalType(decl) : false;
144
195
  if (decl) {
145
196
  if (ts.isClassDeclaration(decl))
146
197
  kind = "class";
@@ -149,12 +200,18 @@ class TypeRegistry {
149
200
  else if (ts.isEnumDeclaration(decl))
150
201
  kind = "enum";
151
202
  }
203
+ if (external) {
204
+ kind = "external";
205
+ }
152
206
  const typeString = checker.typeToString(type);
207
+ const source = decl ? buildExternalSource(decl) : undefined;
153
208
  return {
154
209
  id: name,
155
210
  name,
156
211
  kind,
157
- type: typeString !== name ? typeString : undefined
212
+ type: typeString !== name ? typeString : undefined,
213
+ ...external ? { external: true } : {},
214
+ ...source ? { source } : {}
158
215
  };
159
216
  }
160
217
  registerFromSymbol(symbol, checker) {
@@ -218,9 +275,23 @@ function getSourceLocation(node, sourceFile) {
218
275
  line: line + 1
219
276
  };
220
277
  }
278
+ function getParamDescription(propertyName, jsdocTags, inferredAlias) {
279
+ for (const tag of jsdocTags) {
280
+ if (tag.tagName.text !== "param")
281
+ continue;
282
+ const paramTag = tag;
283
+ const tagParamName = paramTag.name?.getText() ?? "";
284
+ const isMatch = tagParamName === propertyName || inferredAlias && tagParamName === `${inferredAlias}.${propertyName}` || tagParamName.endsWith(`.${propertyName}`);
285
+ if (isMatch) {
286
+ const comment = typeof tag.comment === "string" ? tag.comment : ts2.getTextOfJSDocComment(tag.comment);
287
+ return comment?.trim() || undefined;
288
+ }
289
+ }
290
+ return;
291
+ }
221
292
 
222
293
  // src/compiler/program.ts
223
- import * as path from "node:path";
294
+ import * as path2 from "node:path";
224
295
  import ts3 from "typescript";
225
296
  var DEFAULT_COMPILER_OPTIONS = {
226
297
  target: ts3.ScriptTarget.Latest,
@@ -231,14 +302,14 @@ var DEFAULT_COMPILER_OPTIONS = {
231
302
  };
232
303
  function createProgram({
233
304
  entryFile,
234
- baseDir = path.dirname(entryFile),
305
+ baseDir = path2.dirname(entryFile),
235
306
  content
236
307
  }) {
237
308
  const configPath = ts3.findConfigFile(baseDir, ts3.sys.fileExists, "tsconfig.json");
238
309
  let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
239
310
  if (configPath) {
240
311
  const configFile = ts3.readConfigFile(configPath, ts3.sys.readFile);
241
- const parsedConfig = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path.dirname(configPath));
312
+ const parsedConfig = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path2.dirname(configPath));
242
313
  compilerOptions = { ...compilerOptions, ...parsedConfig.options };
243
314
  }
244
315
  const allowJsVal = compilerOptions.allowJs;
@@ -319,18 +390,413 @@ function serializeEnum(node, ctx) {
319
390
  };
320
391
  }
321
392
 
393
+ // src/types/schema-builder.ts
394
+ import ts4 from "typescript";
395
+ var PRIMITIVES2 = new Set([
396
+ "string",
397
+ "number",
398
+ "boolean",
399
+ "void",
400
+ "undefined",
401
+ "null",
402
+ "any",
403
+ "unknown",
404
+ "never",
405
+ "object",
406
+ "symbol",
407
+ "bigint"
408
+ ]);
409
+ var BUILTIN_GENERICS = new Set([
410
+ "Array",
411
+ "ReadonlyArray",
412
+ "Promise",
413
+ "PromiseLike",
414
+ "Map",
415
+ "Set",
416
+ "WeakMap",
417
+ "WeakSet",
418
+ "Iterable",
419
+ "Iterator",
420
+ "IterableIterator",
421
+ "AsyncIterable",
422
+ "AsyncIterator",
423
+ "AsyncIterableIterator",
424
+ "Generator",
425
+ "AsyncGenerator",
426
+ "Partial",
427
+ "Required",
428
+ "Readonly",
429
+ "Pick",
430
+ "Omit",
431
+ "Record",
432
+ "Exclude",
433
+ "Extract",
434
+ "NonNullable",
435
+ "Parameters",
436
+ "ReturnType",
437
+ "ConstructorParameters",
438
+ "InstanceType",
439
+ "Awaited"
440
+ ]);
441
+ var BUILTIN_TYPES = new Set([
442
+ "Date",
443
+ "RegExp",
444
+ "Error",
445
+ "Function",
446
+ "ArrayBuffer",
447
+ "SharedArrayBuffer",
448
+ "DataView",
449
+ "Uint8Array",
450
+ "Int8Array",
451
+ "Uint16Array",
452
+ "Int16Array",
453
+ "Uint32Array",
454
+ "Int32Array",
455
+ "Float32Array",
456
+ "Float64Array",
457
+ "BigInt64Array",
458
+ "BigUint64Array"
459
+ ]);
460
+ function isPrimitiveName(name) {
461
+ return PRIMITIVES2.has(name);
462
+ }
463
+ function isBuiltinGeneric(name) {
464
+ return BUILTIN_GENERICS.has(name);
465
+ }
466
+ function isAnonymous(type) {
467
+ const symbol = type.getSymbol() || type.aliasSymbol;
468
+ if (!symbol)
469
+ return true;
470
+ const name = symbol.getName();
471
+ return name.startsWith("__") || name === "";
472
+ }
473
+ function withDepth(ctx, fn) {
474
+ ctx.currentDepth++;
475
+ try {
476
+ return fn();
477
+ } finally {
478
+ ctx.currentDepth--;
479
+ }
480
+ }
481
+ function isAtMaxDepth(ctx) {
482
+ if (!ctx)
483
+ return false;
484
+ return ctx.currentDepth >= ctx.maxTypeDepth;
485
+ }
486
+ function buildSchema(type, checker, ctx, _depth = 0) {
487
+ if (isAtMaxDepth(ctx)) {
488
+ return { type: checker.typeToString(type) };
489
+ }
490
+ if (ctx && ctx.visitedTypes.has(type)) {
491
+ const symbol2 = type.getSymbol() || type.aliasSymbol;
492
+ if (symbol2) {
493
+ return { $ref: symbol2.getName() };
494
+ }
495
+ return { type: checker.typeToString(type) };
496
+ }
497
+ if (type.flags & ts4.TypeFlags.String)
498
+ return { type: "string" };
499
+ if (type.flags & ts4.TypeFlags.Number)
500
+ return { type: "number" };
501
+ if (type.flags & ts4.TypeFlags.Boolean)
502
+ return { type: "boolean" };
503
+ if (type.flags & ts4.TypeFlags.Undefined)
504
+ return { type: "undefined" };
505
+ if (type.flags & ts4.TypeFlags.Null)
506
+ return { type: "null" };
507
+ if (type.flags & ts4.TypeFlags.Void)
508
+ return { type: "void" };
509
+ if (type.flags & ts4.TypeFlags.Any)
510
+ return { type: "any" };
511
+ if (type.flags & ts4.TypeFlags.Unknown)
512
+ return { type: "unknown" };
513
+ if (type.flags & ts4.TypeFlags.Never)
514
+ return { type: "never" };
515
+ if (type.flags & ts4.TypeFlags.BigInt)
516
+ return { type: "bigint" };
517
+ if (type.flags & ts4.TypeFlags.ESSymbol)
518
+ return { type: "symbol" };
519
+ if (type.flags & ts4.TypeFlags.StringLiteral) {
520
+ const literal = type.value;
521
+ return { type: "string", enum: [literal] };
522
+ }
523
+ if (type.flags & ts4.TypeFlags.NumberLiteral) {
524
+ const literal = type.value;
525
+ return { type: "number", enum: [literal] };
526
+ }
527
+ if (type.flags & ts4.TypeFlags.BooleanLiteral) {
528
+ const intrinsicName = type.intrinsicName;
529
+ return { type: "boolean", enum: [intrinsicName === "true"] };
530
+ }
531
+ if (type.isUnion()) {
532
+ const types = type.types;
533
+ const allStringLiterals = types.every((t) => t.flags & ts4.TypeFlags.StringLiteral);
534
+ if (allStringLiterals) {
535
+ const enumValues = types.map((t) => t.value);
536
+ return { type: "string", enum: enumValues };
537
+ }
538
+ const allNumberLiterals = types.every((t) => t.flags & ts4.TypeFlags.NumberLiteral);
539
+ if (allNumberLiterals) {
540
+ const enumValues = types.map((t) => t.value);
541
+ return { type: "number", enum: enumValues };
542
+ }
543
+ if (ctx) {
544
+ return withDepth(ctx, () => ({
545
+ anyOf: types.map((t) => buildSchema(t, checker, ctx))
546
+ }));
547
+ }
548
+ return { anyOf: types.map((t) => buildSchema(t, checker, ctx)) };
549
+ }
550
+ if (type.isIntersection()) {
551
+ if (ctx) {
552
+ return withDepth(ctx, () => ({
553
+ allOf: type.types.map((t) => buildSchema(t, checker, ctx))
554
+ }));
555
+ }
556
+ return { allOf: type.types.map((t) => buildSchema(t, checker, ctx)) };
557
+ }
558
+ if (checker.isArrayType(type)) {
559
+ const typeRef2 = type;
560
+ const elementType = typeRef2.typeArguments?.[0];
561
+ if (elementType) {
562
+ if (ctx) {
563
+ return withDepth(ctx, () => ({
564
+ type: "array",
565
+ items: buildSchema(elementType, checker, ctx)
566
+ }));
567
+ }
568
+ return { type: "array", items: buildSchema(elementType, checker, ctx) };
569
+ }
570
+ return { type: "array" };
571
+ }
572
+ if (checker.isTupleType(type)) {
573
+ const typeRef2 = type;
574
+ const elementTypes = typeRef2.typeArguments ?? [];
575
+ if (ctx) {
576
+ return withDepth(ctx, () => ({
577
+ type: "tuple",
578
+ items: elementTypes.map((t) => buildSchema(t, checker, ctx)),
579
+ minItems: elementTypes.length,
580
+ maxItems: elementTypes.length
581
+ }));
582
+ }
583
+ return {
584
+ type: "tuple",
585
+ items: elementTypes.map((t) => buildSchema(t, checker, ctx)),
586
+ minItems: elementTypes.length,
587
+ maxItems: elementTypes.length
588
+ };
589
+ }
590
+ const typeRef = type;
591
+ if (typeRef.target && typeRef.typeArguments && typeRef.typeArguments.length > 0) {
592
+ const symbol2 = typeRef.target.getSymbol();
593
+ const name = symbol2?.getName();
594
+ if (name && (isBuiltinGeneric(name) || !isAnonymous(typeRef.target))) {
595
+ if (ctx) {
596
+ return withDepth(ctx, () => ({
597
+ $ref: name,
598
+ typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
599
+ }));
600
+ }
601
+ return {
602
+ $ref: name,
603
+ typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
604
+ };
605
+ }
606
+ }
607
+ const symbol = type.getSymbol() || type.aliasSymbol;
608
+ if (symbol && !isAnonymous(type)) {
609
+ const name = symbol.getName();
610
+ if (isPrimitiveName(name)) {
611
+ return { type: name };
612
+ }
613
+ if (BUILTIN_TYPES.has(name)) {
614
+ return { $ref: name };
615
+ }
616
+ if (!name.startsWith("__")) {
617
+ return { $ref: name };
618
+ }
619
+ }
620
+ if (type.flags & ts4.TypeFlags.Object) {
621
+ const objectType = type;
622
+ const callSignatures = type.getCallSignatures();
623
+ if (callSignatures.length > 0) {
624
+ return buildFunctionSchema(callSignatures, checker, ctx);
625
+ }
626
+ const properties = type.getProperties();
627
+ if (properties.length > 0 || objectType.objectFlags & ts4.ObjectFlags.Anonymous) {
628
+ return buildObjectSchema(properties, checker, ctx);
629
+ }
630
+ }
631
+ return { type: checker.typeToString(type) };
632
+ }
633
+ function buildFunctionSchema(callSignatures, checker, ctx) {
634
+ const buildSignatures = () => {
635
+ const signatures = callSignatures.map((sig) => {
636
+ const params = sig.getParameters().map((param) => {
637
+ const paramType = checker.getTypeOfSymbolAtLocation(param, param.valueDeclaration);
638
+ return {
639
+ name: param.getName(),
640
+ schema: buildSchema(paramType, checker, ctx),
641
+ required: !(param.flags & ts4.SymbolFlags.Optional)
642
+ };
643
+ });
644
+ const returnType = checker.getReturnTypeOfSignature(sig);
645
+ return {
646
+ parameters: params,
647
+ returns: {
648
+ schema: buildSchema(returnType, checker, ctx)
649
+ }
650
+ };
651
+ });
652
+ return signatures;
653
+ };
654
+ if (ctx) {
655
+ return withDepth(ctx, () => ({ type: "function", signatures: buildSignatures() }));
656
+ }
657
+ return { type: "function", signatures: buildSignatures() };
658
+ }
659
+ function buildObjectSchema(properties, checker, ctx) {
660
+ const buildProps = () => {
661
+ const props = {};
662
+ const required = [];
663
+ for (const prop of properties) {
664
+ const propName = prop.getName();
665
+ if (propName.startsWith("_"))
666
+ continue;
667
+ const propType = checker.getTypeOfSymbol(prop);
668
+ props[propName] = buildSchema(propType, checker, ctx);
669
+ if (!(prop.flags & ts4.SymbolFlags.Optional)) {
670
+ required.push(propName);
671
+ }
672
+ }
673
+ return {
674
+ type: "object",
675
+ properties: props,
676
+ ...required.length > 0 ? { required } : {}
677
+ };
678
+ };
679
+ if (ctx) {
680
+ return withDepth(ctx, buildProps);
681
+ }
682
+ return buildProps();
683
+ }
684
+
322
685
  // src/types/parameters.ts
686
+ import ts5 from "typescript";
323
687
  function extractParameters(signature, ctx) {
324
- const { typeChecker: checker, typeRegistry, exportedIds } = ctx;
325
- return signature.getParameters().map((param) => {
326
- const type = checker.getTypeOfSymbolAtLocation(param, param.valueDeclaration);
327
- registerReferencedTypes(type, ctx);
328
- return {
329
- name: param.getName(),
330
- schema: { type: checker.typeToString(type) },
331
- required: !(param.flags & 16777216)
688
+ const { typeChecker: checker } = ctx;
689
+ const result = [];
690
+ const signatureDecl = signature.getDeclaration();
691
+ const jsdocTags = signatureDecl ? ts5.getJSDocTags(signatureDecl) : [];
692
+ for (const param of signature.getParameters()) {
693
+ const decl = param.valueDeclaration;
694
+ const type = checker.getTypeOfSymbolAtLocation(param, decl ?? param.valueDeclaration);
695
+ if (decl && ts5.isObjectBindingPattern(decl.name)) {
696
+ const expandedParams = expandBindingPattern(decl, type, jsdocTags, ctx);
697
+ result.push(...expandedParams);
698
+ } else {
699
+ registerReferencedTypes(type, ctx);
700
+ result.push({
701
+ name: param.getName(),
702
+ schema: buildSchema(type, checker, ctx),
703
+ required: !(param.flags & 16777216)
704
+ });
705
+ }
706
+ }
707
+ return result;
708
+ }
709
+ function expandBindingPattern(paramDecl, paramType, jsdocTags, ctx) {
710
+ const { typeChecker: checker } = ctx;
711
+ const result = [];
712
+ const bindingPattern = paramDecl.name;
713
+ const allProperties = getEffectiveProperties(paramType, checker);
714
+ const inferredAlias = inferParamAlias(jsdocTags);
715
+ for (const element of bindingPattern.elements) {
716
+ if (!ts5.isBindingElement(element))
717
+ continue;
718
+ const propertyName = element.propertyName ? ts5.isIdentifier(element.propertyName) ? element.propertyName.text : element.propertyName.getText() : ts5.isIdentifier(element.name) ? element.name.text : element.name.getText();
719
+ const propSymbol = allProperties.get(propertyName);
720
+ if (!propSymbol)
721
+ continue;
722
+ const propType = checker.getTypeOfSymbol(propSymbol);
723
+ registerReferencedTypes(propType, ctx);
724
+ const isOptional = !!(propSymbol.flags & ts5.SymbolFlags.Optional) || element.initializer !== undefined;
725
+ const description = getParamDescription(propertyName, jsdocTags, inferredAlias);
726
+ const param = {
727
+ name: propertyName,
728
+ schema: buildSchema(propType, checker, ctx),
729
+ required: !isOptional
332
730
  };
333
- });
731
+ if (description) {
732
+ param.description = description;
733
+ }
734
+ if (element.initializer) {
735
+ param.default = extractDefaultValue(element.initializer);
736
+ }
737
+ result.push(param);
738
+ }
739
+ return result;
740
+ }
741
+ function getEffectiveProperties(type, _checker) {
742
+ const properties = new Map;
743
+ if (type.isIntersection()) {
744
+ for (const subType of type.types) {
745
+ for (const prop of subType.getProperties()) {
746
+ properties.set(prop.getName(), prop);
747
+ }
748
+ }
749
+ } else {
750
+ for (const prop of type.getProperties()) {
751
+ properties.set(prop.getName(), prop);
752
+ }
753
+ }
754
+ return properties;
755
+ }
756
+ function inferParamAlias(jsdocTags) {
757
+ const prefixes = [];
758
+ for (const tag of jsdocTags) {
759
+ if (tag.tagName.text !== "param")
760
+ continue;
761
+ const tagText = typeof tag.comment === "string" ? tag.comment : ts5.getTextOfJSDocComment(tag.comment) ?? "";
762
+ const paramTag = tag;
763
+ const paramName = paramTag.name?.getText() ?? "";
764
+ if (paramName.includes(".")) {
765
+ const prefix = paramName.split(".")[0];
766
+ if (prefix && !prefix.startsWith("__")) {
767
+ prefixes.push(prefix);
768
+ }
769
+ } else if (tagText.includes(".")) {
770
+ const match = tagText.match(/^(\w+)\./);
771
+ if (match && !match[1].startsWith("__")) {
772
+ prefixes.push(match[1]);
773
+ }
774
+ }
775
+ }
776
+ if (prefixes.length === 0)
777
+ return;
778
+ const counts = new Map;
779
+ for (const p of prefixes)
780
+ counts.set(p, (counts.get(p) ?? 0) + 1);
781
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
782
+ }
783
+ function extractDefaultValue(initializer) {
784
+ if (ts5.isStringLiteral(initializer)) {
785
+ return initializer.text;
786
+ }
787
+ if (ts5.isNumericLiteral(initializer)) {
788
+ return Number(initializer.text);
789
+ }
790
+ if (initializer.kind === ts5.SyntaxKind.TrueKeyword) {
791
+ return true;
792
+ }
793
+ if (initializer.kind === ts5.SyntaxKind.FalseKeyword) {
794
+ return false;
795
+ }
796
+ if (initializer.kind === ts5.SyntaxKind.NullKeyword) {
797
+ return null;
798
+ }
799
+ return initializer.getText();
334
800
  }
335
801
  function registerReferencedTypes(type, ctx) {
336
802
  if (ctx.visitedTypes.has(type))
@@ -374,7 +840,7 @@ function serializeFunctionExport(node, ctx) {
374
840
  return {
375
841
  parameters: params,
376
842
  returns: {
377
- schema: { type: ctx.typeChecker.typeToString(returnType) }
843
+ schema: buildSchema(returnType, ctx.typeChecker, ctx)
378
844
  }
379
845
  };
380
846
  });
@@ -421,7 +887,7 @@ function serializeTypeAlias(node, ctx) {
421
887
  const { description, tags, examples } = getJSDocComment(node);
422
888
  const source = getSourceLocation(node, declSourceFile);
423
889
  const type = ctx.typeChecker.getTypeAtLocation(node);
424
- const typeString = ctx.typeChecker.typeToString(type);
890
+ registerReferencedTypes(type, ctx);
425
891
  return {
426
892
  id: name,
427
893
  name,
@@ -429,7 +895,7 @@ function serializeTypeAlias(node, ctx) {
429
895
  description,
430
896
  tags,
431
897
  source,
432
- ...typeString !== name ? { type: typeString } : {},
898
+ schema: buildSchema(type, ctx.typeChecker, ctx),
433
899
  ...examples.length > 0 ? { examples } : {}
434
900
  };
435
901
  }
@@ -444,7 +910,6 @@ function serializeVariable(node, statement, ctx) {
444
910
  const { description, tags, examples } = getJSDocComment(statement);
445
911
  const source = getSourceLocation(node, declSourceFile);
446
912
  const type = ctx.typeChecker.getTypeAtLocation(node);
447
- const typeString = ctx.typeChecker.typeToString(type);
448
913
  registerReferencedTypes(type, ctx);
449
914
  return {
450
915
  id: name,
@@ -453,16 +918,16 @@ function serializeVariable(node, statement, ctx) {
453
918
  description,
454
919
  tags,
455
920
  source,
456
- ...typeString && typeString !== name ? { type: typeString } : {},
921
+ schema: buildSchema(type, ctx.typeChecker, ctx),
457
922
  ...examples.length > 0 ? { examples } : {}
458
923
  };
459
924
  }
460
925
 
461
926
  // src/builder/spec-builder.ts
462
- import * as fs from "node:fs";
463
- import * as path2 from "node:path";
927
+ import * as fs2 from "node:fs";
928
+ import * as path3 from "node:path";
464
929
  import { SCHEMA_VERSION } from "@openpkg-ts/spec";
465
- import ts4 from "typescript";
930
+ import ts6 from "typescript";
466
931
 
467
932
  // src/serializers/context.ts
468
933
  function createContext(program, sourceFile, options = {}) {
@@ -470,7 +935,9 @@ function createContext(program, sourceFile, options = {}) {
470
935
  typeChecker: program.getTypeChecker(),
471
936
  program,
472
937
  sourceFile,
473
- maxTypeDepth: options.maxTypeDepth ?? 20,
938
+ maxTypeDepth: options.maxTypeDepth ?? 4,
939
+ maxExternalTypeDepth: options.maxExternalTypeDepth ?? 2,
940
+ currentDepth: 0,
474
941
  resolveExternalTypes: options.resolveExternalTypes ?? true,
475
942
  typeRegistry: new TypeRegistry,
476
943
  exportedIds: new Set,
@@ -480,7 +947,7 @@ function createContext(program, sourceFile, options = {}) {
480
947
 
481
948
  // src/builder/spec-builder.ts
482
949
  async function extract(options) {
483
- const { entryFile, baseDir, content, maxTypeDepth, resolveExternalTypes } = options;
950
+ const { entryFile, baseDir, content, maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes } = options;
484
951
  const diagnostics = [];
485
952
  const exports = [];
486
953
  const result = createProgram({ entryFile, baseDir, content });
@@ -504,7 +971,7 @@ async function extract(options) {
504
971
  for (const symbol of exportedSymbols) {
505
972
  exportedIds.add(symbol.getName());
506
973
  }
507
- const ctx = createContext(program, sourceFile, { maxTypeDepth, resolveExternalTypes });
974
+ const ctx = createContext(program, sourceFile, { maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes });
508
975
  ctx.exportedIds = exportedIds;
509
976
  for (const symbol of exportedSymbols) {
510
977
  try {
@@ -537,36 +1004,36 @@ async function extract(options) {
537
1004
  }
538
1005
  function resolveExportTarget(symbol, checker) {
539
1006
  let targetSymbol = symbol;
540
- if (symbol.flags & ts4.SymbolFlags.Alias) {
1007
+ if (symbol.flags & ts6.SymbolFlags.Alias) {
541
1008
  const aliasTarget = checker.getAliasedSymbol(symbol);
542
1009
  if (aliasTarget && aliasTarget !== symbol) {
543
1010
  targetSymbol = aliasTarget;
544
1011
  }
545
1012
  }
546
1013
  const declarations = targetSymbol.declarations ?? [];
547
- const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts4.SyntaxKind.ExportSpecifier) || declarations[0];
1014
+ const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts6.SyntaxKind.ExportSpecifier) || declarations[0];
548
1015
  return { declaration, targetSymbol };
549
1016
  }
550
1017
  function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportName, ctx) {
551
1018
  let result = null;
552
- if (ts4.isFunctionDeclaration(declaration)) {
1019
+ if (ts6.isFunctionDeclaration(declaration)) {
553
1020
  result = serializeFunctionExport(declaration, ctx);
554
- } else if (ts4.isClassDeclaration(declaration)) {
1021
+ } else if (ts6.isClassDeclaration(declaration)) {
555
1022
  result = serializeClass(declaration, ctx);
556
- } else if (ts4.isInterfaceDeclaration(declaration)) {
1023
+ } else if (ts6.isInterfaceDeclaration(declaration)) {
557
1024
  result = serializeInterface(declaration, ctx);
558
- } else if (ts4.isTypeAliasDeclaration(declaration)) {
1025
+ } else if (ts6.isTypeAliasDeclaration(declaration)) {
559
1026
  result = serializeTypeAlias(declaration, ctx);
560
- } else if (ts4.isEnumDeclaration(declaration)) {
1027
+ } else if (ts6.isEnumDeclaration(declaration)) {
561
1028
  result = serializeEnum(declaration, ctx);
562
- } else if (ts4.isVariableDeclaration(declaration)) {
1029
+ } else if (ts6.isVariableDeclaration(declaration)) {
563
1030
  const varStatement = declaration.parent?.parent;
564
- if (varStatement && ts4.isVariableStatement(varStatement)) {
1031
+ if (varStatement && ts6.isVariableStatement(varStatement)) {
565
1032
  result = serializeVariable(declaration, varStatement, ctx);
566
1033
  }
567
- } else if (ts4.isNamespaceExport(declaration) || ts4.isModuleDeclaration(declaration)) {
1034
+ } else if (ts6.isNamespaceExport(declaration) || ts6.isModuleDeclaration(declaration)) {
568
1035
  result = serializeNamespaceExport(exportSymbol, exportName, ctx);
569
- } else if (ts4.isSourceFile(declaration)) {
1036
+ } else if (ts6.isSourceFile(declaration)) {
570
1037
  result = serializeNamespaceExport(exportSymbol, exportName, ctx);
571
1038
  }
572
1039
  if (result) {
@@ -590,11 +1057,11 @@ function getJSDocFromExportSymbol(symbol) {
590
1057
  const examples = [];
591
1058
  const decl = symbol.declarations?.[0];
592
1059
  if (decl) {
593
- const exportDecl = ts4.isNamespaceExport(decl) ? decl.parent : decl;
594
- if (exportDecl && ts4.isExportDeclaration(exportDecl)) {
595
- const jsDocs = ts4.getJSDocCommentsAndTags(exportDecl);
1060
+ const exportDecl = ts6.isNamespaceExport(decl) ? decl.parent : decl;
1061
+ if (exportDecl && ts6.isExportDeclaration(exportDecl)) {
1062
+ const jsDocs = ts6.getJSDocCommentsAndTags(exportDecl);
596
1063
  for (const doc of jsDocs) {
597
- if (ts4.isJSDoc(doc) && doc.comment) {
1064
+ if (ts6.isJSDoc(doc) && doc.comment) {
598
1065
  const commentText = typeof doc.comment === "string" ? doc.comment : doc.comment.map((c) => ("text" in c) ? c.text : "").join("");
599
1066
  if (commentText) {
600
1067
  return {
@@ -655,7 +1122,7 @@ function withExportName(entry, exportName) {
655
1122
  function createEmptySpec(entryFile) {
656
1123
  return {
657
1124
  openpkg: SCHEMA_VERSION,
658
- meta: { name: path2.basename(entryFile, path2.extname(entryFile)) },
1125
+ meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
659
1126
  exports: [],
660
1127
  generation: {
661
1128
  generator: "@openpkg-ts/extract",
@@ -664,18 +1131,18 @@ function createEmptySpec(entryFile) {
664
1131
  };
665
1132
  }
666
1133
  async function getPackageMeta(entryFile, baseDir) {
667
- const searchDir = baseDir ?? path2.dirname(entryFile);
668
- const pkgPath = path2.join(searchDir, "package.json");
1134
+ const searchDir = baseDir ?? path3.dirname(entryFile);
1135
+ const pkgPath = path3.join(searchDir, "package.json");
669
1136
  try {
670
- if (fs.existsSync(pkgPath)) {
671
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
1137
+ if (fs2.existsSync(pkgPath)) {
1138
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
672
1139
  return {
673
- name: pkg.name ?? path2.basename(searchDir),
1140
+ name: pkg.name ?? path3.basename(searchDir),
674
1141
  version: pkg.version,
675
1142
  description: pkg.description
676
1143
  };
677
1144
  }
678
1145
  } catch {}
679
- return { name: path2.basename(searchDir) };
1146
+ return { name: path3.basename(searchDir) };
680
1147
  }
681
- export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, serializeClass, serializeEnum, extractParameters, registerReferencedTypes, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
1148
+ export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, serializeClass, serializeEnum, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, extractParameters, registerReferencedTypes, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
@@ -24,14 +24,12 @@ declare function getJSDocComment(node: ts2.Node): {
24
24
  };
25
25
  declare function getSourceLocation(node: ts2.Node, sourceFile: ts2.SourceFile): SpecSource;
26
26
  import { OpenPkg } from "@openpkg-ts/spec";
27
- import { TypeChecker as TypeChecker_lrgbhchnsl } from "typescript";
28
- import { Program as Program_jbfzpxflck } from "typescript";
29
- import { SourceFile as SourceFile_eubaywigwb } from "typescript";
30
27
  interface ExtractOptions {
31
28
  entryFile: string;
32
29
  baseDir?: string;
33
30
  content?: string;
34
31
  maxTypeDepth?: number;
32
+ maxExternalTypeDepth?: number;
35
33
  resolveExternalTypes?: boolean;
36
34
  schemaExtraction?: "static" | "hybrid";
37
35
  }
@@ -48,13 +46,6 @@ interface Diagnostic {
48
46
  column?: number;
49
47
  };
50
48
  }
51
- interface SerializerContext {
52
- typeChecker: TypeChecker_lrgbhchnsl;
53
- program: Program_jbfzpxflck;
54
- sourceFile: SourceFile_eubaywigwb;
55
- maxTypeDepth: number;
56
- resolveExternalTypes: boolean;
57
- }
58
49
  declare function extract(options: ExtractOptions): Promise<ExtractResult>;
59
50
  import ts3 from "typescript";
60
51
  interface ProgramOptions {
@@ -95,48 +86,66 @@ declare function extractStandardSchemas(_program: ts5.Program, _entryFile: strin
95
86
  import { SpecExport } from "@openpkg-ts/spec";
96
87
  import ts7 from "typescript";
97
88
  import ts6 from "typescript";
98
- interface SerializerContext2 {
89
+ interface SerializerContext {
99
90
  typeChecker: ts6.TypeChecker;
100
91
  program: ts6.Program;
101
92
  sourceFile: ts6.SourceFile;
102
93
  maxTypeDepth: number;
94
+ maxExternalTypeDepth: number;
95
+ currentDepth: number;
103
96
  resolveExternalTypes: boolean;
104
97
  typeRegistry: TypeRegistry;
105
98
  exportedIds: Set<string>;
106
99
  /** Track visited types to prevent infinite recursion */
107
100
  visitedTypes: Set<ts6.Type>;
108
101
  }
109
- declare function serializeClass(node: ts7.ClassDeclaration, ctx: SerializerContext2): SpecExport | null;
102
+ declare function serializeClass(node: ts7.ClassDeclaration, ctx: SerializerContext): SpecExport | null;
110
103
  import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
111
104
  import ts8 from "typescript";
112
- declare function serializeEnum(node: ts8.EnumDeclaration, ctx: SerializerContext2): SpecExport2 | null;
105
+ declare function serializeEnum(node: ts8.EnumDeclaration, ctx: SerializerContext): SpecExport2 | null;
113
106
  import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
114
107
  import ts9 from "typescript";
115
- declare function serializeFunctionExport(node: ts9.FunctionDeclaration | ts9.ArrowFunction, ctx: SerializerContext2): SpecExport3 | null;
108
+ declare function serializeFunctionExport(node: ts9.FunctionDeclaration | ts9.ArrowFunction, ctx: SerializerContext): SpecExport3 | null;
116
109
  import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
117
110
  import ts10 from "typescript";
118
- declare function serializeInterface(node: ts10.InterfaceDeclaration, ctx: SerializerContext2): SpecExport4 | null;
111
+ declare function serializeInterface(node: ts10.InterfaceDeclaration, ctx: SerializerContext): SpecExport4 | null;
119
112
  import { SpecExport as SpecExport5 } from "@openpkg-ts/spec";
120
113
  import ts11 from "typescript";
121
- declare function serializeTypeAlias(node: ts11.TypeAliasDeclaration, ctx: SerializerContext2): SpecExport5 | null;
114
+ declare function serializeTypeAlias(node: ts11.TypeAliasDeclaration, ctx: SerializerContext): SpecExport5 | null;
122
115
  import { SpecExport as SpecExport6 } from "@openpkg-ts/spec";
123
116
  import ts12 from "typescript";
124
- declare function serializeVariable(node: ts12.VariableDeclaration, statement: ts12.VariableStatement, ctx: SerializerContext2): SpecExport6 | null;
117
+ declare function serializeVariable(node: ts12.VariableDeclaration, statement: ts12.VariableStatement, ctx: SerializerContext): SpecExport6 | null;
125
118
  import ts13 from "typescript";
126
119
  declare function formatTypeReference(type: ts13.Type, checker: ts13.TypeChecker): string;
127
120
  declare function collectReferencedTypes(type: ts13.Type, checker: ts13.TypeChecker, visited?: Set<string>): string[];
128
121
  import { SpecSignatureParameter } from "@openpkg-ts/spec";
129
122
  import ts14 from "typescript";
130
- declare function extractParameters(signature: ts14.Signature, ctx: SerializerContext2): SpecSignatureParameter[];
123
+ declare function extractParameters(signature: ts14.Signature, ctx: SerializerContext): SpecSignatureParameter[];
131
124
  /**
132
125
  * Recursively register types referenced by a ts.Type.
133
126
  * Uses ctx.visitedTypes to prevent infinite recursion on circular types.
134
127
  */
135
- declare function registerReferencedTypes(type: ts14.Type, ctx: SerializerContext2): void;
128
+ declare function registerReferencedTypes(type: ts14.Type, ctx: SerializerContext): void;
136
129
  import { SpecSchema as SpecSchema3 } from "@openpkg-ts/spec";
137
130
  import ts15 from "typescript";
138
- declare function buildSchema(type: ts15.Type, checker: ts15.TypeChecker, depth?: number): SpecSchema3;
131
+ /**
132
+ * Check if a name is a primitive type
133
+ */
134
+ declare function isPrimitiveName(name: string): boolean;
135
+ /**
136
+ * Check if a name is a built-in generic type
137
+ */
138
+ declare function isBuiltinGeneric(name: string): boolean;
139
+ /**
140
+ * Check if a type is anonymous (no meaningful symbol name)
141
+ */
142
+ declare function isAnonymous(type: ts15.Type): boolean;
143
+ /**
144
+ * Build a structured SpecSchema from a TypeScript type.
145
+ * Uses $ref for named types and typeArguments for generics.
146
+ */
147
+ declare function buildSchema(type: ts15.Type, checker: ts15.TypeChecker, ctx?: SerializerContext, _depth?: number): SpecSchema3;
139
148
  import ts16 from "typescript";
140
149
  declare function isExported(node: ts16.Node): boolean;
141
150
  declare function getNodeName(node: ts16.Node): string | undefined;
142
- export { zodAdapter, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, registerReferencedTypes, registerAdapter, isSchemaType, isExported, getSourceLocation, getNodeName, getJSDocComment, formatTypeReference, findAdapter, extractStandardSchemas, extractSchemaType, extractParameters, extract, createProgram, collectReferencedTypes, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaResult, SerializerContext, SchemaAdapter, ProgramResult, ProgramOptions, ExtractResult, ExtractOptions, Diagnostic };
151
+ export { zodAdapter, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, registerReferencedTypes, registerAdapter, isSchemaType, isPrimitiveName, isExported, isBuiltinGeneric, isAnonymous, getSourceLocation, getNodeName, getJSDocComment, formatTypeReference, findAdapter, extractStandardSchemas, extractSchemaType, extractParameters, extract, createProgram, collectReferencedTypes, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaResult, SerializerContext, SchemaAdapter, ProgramResult, ProgramOptions, ExtractResult, ExtractOptions, Diagnostic };
package/dist/src/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import {
2
2
  TypeRegistry,
3
+ buildSchema,
3
4
  createProgram,
4
5
  extract,
5
6
  extractParameters,
6
7
  getJSDocComment,
7
8
  getSourceLocation,
9
+ isAnonymous,
10
+ isBuiltinGeneric,
11
+ isPrimitiveName,
8
12
  registerReferencedTypes,
9
13
  serializeClass,
10
14
  serializeEnum,
@@ -12,7 +16,7 @@ import {
12
16
  serializeInterface,
13
17
  serializeTypeAlias,
14
18
  serializeVariable
15
- } from "../shared/chunk-twaykyxs.js";
19
+ } from "../shared/chunk-wddga8ye.js";
16
20
  // src/schema/adapters/arktype.ts
17
21
  var arktypeAdapter = {
18
22
  name: "arktype",
@@ -70,32 +74,6 @@ function collectReferencedTypes(type, checker, visited = new Set) {
70
74
  visited.add(name);
71
75
  return [name];
72
76
  }
73
- // src/types/schema-builder.ts
74
- function buildSchema(type, checker, depth = 0) {
75
- if (depth > 10) {
76
- return { type: checker.typeToString(type) };
77
- }
78
- const typeString = checker.typeToString(type);
79
- if (type.flags & 4)
80
- return { type: "string" };
81
- if (type.flags & 8)
82
- return { type: "number" };
83
- if (type.flags & 16)
84
- return { type: "boolean" };
85
- if (type.flags & 32768)
86
- return { type: "undefined" };
87
- if (type.flags & 65536)
88
- return { type: "null" };
89
- if (type.flags & 16384)
90
- return { type: "void" };
91
- if (type.flags & 1)
92
- return { type: "any" };
93
- if (type.flags & 2)
94
- return { type: "unknown" };
95
- if (type.flags & 131072)
96
- return { type: "never" };
97
- return { type: typeString };
98
- }
99
77
  // src/types/utils.ts
100
78
  function isExported(node) {
101
79
  const modifiers = node.modifiers;
@@ -123,7 +101,10 @@ export {
123
101
  registerReferencedTypes,
124
102
  registerAdapter,
125
103
  isSchemaType,
104
+ isPrimitiveName,
126
105
  isExported,
106
+ isBuiltinGeneric,
107
+ isAnonymous,
127
108
  getSourceLocation,
128
109
  getNodeName,
129
110
  getJSDocComment,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/extract",
3
- "version": "0.11.4",
3
+ "version": "0.12.0",
4
4
  "description": "TypeScript export extraction to OpenPkg spec",
5
5
  "keywords": [
6
6
  "openpkg",
@@ -40,7 +40,7 @@
40
40
  "format": "biome format --write src/"
41
41
  },
42
42
  "dependencies": {
43
- "@openpkg-ts/spec": "^0.11.1",
43
+ "@openpkg-ts/spec": "^0.12.0",
44
44
  "commander": "^12.0.0",
45
45
  "typescript": "^5.0.0"
46
46
  },