@workos/oagen-emitters 0.7.1 → 0.7.3

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,3 +1,3 @@
1
1
  {
2
- ".": "0.7.1"
2
+ ".": "0.7.3"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.3](https://github.com/workos/oagen-emitters/compare/v0.7.2...v0.7.3) (2026-05-02)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * url-encode path parameters in php/node/python/kotlin emitters ([#75](https://github.com/workos/oagen-emitters/issues/75)) ([34c08fc](https://github.com/workos/oagen-emitters/commit/34c08fc10510db53f1f989edab5b4d0cead15aa9))
9
+
10
+ ## [0.7.2](https://github.com/workos/oagen-emitters/compare/v0.7.1...v0.7.2) (2026-05-01)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **php:** wrap degenerate-union models in fromArray/toArray ([#72](https://github.com/workos/oagen-emitters/issues/72)) ([053d34a](https://github.com/workos/oagen-emitters/commit/053d34adcbbecbed6793c54c4fd48a03aef97f21))
16
+
3
17
  ## [0.7.1](https://github.com/workos/oagen-emitters/compare/v0.7.0...v0.7.1) (2026-05-01)
4
18
 
5
19
 
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as nodeEmitter, a as rustExtractor, c as pythonExtractor, d as rubyEmitter, f as kotlinEmitter, g as pythonEmitter, h as phpEmitter, i as kotlinExtractor, l as rubyExtractor, m as goEmitter, n as elixirExtractor, o as goExtractor, p as dotnetEmitter, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor } from "./plugin-h8Onp2Ma.mjs";
1
+ import { _ as nodeEmitter, a as rustExtractor, c as pythonExtractor, d as rubyEmitter, f as kotlinEmitter, g as pythonEmitter, h as phpEmitter, i as kotlinExtractor, l as rubyExtractor, m as goEmitter, n as elixirExtractor, o as goExtractor, p as dotnetEmitter, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor } from "./plugin-BoTAX4nl.mjs";
2
2
  export { dotnetEmitter, dotnetExtractor, elixirExtractor, goEmitter, goExtractor, kotlinEmitter, kotlinExtractor, nodeEmitter, nodeExtractor, phpEmitter, phpExtractor, pythonEmitter, pythonExtractor, rubyEmitter, rubyExtractor, rustExtractor, workosEmittersPlugin };
@@ -4475,6 +4475,79 @@ function formatWrapperDescription(name) {
4475
4475
  return name.split("_").map((w, i) => i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w).join(" ");
4476
4476
  }
4477
4477
  //#endregion
