@workos/oagen-emitters 0.12.0 → 0.12.1

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 +1 @@
1
- {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;cA0Ba,oBAAA,EAAsB,IAAA,CAAK,WAAA"}
1
+ {"version":3,"file":"plugin.d.mts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;cA0Ba,oBAAA,EAAsB,IAAI,CAAC,WAAA"}
package/dist/plugin.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as workosEmittersPlugin } from "./plugin-C408Wh-o.mjs";
1
+ import { t as workosEmittersPlugin } from "./plugin-CmfzawTp.mjs";
2
2
  export { workosEmittersPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workos/oagen-emitters",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "WorkOS' oagen emitters",
5
5
  "license": "MIT",
6
6
  "author": "WorkOS",
@@ -38,22 +38,22 @@
38
38
  "prepare": "husky"
39
39
  },
40
40
  "devDependencies": {
41
- "@commitlint/cli": "^20.5.3",
42
- "@commitlint/config-conventional": "^20.5.3",
43
- "@types/node": "^25.6.0",
41
+ "@commitlint/cli": "^21.0.1",
42
+ "@commitlint/config-conventional": "^21.0.1",
43
+ "@types/node": "^25.7.0",
44
44
  "husky": "^9.1.7",
45
- "oxfmt": "^0.48.0",
46
- "oxlint": "^1.63.0",
45
+ "oxfmt": "^0.49.0",
46
+ "oxlint": "^1.64.0",
47
47
  "prettier": "^3.8.3",
48
- "tsdown": "^0.21.10",
48
+ "tsdown": "^0.22.0",
49
49
  "tsx": "^4.21.0",
50
50
  "typescript": "^6.0.3",
51
- "vitest": "^4.1.5"
51
+ "vitest": "^4.1.6"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=24.10.0"
55
55
  },
56
56
  "dependencies": {
57
- "@workos/oagen": "^0.18.1"
57
+ "@workos/oagen": "^0.18.2"
58
58
  }
59
59
  }
@@ -41,7 +41,12 @@ export function generateModelFixture(
41
41
 
42
42
  for (const field of model.fields) {
43
43
  if (!field.required) continue;
44
- result[field.name] = exampleFor(field.type, modelMap, enumMap, visiting, field.name);
44
+ // Prefer the spec `example` value when it is shape-compatible with the
45
+ // declared type. Falls back to the placeholder generator when no example
46
+ // is provided or when the example would not deserialize cleanly.
47
+ const fromExample = exampleFromSpec(field.example, field.type, enumMap);
48
+ result[field.name] =
49
+ fromExample !== undefined ? fromExample : exampleFor(field.type, modelMap, enumMap, visiting, field.name);
45
50
  }
46
51
 
47
52
  visiting.delete(model.name);
@@ -108,3 +113,84 @@ export function exampleFor(
108
113
  }
109
114
  }
110
115
  }
