@openpkg-ts/extract 0.12.0 → 0.13.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-wddga8ye.js";
4
+ } from "../shared/chunk-ksf9k654.js";
5
5
 
6
6
  // src/cli/spec.ts
7
7
  import * as fs from "node:fs";
@@ -203,17 +203,133 @@ class TypeRegistry {
203
203
  if (external) {
204
204
  kind = "external";
205
205
  }
206
- const typeString = checker.typeToString(type);
207
206
  const source = decl ? buildExternalSource(decl) : undefined;
207
+ const schema = this.buildShallowSchema(type, checker);
208
+ let members;
209
+ if (decl && (ts.isClassDeclaration(decl) || ts.isInterfaceDeclaration(decl))) {
210
+ members = this.extractShallowMembers(decl, checker);
211
+ }
208
212
  return {
209
213
  id: name,
210
214
  name,
211
215
  kind,
212
- type: typeString !== name ? typeString : undefined,
216
+ schema,
217
+ members: members?.length ? members : undefined,
213
218
  ...external ? { external: true } : {},
214
219
  ...source ? { source } : {}
215
220
  };
216
221
  }
222
+ buildShallowSchema(type, checker) {
223
+ if (type.isUnion()) {
224
+ const allEnumLiterals = type.types.every((t) => t.flags & (ts.TypeFlags.EnumLiteral | ts.TypeFlags.NumberLiteral));
225
+ if (allEnumLiterals) {
226
+ const values = type.types.map((t) => {
227
+ if (t.flags & ts.TypeFlags.NumberLiteral) {
228
+ return t.value;
229
+ }
230
+ return checker.typeToString(t);
231
+ }).filter((v) => v !== undefined);
232
+ if (values.every((v) => typeof v === "number")) {
233
+ return { type: "number", enum: values };
234
+ }
235
+ return { type: "string", enum: values };
236
+ }
237
+ return {
238
+ anyOf: type.types.map((t) => {
239
+ if (t.flags & ts.TypeFlags.EnumLiteral) {
240
+ const value = t.value;
241
+ if (typeof value === "number") {
242
+ return { type: "number", enum: [value] };
243
+ }
244
+ return { type: checker.typeToString(t) };
245
+ }
246
+ const sym = t.getSymbol() || t.aliasSymbol;
247
+ if (sym && sym.flags & ts.SymbolFlags.EnumMember) {
248
+ return { type: checker.typeToString(t) };
249
+ }
250
+ if (sym && !sym.getName().startsWith("__")) {
251
+ return { $ref: sym.getName() };
252
+ }
253
+ if (t.flags & ts.TypeFlags.StringLiteral) {
254
+ return { type: "string", enum: [t.value] };
255
+ }
256
+ if (t.flags & ts.TypeFlags.NumberLiteral) {
257
+ return { type: "number", enum: [t.value] };
258
+ }
259
+ return { type: checker.typeToString(t) };
260
+ })
261
+ };
262
+ }
263
+ if (type.isIntersection()) {
264
+ return {
265
+ allOf: type.types.map((t) => {
266
+ const sym = t.getSymbol() || t.aliasSymbol;
267
+ if (sym && !sym.getName().startsWith("__")) {
268
+ return { $ref: sym.getName() };
269
+ }
270
+ return { type: checker.typeToString(t) };
271
+ })
272
+ };
273
+ }
274
+ const props = type.getProperties();
275
+ if (props.length > 0) {
276
+ const properties = {};
277
+ const required = [];
278
+ for (const prop of props.slice(0, 30)) {
279
+ const propName = prop.getName();
280
+ if (propName.startsWith("_"))
281
+ continue;
282
+ const propType = checker.getTypeOfSymbol(prop);
283
+ const callSigs = propType.getCallSignatures();
284
+ if (callSigs.length > 0) {
285
+ properties[propName] = { type: "function" };
286
+ } else {
287
+ const propSym = propType.getSymbol() || propType.aliasSymbol;
288
+ const symName = propSym?.getName();
289
+ if (propSym && symName && !symName.startsWith("__") && symName !== propName) {
290
+ properties[propName] = { $ref: symName };
291
+ } else {
292
+ properties[propName] = { type: checker.typeToString(propType) };
293
+ }
294
+ }
295
+ if (!(prop.flags & ts.SymbolFlags.Optional)) {
296
+ required.push(propName);
297
+ }
298
+ }
299
+ return {
300
+ type: "object",
301
+ properties,
302
+ ...required.length ? { required } : {}
303
+ };
304
+ }
305
+ return { type: checker.typeToString(type) };
306
+ }
307
+ extractShallowMembers(decl, checker) {
308
+ const members = [];
309
+ for (const member of decl.members) {
310
+ if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
311
+ const name = member.name?.getText();
312
+ if (!name || name.startsWith("#") || name.startsWith("_"))
313
+ continue;
314
+ const type = checker.getTypeAtLocation(member);
315
+ const sym = type.getSymbol() || type.aliasSymbol;
316
+ members.push({
317
+ name,
318
+ kind: "property",
319
+ schema: sym && !sym.getName().startsWith("__") ? { $ref: sym.getName() } : { type: checker.typeToString(type) }
320
+ });
321
+ } else if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
322
+ const name = member.name?.getText();
323
+ if (!name || name.startsWith("#") || name.startsWith("_"))
324
+ continue;
325
+ members.push({
326
+ name,
327
+ kind: "method"
328
+ });
329
+ }
330
+ }
331
+ return members;
332
+ }
217
333
  registerFromSymbol(symbol, checker) {
218
334
  const name = symbol.getName();
219
335
  if (this.has(name))
@@ -289,6 +405,29 @@ function getParamDescription(propertyName, jsdocTags, inferredAlias) {
289
405
  }
290
406
  return;
291
407
  }