4478
+ //#region src/shared/path-template.ts
4479
+ /**
4480
+ * Parse an OpenAPI path template into an ordered list of literal and
4481
+ * parameter segments. Adjacent literals are coalesced into a single segment
4482
+ * (the only way two literals can be adjacent is if the input had `{}{}`
4483
+ * which is malformed; we still handle it deterministically).
4484
+ *
4485
+ * Examples:
4486
+ * "/orgs/{id}/users/{uid}" → [
4487
+ * { kind: 'literal', value: '/orgs/' },
4488
+ * { kind: 'param', name: 'id' },
4489
+ * { kind: 'literal', value: '/users/' },
4490
+ * { kind: 'param', name: 'uid' },
4491
+ * ]
4492
+ * "/health" → [{ kind: 'literal', value: '/health' }]
4493
+ * "" → []
4494
+ */
4495
+ function parsePathTemplate(path, options = {}) {
4496
+ const normalized = options.stripLeadingSlash && path.startsWith("/") ? path.slice(1) : path;
4497
+ if (normalized === "") return [];
4498
+ const segments = [];
4499
+ const re = /\{([^{}]+)\}/g;
4500
+ let cursor = 0;
4501
+ let m;
4502
+ while ((m = re.exec(normalized)) !== null) {
4503
+ if (m.index > cursor) segments.push({
4504
+ kind: "literal",
4505
+ value: normalized.slice(cursor, m.index)
4506
+ });
4507
+ segments.push({
4508
+ kind: "param",
4509
+ name: m[1]
4510
+ });
4511
+ cursor = m.index + m[0].length;
4512
+ }
4513
+ if (cursor < normalized.length) segments.push({
4514
+ kind: "literal",
4515
+ value: normalized.slice(cursor)
4516
+ });
4517
+ return segments;
4518
+ }
4519
+ /** True when at least one segment is a parameter placeholder. */
4520
+ function hasPathParams(segments) {
4521
+ return segments.some((s) => s.kind === "param");
4522
+ }
4523
+ //#endregion
4524
+ //#region src/node/path-expression.ts
4525
+ /**
4526
+ * Build the TypeScript expression that the SDK passes as the request path.
4527
+ *
4528
+ * Every {paramName} placeholder becomes `${encodeURIComponent(name)}` inside a
4529
+ * template literal. encodeURIComponent is used (not encodeURI) because we want
4530
+ * "/" to be encoded too — otherwise a caller-supplied id containing "../" can
4531
+ * be normalized by the underlying HTTP transport (libcurl, fetch, etc.) into
4532
+ * a different endpoint of the WorkOS API while still authenticated with the
4533
+ * application's API key.
4534
+ *
4535
+ * "/orgs" → `'orgs'`
4536
+ * "/orgs/{id}" → `` `orgs/${encodeURIComponent(id)}` ``
4537
+ * "/orgs/{id}/foo" → `` `orgs/${encodeURIComponent(id)}/foo` ``
4538
+ */
4539
+ function buildNodePathExpression(rawPath) {
4540
+ const segments = parsePathTemplate(rawPath);
4541
+ if (!hasPathParams(segments)) return `'${rawPath}'`;
4542
+ let body = "";
4543
+ for (const seg of segments) body += renderSegment(seg);
4544
+ return `\`${body}\``;
4545
+ }
4546
+ function renderSegment(seg) {
4547
+ if (seg.kind === "literal") return seg.value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
4548
+ return `\${encodeURIComponent(${fieldName$5(seg.name)})}`;
4549
+ }
4550
+ //#endregion
4478
4551
  //#region src/node/wrappers.ts
4479
4552
  /**
4480
4553
  * Generate TypeScript wrapper method lines for union split operations.
@@ -4564,8 +4637,7 @@ function emitWrapperMethod$6(lines, resolvedOp, wrapper, ctx) {
4564
4637
  }
4565
4638
  /** Build a path template string from an Operation. */
4566
4639
  function buildPathStr$1(op) {
4567
- const interpolated = op.path.replace(/\{(\w+)\}/g, (_, p) => `\${${fieldName$5(p)}}`);
4568
- return interpolated.includes("${") ? `\`${interpolated}\`` : `'${op.path}'`;
4640
+ return buildNodePathExpression(op.path);
4569
4641
  }
4570
4642
  /** Convert a JS value to a TypeScript literal. */
