@workos/oagen 0.14.1 → 0.16.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.
@@ -1,5 +1,5 @@
1
1
  import { t as safeParse$1, v as ExtractorError } from "../tree-sitter-DcUvm0KA.mjs";
2
- import { A as getDefaultPolicy, C as defaultSeverityForCategory, D as isCompatibleSchemaVersion, E as COMPAT_SCHEMA_VERSION, O as validateSnapshot, S as summarizeChanges, T as apiSurfaceToSnapshot, _ as nodeHints, a as registerExtractor, b as classifyAddedSymbol, c as filterSurface, d as specDerivedHttpKeys, f as specDerivedMethodPaths, g as namedTypeWordsOverlap, h as defaultIsNullableOnlyDifference, i as getExtractor, j as mergePolicy, k as ALL_LANGUAGE_IDS, l as specDerivedEnumValues, m as NAMED_TYPE_RE, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, s as diffSurfaces, t as buildOverlayLookup, u as specDerivedFieldPaths, v as resolveHints, w as severityMeetsThreshold, x as classifySymbolChanges, y as typeExistsInSurface } from "../overlay-Dlk7bV5R.mjs";
2
+ import { A as getDefaultPolicy, C as defaultSeverityForCategory, D as isCompatibleSchemaVersion, E as COMPAT_SCHEMA_VERSION, O as validateSnapshot, S as summarizeChanges, T as apiSurfaceToSnapshot, _ as nodeHints, a as registerExtractor, b as classifyAddedSymbol, c as filterSurface, d as specDerivedHttpKeys, f as specDerivedMethodPaths, g as namedTypeWordsOverlap, h as defaultIsNullableOnlyDifference, i as getExtractor, j as mergePolicy, k as ALL_LANGUAGE_IDS, l as specDerivedEnumValues, m as NAMED_TYPE_RE, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, s as diffSurfaces, t as buildOverlayLookup, u as specDerivedFieldPaths, v as resolveHints, w as severityMeetsThreshold, x as classifySymbolChanges, y as typeExistsInSurface } from "../overlay-YkIApOxh.mjs";
3
3
  import path, { basename, dirname, join, relative, resolve } from "node:path";
4
4
  import Parser from "tree-sitter";
5
5
  import ts from "typescript";
@@ -701,6 +701,7 @@ function parseConstElement(constElement) {
701
701
  }
702
702
  function parseMethods$2(classBody) {
703
703
  const methods = [];
704
+ const allPromotedProperties = [];
704
705
  for (const child of classBody.namedChildren) {
705
706
  if (child.type !== "method_declaration") continue;
706
707
  const nameNode = child.childForFieldName("name");
@@ -710,10 +711,11 @@ function parseMethods$2(classBody) {
710
711
  const visibility = getVisibility(child);
711
712
  const isStatic = isStaticMethod(child);
712
713
  const docInfo = findDocComment(child);
714
+ const promotedProperties = [];
713
715
  const params = [];
714
716
  const paramsList = child.childForFieldName("parameters");
715
717
  if (paramsList) for (const paramNode of paramsList.namedChildren) {
716
- if (paramNode.type !== "simple_parameter") continue;
718
+ if (paramNode.type !== "simple_parameter" && paramNode.type !== "property_promotion_parameter") continue;
717
719
  const paramNameNode = paramNode.namedChildren.find((c) => c.type === "variable_name");
718
720
  if (!paramNameNode) continue;
719
721
  const paramName = paramNameNode.text.replace(/^\$/, "");
@@ -728,6 +730,14 @@ function parseMethods$2(classBody) {
728
730
  type: paramType,
729
731
  optional: hasDefault
730
732
  });
733
+ if (paramNode.type === "property_promotion_parameter") {
734
+ const visibility = paramNode.namedChildren.find((c) => c.type === "visibility_modifier")?.text ?? "public";
735
+ promotedProperties.push({
736
+ name: paramName,
737
+ type: paramType,
738
+ visibility
739
+ });
740
+ }
731
741
  }
732
742
  let returnType = "";
733
743
  const returnTypeNode = child.childForFieldName("return_type");
@@ -741,8 +751,12 @@ function parseMethods$2(classBody) {
741
751
  params,
742
752
  returnType
743
753
  });
754
+ if (methodName === "__construct") allPromotedProperties.push(...promotedProperties);
744
755
  }
745
- return methods;
756
+ return {
757
+ methods,
758
+ promotedProperties: allPromotedProperties
759
+ };
746
760
  }