116
+
117
+ /**
118
+ * Resolve a spec-provided `example` against a TypeRef and return the value to
119
+ * embed in the fixture, or `undefined` when the example cannot be used safely.
120
+ *
121
+ * "Safely" means the value would round-trip through serde to the generated
122
+ * Rust type. We deliberately only accept primitives, enum string/number
123
+ * values, and homogenous arrays of those; nested object examples (which the
124
+ * spec sometimes supplies as illustrative metadata blobs) are skipped because
125
+ * they rarely match the strict struct shape Rust expects.
126
+ */
127
+ export function exampleFromSpec(example: unknown, type: TypeRef, enumMap: Map<string, Enum>): unknown {
128
+ if (example === undefined) return undefined;
129
+ // Spec authors sometimes use `null` as a sentinel; let placeholder gen
130
+ // handle nullable types so we don't emit `null` for required fields.
131
+ if (example === null) return undefined;
132
+ return matchExampleToType(example, type, enumMap);
133
+ }
134
+
135
+ function matchExampleToType(value: unknown, type: TypeRef, enumMap: Map<string, Enum>): unknown {
136
+ switch (type.kind) {
137
+ case 'primitive':
138
+ return matchPrimitive(value, type.type);
139
+ case 'literal':
140
+ return value === type.value ? value : undefined;
141
+ case 'enum': {
142
+ const e = enumMap.get(type.name);
143
+ if (!e) return undefined;
144
+ const ok = e.values.some((v) => v.value === value);
145
+ return ok ? value : undefined;
146
+ }
147
+ case 'array': {
148
+ if (!Array.isArray(value)) return undefined;
149
+ const out: unknown[] = [];
150
+ for (const item of value) {
151
+ const matched = matchExampleToType(item, type.items, enumMap);
152
+ if (matched === undefined) return undefined;
153
+ out.push(matched);
154
+ }
155
+ // Empty arrays are valid but unhelpful in fixtures — fall back so the
156
+ // placeholder generator can produce a one-element example.
157
+ if (out.length === 0) return undefined;
158
+ return out;
159
+ }
160
+ case 'nullable':
161
+ return matchExampleToType(value, type.inner, enumMap);
162
+ case 'map':
163
+ // Map examples are usually free-form metadata blobs that match
164
+ // `HashMap<String, _>`; only accept plain objects with string-keyed values.
165
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;
166
+ return value;
167
+ case 'union': {
168
+ for (const variant of type.variants) {
169
+ const matched = matchExampleToType(value, variant, enumMap);
170
+ if (matched !== undefined) return matched;
171
+ }
172
+ return undefined;
173
+ }
174
+ case 'model':
175
+ // Model-shaped examples are too risky to copy verbatim: they rarely
176
+ // supply every required field and may use wire names that don't align
177
+ // with the generated struct. Let the recursive generator handle them.
178
+ return undefined;
179
+ }
180
+ }
181
+
182
+ function matchPrimitive(value: unknown, primitive: 'string' | 'integer' | 'number' | 'boolean' | 'unknown'): unknown {
183
+ switch (primitive) {
184
+ case 'string':
185
+ return typeof value === 'string' ? value : undefined;
186
+ case 'integer':
187
+ return typeof value === 'number' && Number.isInteger(value) ? value : undefined;
188
+ case 'number':
189
+ return typeof value === 'number' ? value : undefined;
190
+ case 'boolean':
191
+ return typeof value === 'boolean' ? value : undefined;
192
+ case 'unknown':
193
+ // `unknown` deserialises to `serde_json::Value`, so any JSON value works.
194
+ return value;
195
+ }
196
+ }
@@ -105,8 +105,13 @@ function resolveFieldNames(fields: Field[]): string[] {
105
105
 
106
106
  function renderField(field: Field, rustField: string, modelName: string, registry: UnionRegistry): string {
107
107
  const lines: string[] = [];
108
- if (field.description) {
109
- for (const c of docComment(field.description)) lines.push(` ${c}`);
108
+ const hasDescription = !!field.description;
109
+ if (hasDescription) {
110
+ for (const c of docComment(field.description!)) lines.push(` ${c}`);
111
+ }
112
+ if (field.default != null) {
113
+ if (hasDescription) lines.push(' ///');
114
+ lines.push(` /// Defaults to \`${formatDefault(field.default)}\`.`);
110
115
  }
111
116
 
112
117
  const rename = rustField !== field.name ? field.name : null;
@@ -148,3 +153,13 @@ function docComment(text: string): string[] {
148
153
  .filter((l) => l.length > 0)
149
154
  .map((l) => `/// ${l}`);
150
155
  }
156
+
157
+ /**
158
+ * Render a spec-level default value for inclusion in a doc comment. Strings
159
+ * render bare (e.g. `desc`) so they nest naturally inside the surrounding
160
+ * backticks; numbers/booleans use JSON encoding.
161
+ */
162
+ function formatDefault(value: unknown): string {
163
+ if (typeof value === 'string') return value;
164
+ return JSON.stringify(value);
165
+ }