4571
4643
  function tsLiteral$1(value) {
@@ -5365,8 +5437,7 @@ function renderQueryExpr(queryParams) {
5365
5437
  return `options ? { ${parts.join(", ")} } : undefined`;
5366
5438
  }
5367
5439
  function buildPathStr(op) {
5368
- const interpolated = op.path.replace(/\{(\w+)\}/g, (_, p) => `\${${fieldName$5(p)}}`);
5369
- return interpolated.includes("${") ? `\`${interpolated}\`` : `'${op.path}'`;
5440
+ return buildNodePathExpression(op.path);
5370
5441
  }
5371
5442
  function buildPathParams(op, specEnumNames) {
5372
5443
  const declaredNames = new Set(op.pathParams.map((p) => fieldName$5(p.name)));
@@ -7638,6 +7709,46 @@ function buildRecursiveHashMap$1(models, enums) {
7638
7709
  return hashCache;
7639
7710
  }
7640
7711
  //#endregion
7712
+ //#region src/python/path-expression.ts
7713
+ /**
7714
+ * Build the Python f-string that the SDK passes to the request layer.
7715
+ *
7716
+ * Every {paramName} placeholder is wrapped in
7717
+ * `urllib.parse.quote(str(...), safe="")` so that an unencoded "/" or "../"
7718
+ * in a caller-supplied id cannot be normalized by the underlying HTTP
7719
+ * transport into a different endpoint of the WorkOS API while still
7720
+ * authenticated with the application's API key. `safe=""` is critical:
7721
+ * the stdlib default of `safe="/"` does NOT encode "/" and would leave the
7722
+ * traversal vector open.
7723
+ *
7724
+ * Generated files using this helper must import `quote` (e.g.
7725
+ * `from urllib.parse import quote`).
7726
+ *
7727
+ * "/orgs" → `"orgs"`
7728
+ * "/orgs/{id}" → `f"orgs/{quote(str(id), safe='')}"`
7729
+ * "/orgs/{id}" with id ∈ enums → `f"orgs/{quote(str(enum_value(id)), safe='')}"`
7730
+ */
7731
+ function buildPythonPathExpression(rawPath, options = {}) {
7732
+ const segments = parsePathTemplate(rawPath, { stripLeadingSlash: true });
7733
+ if (segments.length === 0) return "\"\"";
7734
+ if (!hasPathParams(segments)) {
7735
+ const literal = segments[0].value;
7736
+ return `"${escapePyDoubleQuoted(literal)}"`;
7737
+ }
7738
+ const enums = options.enumParams;
7739
+ let body = "";
7740
+ for (const seg of segments) if (seg.kind === "literal") body += escapePyDoubleQuoted(seg.value);
7741
+ else {
7742
+ const varName = fieldName$4(seg.name);
7743
+ const inner = enums?.has(seg.name) ? `enum_value(${varName})` : varName;
7744
+ body += `{quote(str(${inner}), safe='')}`;
7745
+ }
7746
+ return `f"${body}"`;
7747
+ }
7748
+ function escapePyDoubleQuoted(literal) {
7749
+ return literal.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\{/g, "{{").replace(/\}/g, "}}");
7750
+ }
7751
+ //#endregion
7641
7752
  //#region src/python/wrappers.ts
7642
7753
  /**
7643
7754
  * Generate Python wrapper method lines for split operations.
@@ -7707,12 +7818,7 @@ function emitWrapperMethod$5(lines, resolvedOp, wrapper, ctx, isAsync) {
7707
7818
  lines.push(` if ${pyName} is not None:`);
7708
7819
  lines.push(` body["${paramName}"] = ${pyName}`);
7709
7820
  }
7710
- let pathExpr;
7711
- if (op.pathParams.length > 0) {
7712
- let path = op.path.replace(/^\//, "");
7713
- for (const p of op.pathParams) path = path.replace(`{${p.name}}`, `{${fieldName$4(p.name)}}`);
7714
- pathExpr = `f"${path}"`;
7715
- } else pathExpr = `"${op.path.replace(/^\//, "")}"`;
7821
+ const pathExpr = buildPythonPathExpression(op.path);
7716
7822
  const awaitPrefix = isAsync ? "await " : "";
7717
7823
  lines.push("");
7718
7824
  if (wrapper.responseModelName) {
@@ -8397,6 +8503,7 @@ function generateResources$5(services, ctx) {
8397
8503
  lines.push("from __future__ import annotations");
8398
8504
  lines.push("");
8399
8505
  lines.push("from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Type, Union, cast");
8506
+ if (allOperations.some((op) => op.pathParams.length > 0) || allOperations.some((op) => /\{[^{}]+\}/.test(op.path))) lines.push("from urllib.parse import quote");
8400
8507
  lines.push("");
8401
8508
  lines.push("if TYPE_CHECKING:");
8402
8509
  lines.push(` from ${importPrefix}_client import AsyncWorkOSClient, WorkOSClient`);
@@ -8699,15 +8806,12 @@ function buildErrorRaisesBlock(op) {
8699
8806
  }
8700
8807
  /**
8701
8808
  * Build a Python f-string path expression from an operation path.
8702
- * E.g., "/organizations/{id}" -> f"organizations/{id}"
8809
+ * E.g., "/organizations/{id}" -> f"organizations/{quote(str(id), safe='')}"
8703
8810
  */
8704
8811
  function buildPathString$1(op) {
8705
- const path = op.path.replace(/^\//, "");
8706
- if (op.pathParams.length === 0) return `"${path}"`;
8707
- let fPath = path;
8708
- for (const param of op.pathParams) if (param.type.kind === "enum" || param.type.kind === "nullable" && param.type.inner?.kind === "enum") fPath = fPath.replace(`{${param.name}}`, `{enum_value(${fieldName$4(param.name)})}`);
8709
- else fPath = fPath.replace(`{${param.name}}`, `{${fieldName$4(param.name)}}`);
8710
- return `f"${fPath}"`;
8812
+ const enumParams = /* @__PURE__ */ new Set();
8813
+ for (const p of op.pathParams) if (p.type.kind === "enum" || p.type.kind === "nullable" && p.type.inner?.kind === "enum") enumParams.add(p.name);
8814
+ return buildPythonPathExpression(op.path, { enumParams });
8711
8815
  }
8712
8816
  //#endregion
8713
8817
  //#region src/shared/non-spec-services.ts
@@ -10624,6 +10728,28 @@ function generateModels$4(models, ctx) {
10624
10728
  return files;
10625
10729
  }
10626
10730
  /**
10731
+ * Resolve a degenerate union to a single TypeRef when possible.
10732
+ * - allOf: PHP collapses to the first variant (mirrors `mapTypeRef`).
10733
+ * - oneOf/anyOf where every variant has the same generated PHP type: collapse
10734
+ * to that variant (e.g. discriminated unions whose branches the IR pinned to
10735
+ * one model name).
10736
+ * Returns null when the union is genuinely polymorphic.
10737
+ */
10738
+ function resolveDegenerateUnion(ref) {
10739
+ if (ref.kind !== "union") return null;
10740
+ if (ref.compositionKind === "allOf") return ref.variants[0] ?? null;
10741
+ if (ref.variants.length === 0) return null;
10742
+ const signature = (v) => {
10743
+ if (v.kind === "model") return `model:${v.name}`;
10744
+ if (v.kind === "enum") return `enum:${v.name}`;
10745
+ return `kind:${v.kind}`;
10746
+ };
10747
+ const first = ref.variants[0];
10748
+ const firstSig = signature(first);
10749
+ for (const v of ref.variants) if (signature(v) !== firstSig) return null;
10750
+ return first;
10751
+ }
10752
+ /**
10627
10753
  * Generate the fromArray accessor expression for a field.
10628
10754
  */
10629
10755
  function generateFromArrayAccessor(ref, wireName, required) {
@@ -10654,7 +10780,11 @@ function generateFromArrayValue(ref, accessor) {
10654
10780
  if (ref.items.kind === "primitive" && ref.items.format === "date-time") return `array_map(fn ($item) => new \\DateTimeImmutable($item), ${accessor})`;
10655
10781
  return accessor;
10656
10782
  case "nullable": return generateFromArrayValue(ref.inner, accessor);
10657
- case "union": return accessor;
10783
+ case "union": {
10784
+ const resolved = resolveDegenerateUnion(ref);
10785
+ if (resolved) return generateFromArrayValue(resolved, accessor);
10786
+ return accessor;
10787
+ }
10658
10788
  case "map": return accessor;
10659
10789
  case "literal": return accessor;
10660
10790
  }
@@ -10676,6 +10806,10 @@ function isComplexType(ref) {
10676
10806
  case "enum": return true;
10677
10807
  case "array": return isComplexType(ref.items);
10678
10808
  case "nullable": return isComplexType(ref.inner);
10809
+ case "union": {
10810
+ const resolved = resolveDegenerateUnion(ref);
10811
+ return resolved ? isComplexType(resolved) : false;
10812
+ }
10679
10813
  default: return false;
10680
10814
  }
10681
10815
  }
@@ -10697,7 +10831,11 @@ function generateToArrayValue(ref, accessor, nullable = false) {
10697
10831
  return accessor;
10698
10832
  case "nullable": return generateToArrayValue(ref.inner, accessor, true);
10699
10833
  case "map": return accessor;
10700
- case "union": return accessor;
10834
+ case "union": {
10835
+ const resolved = resolveDegenerateUnion(ref);
10836
+ if (resolved) return generateToArrayValue(resolved, accessor, nullable);
10837
+ return accessor;
10838
+ }
10701
10839
  case "literal": return accessor;
10702
10840
  }
10703
10841
  }
@@ -10760,6 +10898,39 @@ function generateEnums$4(enums, ctx) {
10760
10898
  return files;
10761
10899
  }
10762
10900
  //#endregion
10901
+ //#region src/php/path-expression.ts
10902
+ /**
10903
+ * Build the PHP expression that the SDK passes to HttpClient as `path:`.
10904
+ *
10905
+ * Concatenation, not interpolation: PHP does not allow function calls inside
10906
+ * "..." strings, and every parameter must be wrapped in `rawurlencode(...)` so
10907
+ * that an unencoded "../" in a caller-supplied id cannot be normalized by
10908
+ * libcurl into a different endpoint of the WorkOS API while still
10909
+ * authenticated with the application's API key.
10910
+ *
10911
+ * "/orgs" → `'orgs'`
10912
+ * "/orgs/{id}" → `'orgs/' . rawurlencode($id)`
10913
+ * "/orgs/{id}/users" → `'orgs/' . rawurlencode($id) . '/users'`
10914
+ * "/orgs/{id}" with id ∈ valueAccessorParams → `'orgs/' . rawurlencode($id->value)`
10915
+ */
10916
+ function buildPhpPathExpression(rawPath, options = {}) {
10917
+ const segments = parsePathTemplate(rawPath, { stripLeadingSlash: true });
10918
+ if (segments.length === 0) return "''";
10919
+ if (!hasPathParams(segments)) return phpSingleQuoted(segments[0].value);
10920
+ const valueAccessor = options.valueAccessorParams;
10921
+ const parts = [];
10922
+ for (const seg of segments) if (seg.kind === "literal") parts.push(phpSingleQuoted(seg.value));
10923
+ else {
10924
+ const varName = fieldName$3(seg.name);
10925
+ const accessor = valueAccessor?.has(seg.name) ? `$${varName}->value` : `$${varName}`;
10926
+ parts.push(`rawurlencode(${accessor})`);
10927
+ }
10928
+ return parts.join(" . ");
10929
+ }
10930
+ function phpSingleQuoted(literal) {
10931
+ return `'${literal.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
10932
+ }
10933
+ //#endregion
10763
10934
  //#region src/php/wrappers.ts
10764
10935
  /**
10765
10936
  * Generate PHP wrapper methods for split union operations.
@@ -10819,14 +10990,11 @@ function emitWrapperMethod$4(lines, resolvedOp, wrapper, ctx) {
10819
10990
  lines.push(` $body['${clientField}'] = ${clientExpr};`);
10820
10991
  }
10821
10992
  const httpMethod = op.httpMethod.toUpperCase();
10822
- let path = op.path.startsWith("/") ? op.path.slice(1) : op.path;
10823
- const hasInterpolation = /\{[^}]+\}/.test(path);
10824
- path = path.replace(/\{([^}]+)\}/g, (_match, param) => `{$${fieldName$3(param)}}`);
10825
- const pathQuote = hasInterpolation ? "\"" : "'";
10993
+ const pathExpr = buildPhpPathExpression(op.path);
10826
10994
  lines.push("");
10827
10995
  lines.push(" $response = $this->client->request(");
10828
10996
  lines.push(` method: '${httpMethod}',`);
10829
- lines.push(` path: ${pathQuote}${path}${pathQuote},`);
10997
+ lines.push(` path: ${pathExpr},`);
10830
10998
  lines.push(" body: $body,");
10831
10999
  lines.push(" options: $options,");
10832
11000
  lines.push(" );");
@@ -11311,16 +11479,9 @@ function buildBodyParamMap(op, bodyModel) {
11311
11479
  return map;
11312
11480
  }
11313
11481
  function buildPathString(op) {
11314
- let path = op.path.startsWith("/") ? op.path.slice(1) : op.path;
11315
- if (op.pathParams.length === 0) return `'${path}'`;
11316
- const paramExprs = /* @__PURE__ */ new Map();
11317
- for (const p of op.pathParams) {
11318
- const phpName = fieldName$3(p.name);
11319
- const isEnum = p.type.kind === "enum" || p.type.kind === "model";
11320
- paramExprs.set(p.name, isEnum ? `{$${phpName}->value}` : `{$${phpName}}`);
11321
- }
11322
- path = path.replace(/\{([^}]+)\}/g, (_match, param) => paramExprs.get(param) ?? `{$${fieldName$3(param)}}`);
11323
- return `"${path}"`;
11482
+ const valueAccessor = /* @__PURE__ */ new Set();
11483
+ for (const p of op.pathParams) if (p.type.kind === "enum" || p.type.kind === "model") valueAccessor.add(p.name);
11484
+ return buildPhpPathExpression(op.path, { valueAccessorParams: valueAccessor });
11324
11485
  }
11325
11486
  function isEnumType(ref) {
11326
11487
  if (ref.kind === "enum") return true;
@@ -13224,7 +13385,7 @@ function generateMethod$1(serviceType, mountName, method, op, plan, _ctx, resolv
13224
13385
  if (paramsType) params.push(`params ${paramsType}`);
13225
13386
  params.push("opts ...RequestOption");
13226
13387
  lines.push(`func (s *${serviceType}) ${method}(${params.join(", ")}) ${returnType} {`);
13227
- const pathExpr = buildPathExpr$2(op);
13388
+ const pathExpr = buildPathExpr$1(op);
13228
13389
  if (isUrlBuilder) {
13229
13390
  emitUrlBuilderMethod(lines, op, pathExpr, resolvedOp, paramsType);
13230
13391
  lines.push("}");
@@ -13523,7 +13684,7 @@ function needsStringsImport(operations, resolvedLookup) {
13523
13684
  }
13524
13685
  return false;
13525
13686
  }
13526
- function buildPathExpr$2(op) {
13687
+ function buildPathExpr$1(op) {
13527
13688
  if (op.pathParams.length === 0) return `"${op.path}"`;
13528
13689
  let fmtStr = op.path;
13529
13690
  const args = [];
@@ -15967,7 +16128,7 @@ function generateMethod(_serviceType, mountName, method, methodStem, op, plan, c
15967
16128
  for (const field of inferFromClient) if (field === "client_id") lines.push(` options.${fieldName$1(field)} = this.Client.RequireClientId();`);
15968
16129
  else lines.push(` options.${fieldName$1(field)} = this.Client.${clientFieldExpression$1(field)} ?? string.Empty;`);
15969
16130
  }
15970
- const pathExpr = buildPathExpr$1(op);
16131
+ const pathExpr = buildPathExpr(op);
15971
16132
  const needsInlineRequest = isUrlBuilder || hasBearerOverride && !!bearerParamName || hasGroups;
15972
16133
  const optionsArg = optionsClass ? "options" : "null";
15973
16134
  if (needsInlineRequest) {
@@ -16031,7 +16192,7 @@ function generateAutoPagingMethod(mountName, method, methodStem, op, plan, ctx,
16031
16192
  params.push("CancellationToken cancellationToken = default");
16032
16193
  lines.push(` public virtual IAsyncEnumerable<${itemType}> ${methodStem}AutoPagingAsync(${params.join(", ")})`);
16033
16194
  lines.push(" {");
16034
- const pathExpr = buildPathExpr$1(op);
16195
+ const pathExpr = buildPathExpr(op);
16035
16196
  const optionsArg = optionsClass ? "options" : "null";
16036
16197
  lines.push(` return this.ListAutoPagingAsync<${itemType}>(${pathExpr}, ${optionsArg}, requestOptions, cancellationToken);`);
16037
16198
  lines.push(" }");
@@ -16055,7 +16216,7 @@ function optionsClassName(mountName, method) {
16055
16216
  if (methodStem.startsWith(prefix)) return `${methodStem}Options`;
16056
16217
  return `${prefix}${methodStem}Options`;
16057
16218
  }
16058
- function buildPathExpr$1(op) {
16219
+ function buildPathExpr(op) {
16059
16220
  if (op.pathParams.length === 0) return `"${op.path}"`;
16060
16221
  let interpolated = op.path;
16061
16222
  for (const p of sortPathParamsByTemplateOrder$1(op)) interpolated = interpolated.replace(`{${p.name}}`, `{Uri.EscapeDataString(${localName(p.name)})}`);
@@ -17834,6 +17995,45 @@ function structuralHash(model) {
17834
17995
  return model.fields.map((f) => `${f.name}:${JSON.stringify(normalizeTypeForHash(f.type))}:${f.required}`).sort().join("|");
17835
17996
  }
17836
17997
  //#endregion
17998
+ //#region src/kotlin/path-expression.ts
17999
+ /**
18000
+ * The fully-qualified runtime helper that the generated code calls. Kotlin
18001
+ * doesn't ship a path-segment URL encoder out of the box (java.net.URLEncoder
18002
+ * is form-encoding — it encodes space as "+", which is wrong for path
18003
+ * segments). The runtime helper lives in workos-kotlin at
18004
+ * com.workos.common.http.encodePathSegment.
18005
+ */
18006
+ const KOTLIN_PATH_ENCODE_IMPORT = "com.workos.common.http.encodePathSegment";
18007
+ /**
18008
+ * Build the Kotlin string-template that the SDK passes as the request path.
18009
+ *
18010
+ * Every {paramName} placeholder is wrapped in `encodePathSegment(...)` so a
18011
+ * caller-supplied id containing "../" cannot be normalized by the underlying
18012
+ * HTTP transport into a different endpoint of the WorkOS API while still
18013
+ * authenticated with the application's API key.
18014
+ *
18015
+ * "/orgs" → `"orgs"`
18016
+ * "/orgs/{id}" → `"orgs/${encodePathSegment(id)}"`
18017
+ * "/orgs/{id}/foo" → `"orgs/${encodePathSegment(id)}/foo"`
18018
+ */
18019
+ function buildKotlinPathExpression(rawPath) {
18020
+ const segments = parsePathTemplate(rawPath);
18021
+ if (!hasPathParams(segments)) return {
18022
+ expression: ktLiteral(rawPath),
18023
+ requiresEncodeImport: false
18024
+ };
18025
+ let body = "";
18026
+ for (const seg of segments) if (seg.kind === "literal") body += escapeKotlinStringLiteral(seg.value);
18027
+ else body += `\${encodePathSegment(${propertyName(seg.name)})}`;
18028
+ return {
18029
+ expression: `"${body}"`,
18030
+ requiresEncodeImport: true
18031
+ };
18032
+ }
18033
+ function escapeKotlinStringLiteral(literal) {
18034
+ return literal.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
18035
+ }
18036
+ //#endregion
17837
18037
  //#region src/kotlin/wrappers.ts
17838
18038
  /**
17839
18039
  * Emit Kotlin wrapper methods for a union-split operation. Each wrapper
@@ -17919,7 +18119,7 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
17919
18119
  }
17920
18120
  lines.push(` )`);
17921
18121
  } else lines.push(` val body = linkedMapOf<String, Any?>()`);
17922
- const pathExpr = buildPathExpr(op.path, pathParams);
18122
+ const pathExpr = buildKotlinPathExpression(op.path).expression;
17923
18123
  const httpMethod = op.httpMethod.toUpperCase();
17924
18124
  lines.push(` val config =`);
17925
18125
  lines.push(` RequestConfig(`);
@@ -17937,17 +18137,6 @@ function emitWrapperMethod$1(resolvedOp, wrapper, ctx) {
17937
18137
  function escapeKdoc$1(s) {
17938
18138
  return s.replace(/\*\//g, "*​/");
17939
18139
  }
17940
- function buildPathExpr(path, pathParams) {
17941
- if (pathParams.length === 0) return ktLiteral(path);
17942
- let result = path;
17943
- for (const pp of pathParams) {
17944
- const placeholder = `{${pp.name}}`;
17945
- const propName = propertyName(pp.name);
17946
- const replacement = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(propName) ? `\$${propName}` : `\${${propName}}`;
17947
- result = result.replaceAll(placeholder, replacement);
17948
- }
17949
- return `"${result.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
17950
- }
17951
18140
  //#endregion
17952
18141
  //#region src/kotlin/overrides.ts
17953
18142
  /**
@@ -18020,6 +18209,7 @@ function generateApiClass(mountName, operations, ctx, resolvedLookup) {
18020
18209
  for (const rp of resolvedParams) if (rp.field) registerTypeImports(rp.field.type, imports, ctx);
18021
18210
  }
18022
18211
  imports.add("com.workos.common.http.bodyOf");
18212
+ if (/\{[^{}]+\}/.test(resolvedOp.operation.path)) imports.add(KOTLIN_PATH_ENCODE_IMPORT);
18023
18213
  const wrapperLines = generateWrapperMethods$1(resolvedOp, ctx);
18024
18214
  if (body.length > 0) body.push("");
18025
18215
  for (const line of wrapperLines) body.push(line);
@@ -18182,7 +18372,9 @@ function renderMethod(_mountName, method, op, ctx, resolvedOp, imports) {
18182
18372
  const specDeclaresBody = op.requestBody !== void 0;
18183
18373
  const hasBody = methodAlwaysHasBody || specDeclaresBody && httpMethod !== "GET" || (httpMethod === "PUT" || httpMethod === "PATCH" || httpMethod === "POST" || httpMethod === "DELETE") && (Object.keys(defaults).length > 0 || inferFromClient.length > 0) && specDeclaresBody;
18184
18374
  const appendDefaultsAsQuery = !hasBody && (Object.keys(defaults).length > 0 || inferFromClient.length > 0);
18185
- const pathExpr = buildPathExpression(op.path, pathParams);
18375
+ const pathBuilt = buildKotlinPathExpression(op.path);
18376
+ const pathExpr = pathBuilt.expression;
18377
+ if (pathBuilt.requiresEncodeImport) imports.add(KOTLIN_PATH_ENCODE_IMPORT);
18186
18378
  if (op.path === "/user_management/authenticate" && httpMethod === "POST" && plan.responseModelName === "AuthenticateResponse") {
18187
18379
  imports.add("com.workos.models.AuthenticateResponse");
18188
18380
  const grantType = defaults.grant_type ?? "authorization_code";
@@ -18405,20 +18597,6 @@ function queryParamToString(type, varName) {
18405
18597
  if (type.kind === "primitive" && type.type === "string") return varName;
18406
18598
  return `${varName}.toString()`;
18407
18599
  }
18408
- function buildPathExpression(path, pathParams) {
18409
- if (pathParams.length === 0) return ktLiteral(path);
18410
- let result = path;
18411
- for (const pp of pathParams) {
18412
- const placeholder = `{${pp.name}}`;
18413
- const propName = propertyName(pp.name);
18414
- const replacement = isBareIdentifier(propName) ? `\$${propName}` : `\${${propName}}`;
18415
- result = result.replaceAll(placeholder, replacement);
18416
- }
18417
- return `"${result.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
18418
- }
18419
- function isBareIdentifier(name) {
18420
- return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
18421
- }
18422
18600
  function pickNamedQueryParam(sorted, name) {
18423
18601
  const match = sorted.find((p) => p.name === name);
18424
18602
  return match ? propertyName(match.name) : "null";
@@ -21975,4 +22153,4 @@ const workosEmittersPlugin = {
21975
22153
  //#endregion
21976
22154
  export { nodeEmitter as _, rustExtractor as a, pythonExtractor as c, rubyEmitter as d, kotlinEmitter as f, pythonEmitter as g, phpEmitter as h, kotlinExtractor as i, rubyExtractor as l, goEmitter as m, elixirExtractor as n, goExtractor as o, dotnetEmitter as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u };
21977
22155
 
21978
- //# sourceMappingURL=plugin-h8Onp2Ma.mjs.map
22156
+ //# sourceMappingURL=plugin-BoTAX4nl.mjs.map