747
761
  function parseProperties$1(classBody) {
748
762
  const properties = [];
@@ -815,18 +829,19 @@ function parseClassDeclarations(tree, sourceFile, namespace) {
815
829
  }
816
830
  const bodyNode = node.childForFieldName("body");
817
831
  if (!bodyNode) continue;
818
- const methods = parseMethods$2(bodyNode);
832
+ const { methods, promotedProperties } = parseMethods$2(bodyNode);
819
833
  const properties = parseProperties$1(bodyNode);
820
834
  const constants = parseConstants(bodyNode);
821
835
  const resourceAttributes = parseResourceAttributes(bodyNode);
822
836
  const hasCustomConstructor = methods.some((m) => m.name === "constructFromResponse" && m.isStatic);
837
+ const allProperties = [...properties, ...promotedProperties];
823
838
  classes.push({
824
839
  name: nameNode.text,
825
840
  namespace,
826
841
  extends: extendsName,
827
842
  isInterface: false,
828
843
  methods,
829
- properties,
844
+ properties: allProperties,
830
845
  constants,
831
846
  resourceAttributes,
832
847
  hasCustomConstructor,
@@ -842,7 +857,7 @@ function parseInterfaceDeclarations(tree, sourceFile, namespace) {
842
857
  if (!nameNode) continue;
843
858
  const bodyNode = node.childForFieldName("body");
844
859
  if (!bodyNode) continue;
845
- const methods = parseMethods$2(bodyNode);
860
+ const { methods } = parseMethods$2(bodyNode);
846
861
  interfaces.push({
847
862
  name: nameNode.text,
848
863
  namespace,
@@ -881,7 +896,7 @@ function parseEnumDeclarations(tree, sourceFile, namespace) {
881
896
  value
882
897
  });
883
898
  }
884
- const methods = parseMethods$2(bodyNode);
899
+ const { methods } = parseMethods$2(bodyNode);
885
900
  enums.push({
886
901
  name: nameNode.text,
887
902
  namespace,
@@ -922,6 +937,27 @@ const DEFAULT_EXCEPTION_BASES$1 = ["Exception", "\\Exception"];
922
937
  function isResourceClass(cls, resourceBases) {
923
938
  return !!cls.extends && resourceBases.has(cls.extends) && cls.resourceAttributes.length > 0;
924
939
  }
940
+ /**
941
+ * Check if a class is a "value object" — PHP 8 model class whose fields
942
+ * live on the constructor as promoted properties (`public Type $field`).
943
+ * Every model class generated by the WorkOS PHP emitter follows this
944
+ * shape: `readonly class Foo implements \JsonSerializable { use
945
+ * JsonSerializableTrait; public function __construct(public Type $field,
946
+ * …) {} }`. The parser collects promoted properties under
947
+ * `cls.properties` so any class with at least one public promoted
948
+ * property is a value object — the field set is its identity.
949
+ *
950
+ * Exception classes are explicitly excluded: an exception subclass that
951
+ * happens to declare a public property (e.g. `BaseRequestException` with
952
+ * a `$requestId` field) is still semantically an exception, not a model.
953
+ * The `isExceptionClass` check is performed by the caller so this stays
954
+ * a pure structural test.
955
+ */
956
+ function isValueObjectClass(cls) {
957
+ if (cls.isInterface) return false;
958
+ if (cls.constants.length > 0) return false;
959
+ return cls.properties.some((p) => p.visibility === "public");
960
+ }
925
961
  /** Check if a class is enum-like (only constants, no public methods). */
926
962
  function isEnumClass(cls) {
927
963
  if (cls.isInterface) return false;
@@ -972,6 +1008,23 @@ function buildSurface$6(allClasses, hints) {
972
1008
  ...cls.hasCustomConstructor ? { hasCustomConstructor: true } : {}
973
1009
  };
974
1010
  collector.add(cls.sourceFile, cls.name);
1011
+ } else if (isValueObjectClass(cls) && !isExceptionClass$1(cls, exceptionBases)) {
1012
+ const fields = {};
1013
+ for (const prop of cls.properties) {
1014
+ if (prop.visibility !== "public") continue;
1015
+ fields[prop.name] = {
1016
+ name: prop.name,
1017
+ type: prop.type,
1018
+ optional: false
1019
+ };
1020
+ }
1021
+ interfaces[cls.name] = {
1022
+ name: cls.name,
1023
+ sourceFile: cls.sourceFile,
1024
+ fields,
1025
+ extends: cls.extends ? [cls.extends] : []
1026
+ };
1027
+ collector.add(cls.sourceFile, cls.name);
975
1028
  } else if (isEnumClass(cls)) {
976
1029
  const members = {};
977
1030
  for (const constant of cls.constants) members[constant.name] = constant.value;
@@ -1520,6 +1573,20 @@ const DEFAULT_EXCEPTION_BASES = ["Exception", "BaseException"];
1520
1573
  function hasBase(cls, bases) {
1521
1574
  return cls.bases.some((b) => bases.has(b));
1522
1575
  }
1576
+ /**
1577
+ * True when the class carries a `@dataclass` decorator (with or without
1578
+ * arguments — `@dataclass`, `@dataclass()`, `@dataclass(slots=True)`, etc.).
1579
+ *
1580
+ * Dataclasses are the canonical "data-shape" Python construct: their
1581
+ * structural identity is the field set, even when generated alongside
1582
+ * helper methods like `from_dict` / `to_dict`. Without this signal the
1583
+ * extractor categorizes them as plain method-bearing classes (category 5)
1584
+ * and loses the field info — which then prevents the compat differ from
1585
+ * recognizing renames structurally.
1586
+ */
1587
+ function isDataclass(cls) {
1588
+ return cls.decorators.some((d) => /^dataclass(\s*\(.*\))?$/.test(d));
1589
+ }
1523
1590
  /** Check if a class is a model class (inherits from BaseModel or configured model bases,
1524
1591
  * transitively from another class that does). */
1525
1592
  function isModelClass(cls, allClasses, modelBases) {
@@ -1701,6 +1768,22 @@ function buildSurface$5(parsedFiles, hints) {
1701
1768
  collector.add(cls.sourceFile, cls.name);
1702
1769
  continue;
1703
1770
  }
1771
+ if (isDataclass(cls) && cls.fields.length > 0) {
1772
+ const fields = {};
1773
+ for (const field of cls.fields) fields[field.name] = {
1774
+ name: field.name,
1775
+ type: field.type,
1776
+ optional: field.hasDefault
1777
+ };
1778
+ interfaces[cls.name] = {
1779
+ name: cls.name,
1780
+ sourceFile: cls.sourceFile,
1781
+ fields: sortRecord(fields),
1782
+ extends: []
1783
+ };
1784
+ collector.add(cls.sourceFile, cls.name);
1785
+ continue;
1786
+ }
1704
1787
  if (cls.methods.length > 0) {
1705
1788
  const apiMethods = {};
1706
1789
  const constructorParams = [];
@@ -2044,6 +2127,7 @@ function extractClasses(source) {
2044
2127
  if (node.type !== "class") continue;
2045
2128
  if (isInsideRanges(node.startPosition.row, serviceRanges)) continue;
2046
2129
  if (node.descendantsOfType("singleton_class").length > 0) continue;
2130
+ if (isEnumShapedClass(node)) continue;
2047
2131
  const nameNode = node.childForFieldName("name");
2048
2132
  if (!nameNode) continue;
2049
2133
  const className = nameNode.text;
@@ -2100,6 +2184,19 @@ function extractServiceModules(source) {
2100
2184
  }
2101
2185
  return services;
2102
2186
  }
2187
+ /**
2188
+ * True when a `class` node looks like a Ruby enum: only constant
2189
+ * assignments, no methods, no attr_*, no singleton class. The
2190
+ * `extractEnumModules` pass picks these up; `extractClasses` skips
2191
+ * them to avoid double-emission. Shared so the two stay in sync.
2192
+ */
2193
+ function isEnumShapedClass(classNode) {
2194
+ const bodyNode = classNode.childForFieldName("body");
2195
+ if (!bodyNode) return false;
2196
+ for (const c of bodyNode.namedChildren) if (c.type === "method" || c.type === "singleton_method" || c.type === "call") return false;
2197
+ const constants = extractEnumConstants(bodyNode);
2198
+ return Object.keys(constants).length >= 2;
2199
+ }
2103
2200
  /** Extract enum-like modules (modules with string/number constants, no class << self). */
2104
2201
  function extractEnumModules$1(source) {
2105
2202
  const tree = safeParse(source);
@@ -2119,6 +2216,22 @@ function extractEnumModules$1(source) {
2119
2216
  members: sortRecord(constants)
2120
2217
  });
2121
2218
  }
2219
+ for (const node of tree.rootNode.descendantsOfType("class")) {
2220
+ if (node.type !== "class") continue;
2221
+ if (isInsideRanges(node.startPosition.row, serviceRanges)) continue;
2222
+ if (node.descendantsOfType("singleton_class").length > 0) continue;
2223
+ const nameNode = node.childForFieldName("name");
2224
+ if (!nameNode) continue;
2225
+ const bodyNode = node.childForFieldName("body");
2226
+ if (!bodyNode) continue;
2227
+ if (bodyNode.namedChildren.some((c) => c.type === "method" || c.type === "singleton_method" || c.type === "call")) continue;
2228
+ const constants = extractEnumConstants(bodyNode);
2229
+ if (Object.keys(constants).length < 2) continue;
2230
+ enums.push({
2231
+ name: nameNode.text,
2232
+ members: sortRecord(constants)
2233
+ });
2234
+ }
2122
2235
  return enums;
2123
2236
  }
2124
2237
  /** Extract autoload declarations to build the export map. */