408
+ function extractTypeParameters(node, checker) {
409
+ if (!node.typeParameters || node.typeParameters.length === 0) {
410
+ return;
411
+ }
412
+ return node.typeParameters.map((tp) => {
413
+ const name = tp.name.text;
414
+ let constraint;
415
+ if (tp.constraint) {
416
+ const constraintType = checker.getTypeAtLocation(tp.constraint);
417
+ constraint = checker.typeToString(constraintType);
418
+ }
419
+ let defaultType;
420
+ if (tp.default) {
421
+ const defType = checker.getTypeAtLocation(tp.default);
422
+ defaultType = checker.typeToString(defType);
423
+ }
424
+ return {
425
+ name,
426
+ ...constraint ? { constraint } : {},
427
+ ...defaultType ? { default: defaultType } : {}
428
+ };
429
+ });
430
+ }
292
431
 
293
432
  // src/compiler/program.ts
294
433
  import * as path2 from "node:path";
@@ -339,57 +478,6 @@ function createProgram({
339
478
  };
340
479
  }
341
480
 
342
- // src/serializers/classes.ts
343
- function serializeClass(node, ctx) {
344
- const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
345
- const name = symbol?.getName() ?? node.name?.getText();
346
- if (!name)
347
- return null;
348
- const declSourceFile = node.getSourceFile();
349
- const { description, tags, examples } = getJSDocComment(node);
350
- const source = getSourceLocation(node, declSourceFile);
351
- return {
352
- id: name,
353
- name,
354
- kind: "class",
355
- description,
356
- tags,
357
- source,
358
- members: [],
359
- ...examples.length > 0 ? { examples } : {}
360
- };
361
- }
362
-
363
- // src/serializers/enums.ts
364
- function serializeEnum(node, ctx) {
365
- const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
366
- const name = symbol?.getName() ?? node.name?.getText();
367
- if (!name)
368
- return null;
369
- const declSourceFile = node.getSourceFile();
370
- const { description, tags, examples } = getJSDocComment(node);
371
- const source = getSourceLocation(node, declSourceFile);
372
- const members = node.members.map((member) => {
373
- const memberSymbol = ctx.typeChecker.getSymbolAtLocation(member.name);
374
- const memberName = memberSymbol?.getName() ?? member.name.getText();
375
- return {
376
- id: memberName,
377
- name: memberName,
378
- kind: "enum-member"
379
- };
380
- });
381
- return {
382
- id: name,
383
- name,
384
- kind: "enum",
385
- description,
386
- tags,
387
- source,
388
- members,
389
- ...examples.length > 0 ? { examples } : {}
390
- };
391
- }
392
-
393
481
  // src/types/schema-builder.ts
394
482
  import ts4 from "typescript";
