@workos/oagen-emitters 0.12.2 → 0.12.4

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.12.2"
2
+ ".": "0.12.4"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.4](https://github.com/workos/oagen-emitters/compare/v0.12.3...v0.12.4) (2026-05-19)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **node:** emit serializers/index.ts barrel for owned and adopted services ([#109](https://github.com/workos/oagen-emitters/issues/109)) ([e176552](https://github.com/workos/oagen-emitters/commit/e17655296de50c9df759b84273b104ae828cd08e))
9
+
10
+ ## [0.12.3](https://github.com/workos/oagen-emitters/compare/v0.12.2...v0.12.3) (2026-05-18)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **node:** generate tests for adopted services and fix related bugs ([#107](https://github.com/workos/oagen-emitters/issues/107)) ([abe877b](https://github.com/workos/oagen-emitters/commit/abe877bd6489365d0f145a1739a7c449b15760c2))
16
+
3
17
  ## [0.12.2](https://github.com/workos/oagen-emitters/compare/v0.12.1...v0.12.2) (2026-05-15)
4
18
 
5
19
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/node/index.ts","../src/python/index.ts","../src/php/index.ts","../src/go/index.ts","../src/dotnet/index.ts","../src/kotlin/index.ts","../src/ruby/index.ts","../src/rust/index.ts"],"mappings":";;;;;cAuVa,WAAA,EAAa,OAwFzB;;;cCxYY,aAAA,EAAe,OA+D3B;;;cClEY,UAAA,EAAY,OA2DxB;;;cClEY,SAAA,EAAW,OAyEvB;;;cCnCY,aAAA,EAAe,OAiR3B;;;cCrTY,aAAA,EAAe,OA6E3B;;;cChFY,WAAA,EAAa,OAkEzB;;;cC1DY,WAAA,EAAa,OA0DzB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/node/index.ts","../src/python/index.ts","../src/php/index.ts","../src/go/index.ts","../src/dotnet/index.ts","../src/kotlin/index.ts","../src/ruby/index.ts","../src/rust/index.ts"],"mappings":";;;;;cAgWa,WAAA,EAAa,OAwFzB;;;cCjZY,aAAA,EAAe,OA+D3B;;;cClEY,UAAA,EAAY,OA2DxB;;;cClEY,SAAA,EAAW,OAyEvB;;;cCnCY,aAAA,EAAe,OAiR3B;;;cCrTY,aAAA,EAAe,OA6E3B;;;cChFY,WAAA,EAAa,OAkEzB;;;cC1DY,WAAA,EAAa,OA0DzB"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as pythonEmitter, a as rustExtractor, c as pythonExtractor, d as rustEmitter, f as rubyEmitter, g as phpEmitter, h as goEmitter, i as kotlinExtractor, l as rubyExtractor, m as dotnetEmitter, n as elixirExtractor, o as goExtractor, p as kotlinEmitter, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor, v as nodeEmitter } from "./plugin-eCuvoL1T.mjs";
1
+ import { _ as pythonEmitter, a as rustExtractor, c as pythonExtractor, d as rustEmitter, f as rubyEmitter, g as phpEmitter, h as goEmitter, i as kotlinExtractor, l as rubyExtractor, m as dotnetEmitter, n as elixirExtractor, o as goExtractor, p as kotlinEmitter, r as dotnetExtractor, s as phpExtractor, t as workosEmittersPlugin, u as nodeExtractor, v as nodeEmitter } from "./plugin-nmiHN7Ko.mjs";
2
2
  export { dotnetEmitter, dotnetExtractor, elixirExtractor, goEmitter, goExtractor, kotlinEmitter, kotlinExtractor, nodeEmitter, nodeExtractor, phpEmitter, phpExtractor, pythonEmitter, pythonExtractor, rubyEmitter, rubyExtractor, rustEmitter, rustExtractor, workosEmittersPlugin };
@@ -338,6 +338,9 @@ let adoptedModelNames = /* @__PURE__ */ new Set();
338
338
  function setAdoptedModelNames(names) {
339
339
  adoptedModelNames = names;
340
340
  }
341
+ function isAdoptedModelName(name) {
342
+ return adoptedModelNames.has(name);
343
+ }
341
344
  /**
342
345
  * Wire/response interface name.
343
346
  *
@@ -4291,17 +4294,23 @@ function hasPathParams(segments) {
4291
4294
  * "/orgs" → `'orgs'`
4292
4295
  * "/orgs/{id}" → `` `orgs/${encodeURIComponent(id)}` ``
4293
4296
  * "/orgs/{id}/foo" → `` `orgs/${encodeURIComponent(id)}/foo` ``
4297
+ *
4298
+ * `paramNameMap` lets a caller override the local variable name used for a
4299
+ * spec parameter — used by the options-object code path so the URL template
4300
+ * references the SDK's public field name (e.g. `organizationMembershipId`)
4301
+ * instead of the spec's path-param name (e.g. `omId`), avoiding a
4302
+ * destructure rename in the method body.
4294
4303
  */
4295
- function buildNodePathExpression(rawPath) {
4304
+ function buildNodePathExpression(rawPath, paramNameMap) {
4296
4305
  const segments = parsePathTemplate(rawPath);
4297
4306
  if (!hasPathParams(segments)) return `'${rawPath}'`;
4298
4307
  let body = "";
4299
- for (const seg of segments) body += renderSegment(seg);
4308
+ for (const seg of segments) body += renderSegment(seg, paramNameMap);
4300
4309
  return `\`${body}\``;
4301
4310
  }
4302
- function renderSegment(seg) {
4311
+ function renderSegment(seg, paramNameMap) {
4303
4312
  if (seg.kind === "literal") return seg.value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
4304
- return `\${encodeURIComponent(${fieldName$6(seg.name)})}`;
4313
+ return `\${encodeURIComponent(${paramNameMap?.get(seg.name) ?? fieldName$6(seg.name)})}`;
4305
4314
  }
4306
4315
  //#endregion
4307
4316
  //#region src/node/wrappers.ts
@@ -5092,8 +5101,8 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
5092
5101
  const optionParam = optionsObjectParam$1(baselineMethod);
5093
5102
  if (!optionParam) return false;
5094
5103
  const responseModel = plan.responseModelName ? resolveInterfaceName(plan.responseModelName, ctx) : null;
5095
- const pathStr = buildPathStr(op);
5096
5104
  const pathBindings = buildOptionsObjectPathBindings(op, optionParam.type, ctx);
5105
+ const pathStr = buildPathStr(op, buildOptionsObjectPathParamMap(op, optionParam.type, ctx));
5097
5106
  if (plan.isPaginated && op.pagination && op.httpMethod === "get") {
5098
5107
  let itemRawName = op.pagination.itemType.kind === "model" ? op.pagination.itemType.name : null;
5099
5108
  if (itemRawName) {
@@ -5199,11 +5208,23 @@ function renderOptionsObjectDestructure(lines, pathBindings, restName) {
5199
5208
  else if (restName) lines.push(` const ${restName} = options;`);
5200
5209
  }
5201
5210
  function buildOptionsObjectPathBindings(op, optionType, ctx) {
5202
- return op.pathParams.map((param) => {
5211
+ return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx));
5212
+ }
5213
+ /**
5214
+ * Map spec path-param names (e.g. `omId`) to the SDK field name exposed on
5215
+ * the options interface (e.g. `organizationMembershipId`). Empty when every
5216
+ * path param's spec name already matches the SDK field. Consumed by
5217
+ * `buildNodePathExpression` so the URL template binds to the same identifier
5218
+ * the destructure does.
5219
+ */
5220
+ function buildOptionsObjectPathParamMap(op, optionType, ctx) {
5221
+ const map = /* @__PURE__ */ new Map();
5222
+ for (const param of op.pathParams) {
5203
5223
  const localName = fieldName$6(param.name);
5204
- const optionField = resolveOptionsObjectField$1(localName, optionType, ctx);
5205
- return optionField === localName ? localName : `${optionField}: ${localName}`;
5206
- });
5224
+ const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx);
5225
+ if (sdkField !== localName) map.set(param.name, sdkField);
5226
+ }
5227
+ return map;
5207
5228
  }
5208
5229
  function resolveOptionsObjectField$1(localName, optionType, ctx) {
5209
5230
  const fields = ctx.apiSurface?.interfaces?.[optionType]?.fields;
@@ -5464,8 +5485,8 @@ function renderQueryExpr(queryParams) {
5464
5485
  }
5465
5486
  return `options ? { ${parts.join(", ")} } : undefined`;
5466
5487
  }
5467
- function buildPathStr(op) {
5468
- return buildNodePathExpression(op.path);
5488
+ function buildPathStr(op, paramNameMap) {
5489
+ return buildNodePathExpression(op.path, paramNameMap);
5469
5490
  }
5470
5491
  function buildPathParams(op, specEnumNames) {
5471
5492
  const declaredNames = new Set(op.pathParams.map((p) => fieldName$6(p.name)));
@@ -5842,6 +5863,7 @@ function isSupportedFieldType(ref, ownerModelName, shared, ctx) {
5842
5863
  if (ref.name === ownerModelName) return true;
5843
5864
  const resolvedName = resolveInterfaceName(ref.name, ctx);
5844
5865
  if (ctx.apiSurface?.interfaces?.[resolvedName] || ctx.apiSurface?.typeAliases?.[resolvedName]) return true;
5866
+ if (isAdoptedModelName(ref.name)) return true;
5845
5867
  return liveSurfaceHasManagedFile(`src/${shared.resolveDir(shared.modelToService.get(ref.name))}/interfaces/${fileName$3(ref.name)}.interface.ts`);
5846
5868
  }
5847
5869
  case "enum": {
@@ -6290,6 +6312,32 @@ function generateSerializers(models, ctx, shared) {
6290
6312
  });
6291
6313
  }
6292
6314
  ctx._skippedSerializeModels = skippedSerializeModels;
6315
+ const serializersByDir = /* @__PURE__ */ new Map();
6316
+ for (const f of files) {
6317
+ const match = f.path.match(/^src\/([^/]+)\/serializers\/(.+)\.serializer\.ts$/);
6318
+ if (!match) continue;
6319
+ const [, dir, stem] = match;
6320
+ if (!serializersByDir.has(dir)) serializersByDir.set(dir, /* @__PURE__ */ new Set());
6321
+ serializersByDir.get(dir).add(stem);
6322
+ }
6323
+ const liveRootForBarrel = ctx.outputDir ?? ctx.targetDir;
6324
+ for (const [dir, stems] of serializersByDir) {
6325
+ if (liveRootForBarrel) {
6326
+ const serializersDir = path.join(liveRootForBarrel, "src", dir, "serializers");
6327
+ try {
6328
+ for (const entry of fs.readdirSync(serializersDir)) {
6329
+ if (!entry.endsWith(".serializer.ts")) continue;
6330
+ stems.add(entry.replace(/\.serializer\.ts$/, ""));
6331
+ }
6332
+ } catch {}
6333
+ }
6334
+ const lines = [...stems].sort().map((stem) => `export * from './${stem}.serializer';`);
6335
+ files.push({
6336
+ path: `src/${dir}/serializers/index.ts`,
6337
+ content: lines.join("\n") + "\n",
6338
+ overwriteExisting: true
6339
+ });
6340
+ }
6293
6341
  return files;
6294
6342
  }
6295
6343
  function generateModelsAndSerializers(models, ctx) {
@@ -6901,6 +6949,7 @@ function generateTests$7(spec, ctx) {
6901
6949
  if (ops.length === 0) continue;
6902
6950
  const propName = mountAccessors.get(mountName) ?? servicePropertyName$4(mountName);
6903
6951
  if (ctx.apiSurface && baselineWorkOSProps.size > 0 && !baselineWorkOSProps.has(propName)) continue;
6952
+ if (resolveResourceClassName$3(mergedService, ctx) !== mountName) continue;
6904
6953
  const testService = ops.length < operations.length ? {
6905
6954
  ...mergedService,
6906
6955
  operations: ops
@@ -7076,8 +7125,11 @@ function renderBodyTest(lines, op, plan, method, serviceProp, modelMap, ctx, ent
7076
7125
  const payload = buildTestPayload(op, modelMap);
7077
7126
  const payloadArg = payload ? payload.camelCaseObj : fallbackBodyArg(op, modelMap);
7078
7127
  const allArgs = buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) ?? (pathArgs ? `${pathArgs}, ${payloadArg}` : payloadArg);
7128
+ const isArrayResponse = !!plan.isArrayResponse;
7129
+ const fixtureExpr = isArrayResponse ? `[${fixture}]` : fixture;
7130
+ const accessor = isArrayResponse ? "result[0]" : "result";
7079
7131
  lines.push(" it('sends the correct request and returns result', async () => {");
7080
- lines.push(` fetchOnce(${fixture});`);
7132
+ lines.push(` fetchOnce(${fixtureExpr});`);
7081
7133
  lines.push("");
7082
7134
  lines.push(` const result = await workos.${serviceProp}.${method}(${allArgs});`);
7083
7135
  lines.push("");
@@ -7086,12 +7138,13 @@ function renderBodyTest(lines, op, plan, method, serviceProp, modelMap, ctx, ent
7086
7138
  lines.push(` expect(new URL(String(fetchURL())).pathname).toBe('${expectedPath}');`);
7087
7139
  if (payload) lines.push(` expect(fetchBody()).toEqual(expect.objectContaining(${payload.snakeCaseObj}));`);
7088
7140
  else lines.push(" expect(fetchBody()).toBeDefined();");
7141
+ if (isArrayResponse) lines.push(" expect(Array.isArray(result)).toBe(true);");
7089
7142
  const bodyHelperName = ctx ? `expect${resolveInterfaceName(responseModelName, ctx)}` : null;
7090
- if (bodyHelperName && entityHelpers?.has(bodyHelperName)) lines.push(` ${bodyHelperName}(result);`);
7143
+ if (bodyHelperName && entityHelpers?.has(bodyHelperName)) lines.push(` ${bodyHelperName}(${accessor});`);
7091
7144
  else {
7092
7145
  const responseModel = modelMap.get(responseModelName);
7093
7146
  if (responseModel) {
7094
- const assertions = buildFieldAssertions(responseModel, "result", modelMap);
7147
+ const assertions = buildFieldAssertions(responseModel, accessor, modelMap);
7095
7148
  if (assertions.length > 0) for (const assertion of assertions) lines.push(` ${assertion}`);
7096
7149
  else lines.push(" expect(result).toBeDefined();");
7097
7150
  } else lines.push(" expect(result).toBeDefined();");
@@ -7103,20 +7156,24 @@ function renderGetTest(lines, op, plan, method, serviceProp, modelMap, ctx, enti
7103
7156
  const fixture = `${toCamelCase(responseModelName)}Fixture`;
7104
7157
  const pathArgs = buildTestPathArgs(op);
7105
7158
  const optionsArg = buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx);
7159
+ const isArrayResponse = !!plan.isArrayResponse;
7160
+ const fixtureExpr = isArrayResponse ? `[${fixture}]` : fixture;
7161
+ const accessor = isArrayResponse ? "result[0]" : "result";
7106
7162
  lines.push(" it('returns the expected result', async () => {");
7107
- lines.push(` fetchOnce(${fixture});`);
7163
+ lines.push(` fetchOnce(${fixtureExpr});`);
7108
7164
  lines.push("");
7109
7165
  lines.push(` const result = await workos.${serviceProp}.${method}(${optionsArg ?? pathArgs});`);
7110
7166
  lines.push("");
7111
7167
  lines.push(` expect(fetchMethod()).toBe('${op.httpMethod.toUpperCase()}');`);
7112
7168
  const expectedPathGet = buildExpectedPath$4(op);
7113
7169
  lines.push(` expect(new URL(String(fetchURL())).pathname).toBe('${expectedPathGet}');`);
7170
+ if (isArrayResponse) lines.push(" expect(Array.isArray(result)).toBe(true);");
7114
7171
  const helperName = ctx ? `expect${resolveInterfaceName(responseModelName, ctx)}` : null;
7115
- if (helperName && entityHelpers?.has(helperName)) lines.push(` ${helperName}(result);`);
7172
+ if (helperName && entityHelpers?.has(helperName)) lines.push(` ${helperName}(${accessor});`);
7116
7173
  else {
7117
7174
  const responseModel = modelMap.get(responseModelName);
7118
7175
  if (responseModel) {
7119
- const assertions = buildFieldAssertions(responseModel, "result", modelMap);
7176
+ const assertions = buildFieldAssertions(responseModel, accessor, modelMap);
7120
7177
  if (assertions.length > 0) for (const assertion of assertions) lines.push(` ${assertion}`);
7121
7178
  else lines.push(" expect(result).toBeDefined();");
7122
7179
  } else lines.push(" expect(result).toBeDefined();");
@@ -7219,23 +7276,28 @@ function generateEntityHelpers(plans, modelMap, ctx) {
7219
7276
  * nested models so we still get meaningful assertions instead of a bare
7220
7277
  * `toBeDefined()`.
7221
7278
  */
7279
+ function isDateTimeFieldType(type) {
7280
+ if (type.kind === "primitive") return type.format === "date-time";
7281
+ if (type.kind === "nullable") return isDateTimeFieldType(type.inner);
7282
+ return false;
7283
+ }
7222
7284
  function buildFieldAssertions(model, accessor, modelMap) {
7223
7285
  const assertions = [];
7224
7286
  for (const field of model.fields) {
7225
7287
  if (!field.required) continue;
7288
+ const domainField = fieldName$6(field.name);
7289
+ const fieldAccessor = isDateTimeFieldType(field.type) ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
7226
7290
  if (field.example !== void 0) {
7227
- const domainField = fieldName$6(field.name);
7228
7291
  if (typeof field.example === "object" && field.example !== null) assertions.push(`expect(${accessor}.${domainField}).toEqual(${JSON.stringify(field.example)});`);
7229
7292
  else {
7230
7293
  const exampleLiteral = typeof field.example === "string" ? `'${field.example}'` : String(field.example);
7231
- assertions.push(`expect(${accessor}.${domainField}).toBe(${exampleLiteral});`);
7294
+ assertions.push(`expect(${fieldAccessor}).toBe(${exampleLiteral});`);
7232
7295
  }
7233
7296
  continue;
7234
7297
  }
7235
7298
  const value = fixtureValueForType(field.type, field.name, model.name);
7236
7299
  if (value === null) continue;
7237
- const domainField = fieldName$6(field.name);
7238
- assertions.push(`expect(${accessor}.${domainField}).toBe(${value});`);
7300
+ assertions.push(`expect(${fieldAccessor}).toBe(${value});`);
7239
7301
  }
7240
7302
  if (assertions.length === 0 && modelMap) for (const field of model.fields) {
7241
7303
  if (!field.required) continue;
@@ -7700,9 +7762,12 @@ function applyLiveSurface(files, ctx, surface) {
7700
7762
  if (policy.hasExistingSdk && !policy.managedPaths.has(f.path) && !canCreateNewPath(f.path, policy)) continue;
7701
7763
  if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path) && !ownedPath) continue;
7702
7764
  if (isUserOwnedAfterFirstEmit(f.path)) {
7765
+ const dir = topLevelDir(f.path);
7766
+ const isAdoptedDir = dir !== void 0 && policy.adoptedServiceDirs.has(dir);
7767
+ const isManagedDir = ownedPath || isAdoptedDir;
7703
7768
  if (surface.files.has(f.path) && !surface.autogenFiles.has(f.path)) continue;
7704
- if (!ownedPath && !surface.autogenFiles.has(f.path)) continue;
7705
- if (ownedPath && !policy.regenerateOwnedTests) continue;
7769
+ if (!isManagedDir && !surface.autogenFiles.has(f.path)) continue;
7770
+ if (isManagedDir && !policy.regenerateOwnedTests) continue;
7706
7771
  }
7707
7772
  if (surface.autogenFiles.has(f.path) || ownedPath) {
7708
7773
  f.overwriteExisting = true;
@@ -26520,4 +26585,4 @@ const workosEmittersPlugin = {
26520
26585
  //#endregion
26521
26586
  export { pythonEmitter as _, rustExtractor as a, pythonExtractor as c, rustEmitter as d, rubyEmitter as f, phpEmitter as g, goEmitter as h, kotlinExtractor as i, rubyExtractor as l, dotnetEmitter as m, elixirExtractor as n, goExtractor as o, kotlinEmitter as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u, nodeEmitter as v };
26522
26587
 
26523
- //# sourceMappingURL=plugin-eCuvoL1T.mjs.map
26588
+ //# sourceMappingURL=plugin-nmiHN7Ko.mjs.map