@workos/oagen 0.29.0 → 0.29.2

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.
@@ -8165,7 +8165,10 @@ function apiSurfaceToSnapshot(surface) {
8165
8165
  }
8166
8166
  return {
8167
8167
  schemaVersion: "1",
8168
- source: { extractedAt: surface.extractedAt },
8168
+ source: {
8169
+ extractedAt: surface.extractedAt,
8170
+ language
8171
+ },
8169
8172
  policies: getDefaultPolicy(language),
8170
8173
  symbols
8171
8174
  };
@@ -8395,10 +8398,138 @@ function severityMeetsThreshold(severity, threshold) {
8395
8398
  //#endregion
8396
8399
  //#region src/compat/classify.ts
8397
8400
  /**
8401
+ * Normalize a type annotation string to a canonical form before equality
8402
+ * comparison. Emitters evolve their annotation style across releases (e.g.
8403
+ * Python's PEP 604 `X | None` replacing `Optional[X]`, PEP 585 lowercase
8404
+ * generics, or dropping quotes from forward refs); such changes are cosmetic
8405
+ * and must not register as breaking type changes. Only Python needs this
8406
+ * today — other languages encode optionality with a single stable spelling
8407
+ * (`*Foo`, `Option<T>`, `T?`, `T.nilable(Foo)`) so they use exact comparison.
8408
+ * String-literal *values* inside `Literal[...]` are preserved verbatim so a
8409
+ * genuine change to a wire value stays visible. When `language` is absent
8410
+ * (older snapshots) no normalization is applied, preserving prior behaviour.
8411
+ */
8412
+ function normalizeType(type, language) {
8413
+ if (!type) return type;
8414
+ if (language !== "python") return type;
8415
+ return canonicalizePythonType(type);
8416
+ }
8417
+ /** PEP 585 capitalized builtins that map to their lowercase spellings, so
8418
+ * `List[str]` and `list[str]` compare equal. */
8419
+ const PEP585_BUILTINS = {
8420
+ List: "list",
8421
+ Dict: "dict",
8422
+ Tuple: "tuple",
8423
+ Set: "set",
8424
+ FrozenSet: "frozenset",
8425
+ Type: "type"
8426
+ };
8427
+ /** Recursively canonicalize a Python annotation. Structural forms are
8428
+ * normalized (`Optional[X]`→`X | None`, `Union[...]`→` | ` joins, PEP 585
8429
+ * builtins, quoted forward refs, parenthesized unions), but `Literal[...]`
8430
+ * arguments are preserved verbatim — their quotes and inner whitespace are
8431
+ * part of the wire value, not cosmetic. */
8432
+ function canonicalizePythonType(s) {
8433
+ const t = s.trim();
8434
+ if (t.length === 0) return t;
8435
+ if (t.startsWith("(") && t.endsWith(")")) return canonicalizePythonType(t.slice(1, -1));
8436
+ if (t.startsWith("Literal[") && t.endsWith("]")) return `Literal[${splitTopLevel(t.slice(8, -1), ",").map((p) => p.trim()).filter((p) => p.length > 0).map(canonicalizeLiteralArg).join(", ")}]`;
8437
+ if (t.startsWith("Optional[") && t.endsWith("]")) return `${canonicalizePythonType(t.slice(9, -1))} | None`;
8438
+ if (t.startsWith("Union[") && t.endsWith("]")) return splitTopLevel(t.slice(6, -1), ",").map((p) => p.trim()).filter((p) => p.length > 0).map(canonicalizePythonType).join(" | ");
8439
+ if (t.includes("|")) {
8440
+ const arms = splitTopLevel(t, "|").map((p) => p.trim()).filter((p) => p.length > 0);
8441
+ if (arms.length > 1) return arms.map(canonicalizePythonType).join(" | ");
8442
+ }
8443
+ const open = t.indexOf("[");
8444
+ if (open !== -1 && t.endsWith("]")) {
8445
+ const outer = t.slice(0, open).trim();
8446
+ const inner = t.slice(open + 1, -1);
8447
+ return `${PEP585_BUILTINS[outer] ?? outer}[${splitTopLevel(inner, ",").map((p) => p.trim()).filter((p) => p.length > 0).map(canonicalizePythonType).join(", ")}]`;
8448
+ }
8449
+ if (t.startsWith("\"") && t.endsWith("\"") && t.length >= 2 || t.startsWith("'") && t.endsWith("'") && t.length >= 2) return t.slice(1, -1);
8450
+ return PEP585_BUILTINS[t] ?? t;
8451
+ }
8452
+ /** Canonicalize one `Literal[...]` argument so semantically identical string
8453
+ * values compare equal regardless of quote style or escape form. The value
8454
+ * is decoded (backslash escapes processed) then re-encoded in a canonical
8455
+ * double-quoted form, so `Literal['say "hi"']` and `Literal["say \\"hi\\""]`
8456
+ * both canonicalize to `"say \"hi\""`. Bare (unquoted) arguments — e.g. an
8457
+ * enum name or a number — are returned unchanged. */
8458
+ function canonicalizeLiteralArg(arg) {
8459
+ const a = arg.trim();
8460
+ if (a.length < 2) return a;
8461
+ let quote = null;
8462
+ if (a.startsWith("\"") && a.endsWith("\"")) quote = "\"";
8463
+ else if (a.startsWith("'") && a.endsWith("'")) quote = "'";
8464
+ if (!quote) return a;
8465
+ return `"${encodeDoubleQuoted(decodeStringLiteral(a.slice(1, -1)))}"`;
8466
+ }
8467
+ /** Decode the backslash escapes inside a Python string literal's content.
8468
+ * Only the delimiter-relevant escapes (`\\`, `\'`, `\"`) are processed;
8469
+ * other sequences (`\n`, `\t`, `\u…`) are kept verbatim since they're
8470
+ * written identically regardless of quote style and so compare equal already. */
8471
+ function decodeStringLiteral(content) {
8472
+ let out = "";
8473
+ for (let i = 0; i < content.length; i++) {
8474
+ const c = content[i];
8475
+ if (c === "\\" && i + 1 < content.length) {
8476
+ const next = content[i + 1];
8477
+ if (next === "\\" || next === "\"" || next === "'") {
8478
+ out += next;
8479
+ i++;
8480
+ continue;
8481
+ }
8482
+ out += c + next;
8483
+ i++;
8484
+ continue;
8485
+ }
8486
+ out += c;
8487
+ }
8488
+ return out;
8489
+ }
8490
+ /** Re-encode a decoded value as a canonical double-quoted Python string. */
8491
+ function encodeDoubleQuoted(value) {
8492
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
8493
+ }
8494
+ /** Split on a separator that sits at bracket-depth 0 and outside string
8495
+ * literals, so commas/pipes inside `Literal["a,b"]` or `"x|y"` are not split.
8496
+ * An escaped matching quote (`\"` inside a double-quoted string) does not
8497
+ * close the string: only a quote preceded by an even number of backslashes
8498
+ * (zero counts) is a real closing delimiter. */
8499
+ function splitTopLevel(s, sep) {
8500
+ const parts = [];
8501
+ let depth = 0;
8502
+ let inStr = null;
8503
+ let start = 0;
8504
+ for (let i = 0; i < s.length; i++) {
8505
+ const c = s[i];
8506
+ if (inStr) {
8507
+ if (c === inStr) {
8508
+ let precedingBackslashes = 0;
8509
+ for (let j = i - 1; j >= 0 && s[j] === "\\"; j--) precedingBackslashes++;
8510
+ if (precedingBackslashes % 2 === 0) inStr = null;
8511
+ }
8512
+ continue;
8513
+ }
8514
+ if (c === "\"" || c === "'") {
8515
+ inStr = c;
8516
+ continue;
8517
+ }
8518
+ if (c === "[") depth++;
8519
+ else if (c === "]") depth--;
8520
+ else if (c === sep && depth === 0) {
8521
+ parts.push(s.slice(start, i));
8522
+ start = i + 1;
8523
+ }
8524
+ }
8525
+ parts.push(s.slice(start));
8526
+ return parts;
8527
+ }
8528
+ /**
8398
8529
  * Classify changes between a baseline and candidate symbol.
8399
8530
  * Returns one or more classified changes for the diff.
8400
8531
  */
8401
- function classifySymbolChanges(baseline, candidate, policy) {
8532
+ function classifySymbolChanges(baseline, candidate, policy, language) {
8402
8533
  const changes = [];
8403
8534
  const specRef = baseline.schemaName ?? candidate?.schemaName;
8404
8535
  if (!candidate) {
@@ -8422,8 +8553,8 @@ function classifySymbolChanges(baseline, candidate, policy) {
8422
8553
  policy,
8423
8554
  specRef
8424
8555
  }));
8425
- if (baseline.parameters && candidate.parameters) changes.push(...classifyParameterChanges(baseline, candidate, policy, specRef));
8426
- if (baseline.returns && candidate.returns && baseline.returns.name !== candidate.returns.name) changes.push(makeChange({
8556
+ if (baseline.parameters && candidate.parameters) changes.push(...classifyParameterChanges(baseline, candidate, policy, specRef, language));
8557
+ if (baseline.returns && candidate.returns && normalizeType(baseline.returns.name, language) !== normalizeType(candidate.returns.name, language)) changes.push(makeChange({
8427
8558
  category: "return_type_changed",
8428
8559
  symbol: baseline.fqName,
8429
8560
  old: { returnType: baseline.returns.name },
@@ -8441,7 +8572,7 @@ function classifySymbolChanges(baseline, candidate, policy) {
8441
8572
  policy,
8442
8573
  specRef
8443
8574
  }));
8444
- if (baseline.typeRef && candidate.typeRef && baseline.typeRef.name !== candidate.typeRef.name) changes.push(makeChange({
8575
+ if (baseline.typeRef && candidate.typeRef && normalizeType(baseline.typeRef.name, language) !== normalizeType(candidate.typeRef.name, language)) changes.push(makeChange({
8445
8576
  category: "field_type_changed",
8446
8577
  symbol: baseline.fqName,
8447
8578
  old: { type: baseline.typeRef.name },
@@ -8463,7 +8594,7 @@ function classifySymbolChanges(baseline, candidate, policy) {
8463
8594
  /**
8464
8595
  * Classify parameter-level changes between two symbol versions.
8465
8596
  */
8466
- function classifyParameterChanges(baseline, candidate, policy, specRef) {
8597
+ function classifyParameterChanges(baseline, candidate, policy, specRef, language) {
8467
8598
  const changes = [];
8468
8599
  const baseParams = baseline.parameters ?? [];
8469
8600
  const candParams = candidate.parameters ?? [];
@@ -8512,7 +8643,7 @@ function classifyParameterChanges(baseline, candidate, policy, specRef) {
8512
8643
  policy,
8513
8644
  specRef
8514
8645
  }));
8515
- if (baseParam.type.name !== candParam.type.name) changes.push(makeChange({
8646
+ if (normalizeType(baseParam.type.name, language) !== normalizeType(candParam.type.name, language)) changes.push(makeChange({
8516
8647
  category: "parameter_type_narrowed",
8517
8648
  symbol: baseline.fqName,
8518
8649
  old: {
@@ -8869,7 +9000,7 @@ function diffSnapshots(baseline, candidate, policy) {
8869
9000
  for (const baseSym of baseline.symbols) {
8870
9001
  if (isServiceWrapperConstructor(baseSym, serviceAccessors)) continue;
8871
9002
  const candSym = candById.get(baseSym.id) ?? candByFqName.get(baseSym.fqName);
8872
- changes.push(...classifySymbolChanges(baseSym, candSym, effectivePolicy));
9003
+ changes.push(...classifySymbolChanges(baseSym, candSym, effectivePolicy, baseline.source.language));
8873
9004
  }
8874
9005
  for (const candSym of candidate.symbols) if (!baseByFqName.has(candSym.fqName)) {
8875
9006
  if (isServiceWrapperConstructor(candSym, serviceAccessors)) continue;