395
483
  var PRIMITIVES2 = new Set([
@@ -574,15 +662,15 @@ function buildSchema(type, checker, ctx, _depth = 0) {
574
662
  const elementTypes = typeRef2.typeArguments ?? [];
575
663
  if (ctx) {
576
664
  return withDepth(ctx, () => ({
577
- type: "tuple",
578
- items: elementTypes.map((t) => buildSchema(t, checker, ctx)),
665
+ type: "array",
666
+ prefixedItems: elementTypes.map((t) => buildSchema(t, checker, ctx)),
579
667
  minItems: elementTypes.length,
580
668
  maxItems: elementTypes.length
581
669
  }));
582
670
  }
583
671
  return {
584
- type: "tuple",
585
- items: elementTypes.map((t) => buildSchema(t, checker, ctx)),
672
+ type: "array",
673
+ prefixedItems: elementTypes.map((t) => buildSchema(t, checker, ctx)),
586
674
  minItems: elementTypes.length,
587
675
  maxItems: elementTypes.length
588
676
  };
@@ -591,6 +679,9 @@ function buildSchema(type, checker, ctx, _depth = 0) {
591
679
  if (typeRef.target && typeRef.typeArguments && typeRef.typeArguments.length > 0) {
592
680
  const symbol2 = typeRef.target.getSymbol();
593
681
  const name = symbol2?.getName();
682
+ if (name && BUILTIN_TYPES.has(name)) {
683
+ return { $ref: name };
684
+ }
594
685
  if (name && (isBuiltinGeneric(name) || !isAnonymous(typeRef.target))) {
595
686
  if (ctx) {
596
687
  return withDepth(ctx, () => ({
@@ -801,7 +892,10 @@ function extractDefaultValue(initializer) {
801
892
  function registerReferencedTypes(type, ctx) {
802
893
  if (ctx.visitedTypes.has(type))
803
894
  return;
804
- ctx.visitedTypes.add(type);
895
+ const isPrimitive = type.flags & (ts5.TypeFlags.String | ts5.TypeFlags.Number | ts5.TypeFlags.Boolean | ts5.TypeFlags.Void | ts5.TypeFlags.Undefined | ts5.TypeFlags.Null | ts5.TypeFlags.Any | ts5.TypeFlags.Unknown | ts5.TypeFlags.Never | ts5.TypeFlags.StringLiteral | ts5.TypeFlags.NumberLiteral | ts5.TypeFlags.BooleanLiteral);
896
+ if (!isPrimitive) {
897
+ ctx.visitedTypes.add(type);
898
+ }
805
899
  const { typeChecker: checker, typeRegistry, exportedIds } = ctx;
806
900
  typeRegistry.registerType(type, checker, exportedIds);
807
901
  const typeArgs = type.typeArguments;
@@ -822,6 +916,280 @@ function registerReferencedTypes(type, ctx) {
822
916
  }
823
917
  }
824
918
 
919
+ // src/serializers/classes.ts
920
+ import ts6 from "typescript";
921
+ function serializeClass(node, ctx) {
922
+ const { typeChecker: checker } = ctx;
923
+ const symbol = checker.getSymbolAtLocation(node.name ?? node);
924
+ const name = symbol?.getName() ?? node.name?.getText();
925
+ if (!name)
926
+ return null;
927
+ const declSourceFile = node.getSourceFile();
928
+ const { description, tags, examples } = getJSDocComment(node);
929
+ const source = getSourceLocation(node, declSourceFile);
930
+ const typeParameters = extractTypeParameters(node, checker);
931
+ const members = [];
932
+ const signatures = [];
933
+ const methodsByName = new Map;
934
+ for (const member of node.members) {
935
+ const memberName = getMemberName(member);
936
+ if (memberName?.startsWith("#"))
937
+ continue;
938
+ if (ts6.isPropertyDeclaration(member)) {
939
+ const propMember = serializeProperty(member, ctx);
940
+ if (propMember)
941
+ members.push(propMember);
942
+ } else if (ts6.isMethodDeclaration(member)) {
943
+ const methodMember = serializeMethod(member, ctx);
944
+ if (methodMember && methodMember.name) {
945
+ if (!methodsByName.has(methodMember.name)) {
946
+ methodsByName.set(methodMember.name, methodMember);
947
+ } else {
948
+ const existing = methodsByName.get(methodMember.name);
949
+ if (!existing.description && methodMember.description) {
950
+ existing.description = methodMember.description;
951
+ }
952
+ if (!existing.tags && methodMember.tags) {
953
+ existing.tags = methodMember.tags;
954
+ }
955
+ }
956
+ }
957
+ } else if (ts6.isConstructorDeclaration(member)) {
958
+ const ctorSig = serializeConstructor(member, ctx);
959
+ if (ctorSig)
960
+ signatures.push(ctorSig);
961
+ } else if (ts6.isGetAccessorDeclaration(member) || ts6.isSetAccessorDeclaration(member)) {
962
+ const accessorMember = serializeAccessor(member, ctx);
963
+ if (accessorMember)
964
+ members.push(accessorMember);
965
+ }
966
+ }
967
+ members.push(...methodsByName.values());
968
+ const extendsClause = getExtendsClause(node, checker);
969
+ const implementsClause = getImplementsClause(node, checker);
970
+ return {
971
+ id: name,
972
+ name,
973
+ kind: "class",
974
+ description,
975
+ tags,
976
+ source,
977
+ typeParameters,
978
+ members: members.length > 0 ? members : undefined,
979
+ signatures: signatures.length > 0 ? signatures : undefined,
980
+ extends: extendsClause,
981
+ implements: implementsClause?.length ? implementsClause : undefined,
982
+ ...examples.length > 0 ? { examples } : {}
983
+ };
984
+ }
985
+ function getMemberName(member) {
986
+ if (ts6.isConstructorDeclaration(member))
987
+ return "constructor";
988
+ if (!member.name)
989
+ return;
990
+ if (ts6.isIdentifier(member.name))
991
+ return member.name.text;
992
+ if (ts6.isPrivateIdentifier(member.name))
993
+ return member.name.text;
994
+ return member.name.getText();
995
+ }
996
+ function getVisibility(member) {
997
+ const modifiers = ts6.getModifiers(member);
998
+ if (!modifiers)
999
+ return;
1000
+ for (const mod of modifiers) {
1001
+ if (mod.kind === ts6.SyntaxKind.PrivateKeyword)
1002
+ return "private";
1003
+ if (mod.kind === ts6.SyntaxKind.ProtectedKeyword)
1004
+ return "protected";
1005
+ if (mod.kind === ts6.SyntaxKind.PublicKeyword)
1006
+ return "public";
1007
+ }
1008
+ return;
1009
+ }
1010
+ function isStatic(member) {
1011
+ const modifiers = ts6.getModifiers(member);
1012
+ return modifiers?.some((m) => m.kind === ts6.SyntaxKind.StaticKeyword) ?? false;
1013
+ }
1014
+ function isReadonly(member) {
1015
+ const modifiers = ts6.getModifiers(member);
1016
+ return modifiers?.some((m) => m.kind === ts6.SyntaxKind.ReadonlyKeyword) ?? false;
1017
+ }
1018
+ function serializeProperty(node, ctx) {
1019
+ const { typeChecker: checker } = ctx;
1020
+ const name = getMemberName(node);
1021
+ if (!name)
1022
+ return null;
1023
+ const { description, tags } = getJSDocComment(node);
1024
+ const visibility = getVisibility(node);
1025
+ const type = checker.getTypeAtLocation(node);
1026
+ const schema = buildSchema(type, checker, ctx);
1027
+ registerReferencedTypes(type, ctx);
1028
+ const flags = {};
1029
+ if (isStatic(node))
1030
+ flags.static = true;
1031
+ if (isReadonly(node))
1032
+ flags.readonly = true;
1033
+ if (node.questionToken)
1034
+ flags.optional = true;
1035
+ return {
1036
+ name,
1037
+ kind: "property",
1038
+ description,
1039
+ tags: tags.length > 0 ? tags : undefined,
1040
+ visibility,
1041
+ schema,
1042
+ flags: Object.keys(flags).length > 0 ? flags : undefined
1043
+ };
1044
+ }
1045
+ function serializeMethod(node, ctx) {
1046
+ const { typeChecker: checker } = ctx;
1047
+ const name = getMemberName(node);
1048
+ if (!name)
1049
+ return null;
1050
+ const { description, tags } = getJSDocComment(node);
1051
+ const visibility = getVisibility(node);
1052
+ const type = checker.getTypeAtLocation(node);
1053
+ const callSignatures = type.getCallSignatures();
1054
+ const signatures = callSignatures.map((sig) => {
1055
+ const params = extractParameters(sig, ctx);
1056
+ const returnType = checker.getReturnTypeOfSignature(sig);
1057
+ registerReferencedTypes(returnType, ctx);
1058
+ return {
1059
+ parameters: params.length > 0 ? params : undefined,
1060
+ returns: {
1061
+ schema: buildSchema(returnType, checker, ctx)
1062
+ }
1063
+ };
1064
+ });
1065
+ const flags = {};
1066
+ if (isStatic(node))
1067
+ flags.static = true;
1068
+ if (node.asteriskToken)
1069
+ flags.generator = true;
1070
+ const modifiers = ts6.getModifiers(node);
1071
+ if (modifiers?.some((m) => m.kind === ts6.SyntaxKind.AsyncKeyword)) {
1072
+ flags.async = true;
1073
+ }
1074
+ return {
1075
+ name,
1076
+ kind: "method",
1077
+ description,
1078
+ tags: tags.length > 0 ? tags : undefined,
1079
+ visibility,
1080
+ signatures: signatures.length > 0 ? signatures : undefined,
1081
+ flags: Object.keys(flags).length > 0 ? flags : undefined
1082
+ };
1083
+ }
1084
+ function serializeConstructor(node, ctx) {
1085
+ const { typeChecker: checker } = ctx;
1086
+ const { description } = getJSDocComment(node);
1087
+ const sig = checker.getSignatureFromDeclaration(node);
1088
+ if (!sig)
1089
+ return null;
1090
+ const params = extractParameters(sig, ctx);
1091
+ return {
1092
+ description,
1093
+ parameters: params.length > 0 ? params : undefined
1094
+ };
1095
+ }
1096
+ function serializeAccessor(node, ctx) {
1097
+ const { typeChecker: checker } = ctx;
1098
+ const name = getMemberName(node);
1099
+ if (!name)
1100
+ return null;
1101
+ const { description, tags } = getJSDocComment(node);
1102
+ const visibility = getVisibility(node);
1103
+ const type = checker.getTypeAtLocation(node);
1104
+ const schema = buildSchema(type, checker, ctx);
1105
+ registerReferencedTypes(type, ctx);
1106
+ const kind = ts6.isGetAccessorDeclaration(node) ? "getter" : "setter";
1107
+ const flags = {};
1108
+ if (isStatic(node))
1109
+ flags.static = true;
1110
+ return {
1111
+ name,
1112
+ kind,
1113
+ description,
1114
+ tags: tags.length > 0 ? tags : undefined,
1115
+ visibility,
1116
+ schema,
1117
+ flags: Object.keys(flags).length > 0 ? flags : undefined
1118
+ };
1119
+ }
1120
+ function getExtendsClause(node, checker) {
1121
+ if (!node.heritageClauses)
1122
+ return;
1123
+ for (const clause of node.heritageClauses) {
1124
+ if (clause.token === ts6.SyntaxKind.ExtendsKeyword) {
1125
+ const expr = clause.types[0];
1126
+ if (expr) {
1127
+ const type = checker.getTypeAtLocation(expr);
1128
+ const symbol = type.getSymbol();
1129
+ return symbol?.getName() ?? expr.expression.getText();
1130
+ }
1131
+ }
1132
+ }
1133
+ return;
1134
+ }
1135
+ function getImplementsClause(node, checker) {
1136
+ if (!node.heritageClauses)
1137
+ return;
1138
+ for (const clause of node.heritageClauses) {
1139
+ if (clause.token === ts6.SyntaxKind.ImplementsKeyword) {
1140
+ return clause.types.map((expr) => {
1141
+ const type = checker.getTypeAtLocation(expr);
1142
+ const symbol = type.getSymbol();
1143
+ return symbol?.getName() ?? expr.expression.getText();
1144
+ });
1145
+ }
1146
+ }
1147
+ return;
1148
+ }
1149
+
1150
+ // src/serializers/enums.ts
1151
+ function serializeEnum(node, ctx) {
1152
+ const { typeChecker: checker } = ctx;
1153
+ const symbol = checker.getSymbolAtLocation(node.name ?? node);
1154
+ const name = symbol?.getName() ?? node.name?.getText();
1155
+ if (!name)
1156
+ return null;
1157
+ const declSourceFile = node.getSourceFile();
1158
+ const { description, tags, examples } = getJSDocComment(node);
1159
+ const source = getSourceLocation(node, declSourceFile);
1160
+ const members = node.members.map((member) => {
1161
+ const memberSymbol = checker.getSymbolAtLocation(member.name);
1162
+ const memberName = memberSymbol?.getName() ?? member.name.getText();
1163
+ const constantValue = checker.getConstantValue(member);
1164
+ let schema;
1165
+ if (typeof constantValue === "string") {
1166
+ schema = { type: "string", enum: [constantValue] };
1167
+ } else if (typeof constantValue === "number") {
1168
+ schema = { type: "number", enum: [constantValue] };
1169
+ } else if (member.initializer) {
1170
+ schema = { type: member.initializer.getText() };
1171
+ }
1172
+ const { description: memberDesc } = getJSDocComment(member);
1173
+ return {
1174
+ id: memberName,
1175
+ name: memberName,
1176
+ kind: "enum-member",
1177
+ ...schema ? { schema } : {},
1178
+ ...memberDesc ? { description: memberDesc } : {}
1179
+ };
1180
+ });
1181
+ return {
1182
+ id: name,
1183
+ name,
1184
+ kind: "enum",
1185
+ description,
1186
+ tags,
1187
+ source,
1188
+ members,
1189
+ ...examples.length > 0 ? { examples } : {}
1190
+ };
1191
+ }
1192
+
825
1193
  // src/serializers/functions.ts
826
1194
  function serializeFunctionExport(node, ctx) {
827
1195
  const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
@@ -831,6 +1199,7 @@ function serializeFunctionExport(node, ctx) {
831
1199
  const declSourceFile = node.getSourceFile();
832
1200
  const { description, tags, examples } = getJSDocComment(node);
833
1201
  const source = getSourceLocation(node, declSourceFile);
1202
+ const typeParameters = extractTypeParameters(node, ctx.typeChecker);
834
1203
  const type = ctx.typeChecker.getTypeAtLocation(node);
835
1204
  const callSignatures = type.getCallSignatures();
836
1205
  const signatures = callSignatures.map((sig) => {
@@ -851,20 +1220,50 @@ function serializeFunctionExport(node, ctx) {
851
1220
  description,
852
1221
  tags,
853
1222
  source,
1223
+ typeParameters,
854
1224
  signatures,
855
1225
  ...examples.length > 0 ? { examples } : {}
856
1226
  };
857
1227
  }
858
1228
 
859
1229
  // src/serializers/interfaces.ts
1230
+ import ts7 from "typescript";
860
1231
  function serializeInterface(node, ctx) {
861
- const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
1232
+ const { typeChecker: checker } = ctx;
1233
+ const symbol = checker.getSymbolAtLocation(node.name ?? node);
862
1234
  const name = symbol?.getName() ?? node.name?.getText();
863
1235
  if (!name)
864
1236
  return null;
865
1237
  const declSourceFile = node.getSourceFile();
866
1238
  const { description, tags, examples } = getJSDocComment(node);
867
1239
  const source = getSourceLocation(node, declSourceFile);
1240
+ const typeParameters = extractTypeParameters(node, checker);
1241
+ const members = [];
1242
+ const methodsByName = new Map;
1243
+ for (const member of node.members) {
1244
+ if (ts7.isPropertySignature(member)) {
1245
+ const propMember = serializePropertySignature(member, ctx);
1246
+ if (propMember)
1247
+ members.push(propMember);
1248
+ } else if (ts7.isMethodSignature(member)) {
1249
+ const methodMember = serializeMethodSignature(member, ctx);
1250
+ if (methodMember && methodMember.name) {
1251
+ if (!methodsByName.has(methodMember.name)) {
1252
+ methodsByName.set(methodMember.name, methodMember);
1253
+ }
1254
+ }
1255
+ } else if (ts7.isCallSignatureDeclaration(member)) {
1256
+ const callMember = serializeCallSignature(member, ctx);
1257
+ if (callMember)
1258
+ members.push(callMember);
1259
+ } else if (ts7.isIndexSignatureDeclaration(member)) {
1260
+ const indexMember = serializeIndexSignature(member, ctx);
1261
+ if (indexMember)
1262
+ members.push(indexMember);
1263
+ }
1264
+ }
1265
+ members.push(...methodsByName.values());
1266
+ const extendsClause = getInterfaceExtends(node, checker);
868
1267
  return {
869
1268
  id: name,
870
1269
  name,
@@ -872,10 +1271,121 @@ function serializeInterface(node, ctx) {
872
1271
  description,
873
1272
  tags,
874
1273
  source,
875
- members: [],
1274
+ typeParameters,
1275
+ members: members.length > 0 ? members : undefined,
1276
+ extends: extendsClause,
876
1277
  ...examples.length > 0 ? { examples } : {}
877
1278
  };
878
1279
  }
1280
+ function serializePropertySignature(node, ctx) {
1281
+ const { typeChecker: checker } = ctx;
1282
+ const name = node.name.getText();
1283
+ const { description, tags } = getJSDocComment(node);
1284
+ const type = checker.getTypeAtLocation(node);
1285
+ const schema = buildSchema(type, checker, ctx);
1286
+ registerReferencedTypes(type, ctx);
1287
+ const flags = {};
1288
+ if (node.questionToken)
1289
+ flags.optional = true;
1290
+ if (node.modifiers?.some((m) => m.kind === ts7.SyntaxKind.ReadonlyKeyword)) {
1291
+ flags.readonly = true;
1292
+ }
1293
+ return {
1294
+ name,
1295
+ kind: "property",
1296
+ description,
1297
+ tags: tags.length > 0 ? tags : undefined,
1298
+ schema,
1299
+ flags: Object.keys(flags).length > 0 ? flags : undefined
1300
+ };
1301
+ }
1302
+ function serializeMethodSignature(node, ctx) {
1303
+ const { typeChecker: checker } = ctx;
1304
+ const name = node.name.getText();
1305
+ const { description, tags } = getJSDocComment(node);
1306
+ const type = checker.getTypeAtLocation(node);
1307
+ const callSignatures = type.getCallSignatures();
1308
+ const signatures = callSignatures.map((sig) => {
1309
+ const params = extractParameters(sig, ctx);
1310
+ const returnType = checker.getReturnTypeOfSignature(sig);
1311
+ registerReferencedTypes(returnType, ctx);
1312
+ return {
1313
+ parameters: params.length > 0 ? params : undefined,
1314
+ returns: {
1315
+ schema: buildSchema(returnType, checker, ctx)
1316
+ }
1317
+ };
1318
+ });
1319
+ const flags = {};
1320
+ if (node.questionToken)
1321
+ flags.optional = true;
1322
+ return {
1323
+ name,
1324
+ kind: "method",
1325
+ description,
1326
+ tags: tags.length > 0 ? tags : undefined,
1327
+ signatures: signatures.length > 0 ? signatures : undefined,
1328
+ flags: Object.keys(flags).length > 0 ? flags : undefined
1329
+ };
1330
+ }
1331
+ function serializeCallSignature(node, ctx) {
1332
+ const { typeChecker: checker } = ctx;
1333
+ const { description, tags } = getJSDocComment(node);
1334
+ const sig = checker.getSignatureFromDeclaration(node);
1335
+ if (!sig)
1336
+ return null;
1337
+ const params = extractParameters(sig, ctx);
1338
+ const returnType = checker.getReturnTypeOfSignature(sig);
1339
+ registerReferencedTypes(returnType, ctx);
1340
+ return {
1341
+ name: "()",
1342
+ kind: "call-signature",
1343
+ description,
1344
+ tags: tags.length > 0 ? tags : undefined,
1345
+ signatures: [
1346
+ {
1347
+ parameters: params.length > 0 ? params : undefined,
1348
+ returns: {
1349
+ schema: buildSchema(returnType, checker, ctx)
1350
+ }
1351
+ }
1352
+ ]
1353
+ };
1354
+ }
1355
+ function serializeIndexSignature(node, ctx) {
1356
+ const { typeChecker: checker } = ctx;
1357
+ const { description, tags } = getJSDocComment(node);
1358
+ const valueType = node.type ? checker.getTypeAtLocation(node.type) : checker.getAnyType();
1359
+ const valueSchema = buildSchema(valueType, checker, ctx);
1360
+ registerReferencedTypes(valueType, ctx);
1361
+ const keyParam = node.parameters[0];
1362
+ const keyType = keyParam?.type ? checker.getTypeAtLocation(keyParam.type) : checker.getStringType();
1363
+ const keyTypeName = checker.typeToString(keyType);
1364
+ return {
1365
+ name: `[${keyTypeName}]`,
1366
+ kind: "index-signature",
1367
+ description,
1368
+ tags: tags.length > 0 ? tags : undefined,
1369
+ schema: {
1370
+ type: "object",
1371
+ additionalProperties: valueSchema
1372
+ }
1373
+ };
1374
+ }
1375
+ function getInterfaceExtends(node, checker) {
1376
+ if (!node.heritageClauses)
1377
+ return;
1378
+ for (const clause of node.heritageClauses) {
1379
+ if (clause.token === ts7.SyntaxKind.ExtendsKeyword && clause.types.length > 0) {
1380
+ const names = clause.types.map((expr) => {
1381
+ const type = checker.getTypeAtLocation(expr);
1382
+ return type.getSymbol()?.getName() ?? expr.expression.getText();
1383
+ });
1384
+ return names.length === 1 ? names[0] : names;
1385
+ }
1386
+ }
1387
+ return;
1388
+ }
879
1389
 
880
1390
  // src/serializers/type-aliases.ts
881
1391
  function serializeTypeAlias(node, ctx) {
@@ -886,7 +1396,9 @@ function serializeTypeAlias(node, ctx) {
886
1396
  const declSourceFile = node.getSourceFile();
887
1397
  const { description, tags, examples } = getJSDocComment(node);
888
1398
  const source = getSourceLocation(node, declSourceFile);
1399
+ const typeParameters = extractTypeParameters(node, ctx.typeChecker);
889
1400
  const type = ctx.typeChecker.getTypeAtLocation(node);
1401
+ const schema = buildSchema(type, ctx.typeChecker, ctx);
890
1402
  registerReferencedTypes(type, ctx);
891
1403
  return {
892
1404
  id: name,
@@ -895,7 +1407,8 @@ function serializeTypeAlias(node, ctx) {
895
1407
  description,
896
1408
  tags,
897
1409
  source,
898
- schema: buildSchema(type, ctx.typeChecker, ctx),
1410
+ typeParameters,
1411
+ schema,
899
1412
  ...examples.length > 0 ? { examples } : {}
900
1413
  };
901
1414
  }
@@ -910,6 +1423,7 @@ function serializeVariable(node, statement, ctx) {
910
1423
  const { description, tags, examples } = getJSDocComment(statement);
911
1424
  const source = getSourceLocation(node, declSourceFile);
912
1425
  const type = ctx.typeChecker.getTypeAtLocation(node);
1426
+ const schema = buildSchema(type, ctx.typeChecker, ctx);
913
1427
  registerReferencedTypes(type, ctx);
914
1428
  return {
915
1429
  id: name,
@@ -918,7 +1432,7 @@ function serializeVariable(node, statement, ctx) {
918
1432
  description,
919
1433
  tags,
920
1434
  source,
921
- schema: buildSchema(type, ctx.typeChecker, ctx),
1435
+ schema,
922
1436
  ...examples.length > 0 ? { examples } : {}
923
1437
  };
924
1438
  }
@@ -927,7 +1441,7 @@ function serializeVariable(node, statement, ctx) {
927
1441
  import * as fs2 from "node:fs";
928
1442
  import * as path3 from "node:path";
929
1443
  import { SCHEMA_VERSION } from "@openpkg-ts/spec";
930
- import ts6 from "typescript";
1444
+ import ts8 from "typescript";
931
1445
 
932
1446
  // src/serializers/context.ts
933
1447
  function createContext(program, sourceFile, options = {}) {
@@ -1004,36 +1518,36 @@ async function extract(options) {
1004
1518
  }
1005
1519
  function resolveExportTarget(symbol, checker) {
1006
1520
  let targetSymbol = symbol;
1007
- if (symbol.flags & ts6.SymbolFlags.Alias) {
1521
+ if (symbol.flags & ts8.SymbolFlags.Alias) {
1008
1522
  const aliasTarget = checker.getAliasedSymbol(symbol);
1009
1523
  if (aliasTarget && aliasTarget !== symbol) {
1010
1524
  targetSymbol = aliasTarget;
1011
1525
  }
1012
1526
  }
1013
1527
  const declarations = targetSymbol.declarations ?? [];
1014
- const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts6.SyntaxKind.ExportSpecifier) || declarations[0];
1528
+ const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts8.SyntaxKind.ExportSpecifier) || declarations[0];
1015
1529
  return { declaration, targetSymbol };
1016
1530
  }
1017
1531
  function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportName, ctx) {
1018
1532
  let result = null;
1019
- if (ts6.isFunctionDeclaration(declaration)) {
1533
+ if (ts8.isFunctionDeclaration(declaration)) {
1020
1534
  result = serializeFunctionExport(declaration, ctx);
1021
- } else if (ts6.isClassDeclaration(declaration)) {
1535
+ } else if (ts8.isClassDeclaration(declaration)) {
1022
1536
  result = serializeClass(declaration, ctx);
1023
- } else if (ts6.isInterfaceDeclaration(declaration)) {
1537
+ } else if (ts8.isInterfaceDeclaration(declaration)) {
1024
1538
  result = serializeInterface(declaration, ctx);
1025
- } else if (ts6.isTypeAliasDeclaration(declaration)) {
1539
+ } else if (ts8.isTypeAliasDeclaration(declaration)) {
1026
1540
  result = serializeTypeAlias(declaration, ctx);
1027
- } else if (ts6.isEnumDeclaration(declaration)) {
1541
+ } else if (ts8.isEnumDeclaration(declaration)) {
1028
1542
  result = serializeEnum(declaration, ctx);
1029
- } else if (ts6.isVariableDeclaration(declaration)) {
1543
+ } else if (ts8.isVariableDeclaration(declaration)) {
1030
1544
  const varStatement = declaration.parent?.parent;
1031
- if (varStatement && ts6.isVariableStatement(varStatement)) {
1545
+ if (varStatement && ts8.isVariableStatement(varStatement)) {
1032
1546
  result = serializeVariable(declaration, varStatement, ctx);
1033
1547
  }
1034
- } else if (ts6.isNamespaceExport(declaration) || ts6.isModuleDeclaration(declaration)) {
1548
+ } else if (ts8.isNamespaceExport(declaration) || ts8.isModuleDeclaration(declaration)) {
1035
1549
  result = serializeNamespaceExport(exportSymbol, exportName, ctx);
1036
- } else if (ts6.isSourceFile(declaration)) {
1550
+ } else if (ts8.isSourceFile(declaration)) {
1037
1551
  result = serializeNamespaceExport(exportSymbol, exportName, ctx);
1038
1552
  }
1039
1553
  if (result) {
@@ -1057,11 +1571,11 @@ function getJSDocFromExportSymbol(symbol) {
1057
1571
  const examples = [];
1058
1572
  const decl = symbol.declarations?.[0];
1059
1573
  if (decl) {
1060
- const exportDecl = ts6.isNamespaceExport(decl) ? decl.parent : decl;
1061
- if (exportDecl && ts6.isExportDeclaration(exportDecl)) {
1062
- const jsDocs = ts6.getJSDocCommentsAndTags(exportDecl);
1574
+ const exportDecl = ts8.isNamespaceExport(decl) ? decl.parent : decl;
1575
+ if (exportDecl && ts8.isExportDeclaration(exportDecl)) {
1576
+ const jsDocs = ts8.getJSDocCommentsAndTags(exportDecl);
1063
1577
  for (const doc of jsDocs) {
1064
- if (ts6.isJSDoc(doc) && doc.comment) {
1578
+ if (ts8.isJSDoc(doc) && doc.comment) {
1065
1579
  const commentText = typeof doc.comment === "string" ? doc.comment : doc.comment.map((c) => ("text" in c) ? c.text : "").join("");
1066
1580
  if (commentText) {
1067
1581
  return {
@@ -1145,4 +1659,4 @@ async function getPackageMeta(entryFile, baseDir) {
1145
1659
  } catch {}
1146
1660
  return { name: path3.basename(searchDir) };
1147
1661
  }
1148
- export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, serializeClass, serializeEnum, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, extractParameters, registerReferencedTypes, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
1662
+ export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
@@ -13,6 +13,16 @@ declare class TypeRegistry {
13
13
  */
14
14
  registerType(type: ts.Type, checker: ts.TypeChecker, exportedIds: Set<string>): string | undefined;
15
15
  private buildSpecType;
16
+ /**
17
+ * Build a shallow schema for registry types (no deep recursion).
18
+ * Only captures top-level structure with $refs.
19
+ */
20
+ private buildShallowSchema;
21
+ /**
22
+ * Extract shallow members for classes/interfaces.
23
+ * Only captures property names and simple type info.
24
+ */
25
+ private extractShallowMembers;
16
26
  registerFromSymbol(symbol: ts.Symbol, checker: ts.TypeChecker): SpecType | undefined;
17
27
  }
18
28
  import { SpecExample, SpecSource, SpecTag } from "@openpkg-ts/spec";
package/dist/src/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  serializeInterface,
17
17
  serializeTypeAlias,
18
18
  serializeVariable
19
- } from "../shared/chunk-wddga8ye.js";
19
+ } from "../shared/chunk-ksf9k654.js";
20
20
  // src/schema/adapters/arktype.ts
21
21
  var arktypeAdapter = {
22
22
  name: "arktype",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/extract",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "TypeScript export extraction to OpenPkg spec",
5
5
  "keywords": [
6
6
  "openpkg",
@@ -42,6 +42,7 @@
42
42
  "dependencies": {
43
43
  "@openpkg-ts/spec": "^0.12.0",
44
44
  "commander": "^12.0.0",
45
+ "tree-sitter-wasms": "^0.1.13",
45
46
  "typescript": "^5.0.0"
46
47
  },
47
48
  "devDependencies": {