@workos/oagen 0.12.0 → 0.13.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.
- package/dist/cli/index.mjs +125 -14
- package/dist/cli/index.mjs.map +1 -1
- package/dist/compat/index.d.mts +3 -1
- package/dist/compat/index.d.mts.map +1 -1
- package/dist/compat/index.mjs +5 -2
- package/dist/compat/index.mjs.map +1 -1
- package/dist/{diff-CihuFSbJ.mjs → diff-C38px0NK.mjs} +2 -2
- package/dist/diff-C38px0NK.mjs.map +1 -0
- package/dist/{differ-BTx-rDgJ.d.mts → differ-fdF9qogA.d.mts} +8 -1
- package/dist/differ-fdF9qogA.d.mts.map +1 -0
- package/dist/index.d.mts +37 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{overlay-DTaApIqN.mjs → overlay-xp7F8_dW.mjs} +84 -1
- package/dist/overlay-xp7F8_dW.mjs.map +1 -0
- package/dist/scripts/smoke/baseline.mjs +1 -1
- package/dist/scripts/smoke/baseline.mjs.map +1 -1
- package/dist/smoke.d.mts +24 -0
- package/dist/smoke.d.mts.map +1 -1
- package/dist/smoke.mjs +1 -1
- package/dist/smoke.mjs.map +1 -1
- package/dist/verify/index.d.mts +1 -1
- package/dist/verify/index.mjs +2 -2
- package/docs/advanced/index.md +1 -0
- package/docs/advanced/transform-spec.md +124 -0
- package/docs/cli.md +13 -12
- package/docs/core/compatibility-policy.md +26 -0
- package/docs/core/compatibility.md +6 -0
- package/docs/index.md +1 -0
- package/package.json +1 -1
- package/dist/diff-CihuFSbJ.mjs.map +0 -1
- package/dist/differ-BTx-rDgJ.d.mts.map +0 -1
- package/dist/overlay-DTaApIqN.mjs.map +0 -1
package/dist/verify/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Bt as CompatPolicyHints, _t as ApiSurface, t as ApiSpec, wt as Violation } from "../types-BL762XqW.mjs";
|
|
2
|
-
import { t as CompatDiffResult } from "../differ-
|
|
2
|
+
import { t as CompatDiffResult } from "../differ-fdF9qogA.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/verify/types.d.ts
|
|
5
5
|
interface VerifyDiagnostics {
|
package/dist/verify/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { m as CommandError } from "../tree-sitter-DcUvm0KA.mjs";
|
|
2
|
-
import { a as MANIFEST_FILENAME, i as generate, n as getEmitter, t as diffSpecs } from "../diff-
|
|
3
|
-
import { A as getDefaultPolicy, T as apiSurfaceToSnapshot, c as filterSurface, f as specDerivedMethodPaths, i as getExtractor, j as mergePolicy, l as specDerivedEnumValues, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, t as buildOverlayLookup, u as specDerivedFieldPaths } from "../overlay-
|
|
2
|
+
import { a as MANIFEST_FILENAME, i as generate, n as getEmitter, t as diffSpecs } from "../diff-C38px0NK.mjs";
|
|
3
|
+
import { A as getDefaultPolicy, T as apiSurfaceToSnapshot, c as filterSurface, f as specDerivedMethodPaths, i as getExtractor, j as mergePolicy, l as specDerivedEnumValues, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, t as buildOverlayLookup, u as specDerivedFieldPaths } from "../overlay-xp7F8_dW.mjs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
package/docs/advanced/index.md
CHANGED
|
@@ -8,4 +8,5 @@ Use this section when you need to preserve an existing SDK surface, verify behav
|
|
|
8
8
|
- [Extractor Contract](../architecture/extractor-contract.md)
|
|
9
9
|
- [Non-Additive Changes](../architecture/non-additive-changes.md)
|
|
10
10
|
- [`@oagen-ignore` — Preserving Hand-Written Code](./oagen-ignore.md)
|
|
11
|
+
- [`transformSpec` — Pre-IR Spec Overlay](./transform-spec.md)
|
|
11
12
|
- [CLI Reference](../cli.md)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# `transformSpec` — Pre-IR Spec Overlay
|
|
2
|
+
|
|
3
|
+
`transformSpec` is a function on `OagenConfig` (and `ParseOptions`) that runs once on the bundled OpenAPI document, after `$ref` bundling and before any IR extraction. Use it to patch around upstream spec quirks that would otherwise emit a breaking SDK change.
|
|
4
|
+
|
|
5
|
+
## Signature
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import type { OagenConfig, OpenApiDocument } from "@workos/oagen";
|
|
9
|
+
|
|
10
|
+
const config: OagenConfig = {
|
|
11
|
+
// ...
|
|
12
|
+
transformSpec(spec: OpenApiDocument): OpenApiDocument {
|
|
13
|
+
// mutate `spec` in place and return it, or return a new object
|
|
14
|
+
return spec;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`OpenApiDocument` is `Record<string, unknown>` — intentionally loose so the transform can reach extension keys without fighting types. Narrow with `as` at use sites.
|
|
20
|
+
|
|
21
|
+
## When to use it
|
|
22
|
+
|
|
23
|
+
`transformSpec` is an emergency lever, not a routine knob. Reach for it when:
|
|
24
|
+
|
|
25
|
+
1. **The upstream spec can't be changed in time.** The right fix is almost always to amend the spec author's PR. `transformSpec` is for when you can't, and the SDK ships tomorrow.
|
|
26
|
+
2. **The change is genuinely additive at the schema level but the spec authoring expressed it as a fork.** The canonical example is below.
|
|
27
|
+
3. **You need a one-shot adapter** for an OpenAPI extension, vendor extension, or non-conforming construct that oagen doesn't natively model.
|
|
28
|
+
|
|
29
|
+
If you find yourself reaching for `transformSpec` repeatedly for the same pattern, that's a signal the underlying issue belongs upstream — either in the spec authoring style or as a feature request to oagen.
|
|
30
|
+
|
|
31
|
+
## Canonical example: schema-fork rescue
|
|
32
|
+
|
|
33
|
+
The pattern: an upstream PR forks a new schema (`FooWithBar`) instead of adding a field to the existing one (`Foo`), and re-points a path's response `$ref` at the fork. Because the schema name is part of the SDK's public surface in typed languages, this changes the return type of every method that uses it — a breaking change in Go, Kotlin, .NET, etc.
|
|
34
|
+
|
|
35
|
+
oagen's compat differ flags this pattern via the `remediation` hint on classified changes (see `docs/core/compatibility-policy.md`). The hint nudges spec authors to fix it upstream. When you can't wait for that fix, `transformSpec` un-forks the schema at parse time:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// oagen.config.ts
|
|
39
|
+
import type { OagenConfig } from "@workos/oagen";
|
|
40
|
+
|
|
41
|
+
const config: OagenConfig = {
|
|
42
|
+
transformSpec(spec) {
|
|
43
|
+
const components = (
|
|
44
|
+
spec as {
|
|
45
|
+
components?: {
|
|
46
|
+
schemas?: Record<
|
|
47
|
+
string,
|
|
48
|
+
{ properties?: Record<string, unknown>; required?: string[] }
|
|
49
|
+
>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
).components;
|
|
53
|
+
const paths = (
|
|
54
|
+
spec as {
|
|
55
|
+
paths?: Record<
|
|
56
|
+
string,
|
|
57
|
+
Record<
|
|
58
|
+
string,
|
|
59
|
+
{
|
|
60
|
+
responses?: Record<
|
|
61
|
+
string,
|
|
62
|
+
{ content?: Record<string, { schema?: { $ref?: string } }> }
|
|
63
|
+
>;
|
|
64
|
+
}
|
|
65
|
+
>
|
|
66
|
+
>;
|
|
67
|
+
}
|
|
68
|
+
).paths;
|
|
69
|
+
|
|
70
|
+
// 1. Rewrite the path response refs back to the original schema.
|
|
71
|
+
const forkedRef = "#/components/schemas/FooWithBarList";
|
|
72
|
+
const originalRef = "#/components/schemas/FooList";
|
|
73
|
+
for (const pathItem of Object.values(paths ?? {})) {
|
|
74
|
+
for (const op of Object.values(pathItem)) {
|
|
75
|
+
const ref =
|
|
76
|
+
op?.responses?.["200"]?.content?.["application/json"]?.schema?.$ref;
|
|
77
|
+
if (
|
|
78
|
+
ref === forkedRef &&
|
|
79
|
+
op.responses?.["200"]?.content?.["application/json"]?.schema
|
|
80
|
+
) {
|
|
81
|
+
op.responses["200"].content["application/json"].schema!.$ref =
|
|
82
|
+
originalRef;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 2. Add the forked schema's new fields to the original schema additively.
|
|
88
|
+
const foo = components?.schemas?.Foo;
|
|
89
|
+
if (foo?.properties) {
|
|
90
|
+
foo.properties.bar = {
|
|
91
|
+
$ref: "#/components/schemas/Bar",
|
|
92
|
+
} as unknown as Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 3. Drop the now-orphaned forked schemas so they don't bloat the IR.
|
|
96
|
+
if (components?.schemas) {
|
|
97
|
+
delete components.schemas.FooWithBar;
|
|
98
|
+
delete components.schemas.FooWithBarList;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return spec;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default config;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The result: typed SDKs see no return-type change. Existing callers compile against the same struct/class, and the new `bar` field is available as an additive property.
|
|
109
|
+
|
|
110
|
+
## When the hook runs
|
|
111
|
+
|
|
112
|
+
- **After:** OpenAPI doc load, `$ref` bundling (`@redocly/openapi-core`), and OpenAPI version validation.
|
|
113
|
+
- **Before:** schema extraction, operation extraction, inline-model normalization, and IR validation.
|
|
114
|
+
|
|
115
|
+
This means anything you do to `spec.components.schemas`, `spec.paths`, or `spec.servers` flows naturally into the IR. Internal `$ref`s are still strings at this point — they aren't dereferenced until extraction.
|
|
116
|
+
|
|
117
|
+
If you need to inspect the resulting IR instead, post-process the `ApiSpec` returned by `parseSpec`. `transformSpec` is intentionally narrow.
|
|
118
|
+
|
|
119
|
+
## Caveats
|
|
120
|
+
|
|
121
|
+
- **Not for backward-compat hacks.** If a server-side change is a real shape change (not a fork), don't paper over it here — fix the SDK instead.
|
|
122
|
+
- **Idempotence is your responsibility.** The hook may run more than once per CLI session (e.g. `verify` parses the spec twice when `--old-spec` is provided). Write transforms that no-op cleanly when applied to an already-patched document.
|
|
123
|
+
- **No type narrowing.** `OpenApiDocument` is intentionally `Record<string, unknown>`. Cast aggressively at use sites; the alternative would be importing OpenAPI's full type tree, which we deliberately avoid in oagen's public surface.
|
|
124
|
+
- **Surfaces in every command.** `parse`, `generate`, `diff`, `resolve`, `verify`, and `compat-extract` all honor `transformSpec` from the config file.
|
package/docs/cli.md
CHANGED
|
@@ -301,15 +301,16 @@ export default config;
|
|
|
301
301
|
|
|
302
302
|
### Options
|
|
303
303
|
|
|
304
|
-
| Key | Type
|
|
305
|
-
| ---------------------- |
|
|
306
|
-
| `emitters` | `Emitter[]`
|
|
307
|
-
| `extractors` | `Extractor[]`
|
|
308
|
-
| `emitterProject` | `string`
|
|
309
|
-
| `smokeRunners` | `Record<string, string>`
|
|
310
|
-
| `operationIdTransform` | `(id: string) => string`
|
|
311
|
-
| `schemaNameTransform` | `(name: string) => string`
|
|
312
|
-
| `
|
|
313
|
-
| `
|
|
314
|
-
| `
|
|
315
|
-
| `
|
|
304
|
+
| Key | Type | Description |
|
|
305
|
+
| ---------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
306
|
+
| `emitters` | `Emitter[]` | Language emitters to register. Each emitter implements the `Emitter` interface and generates SDK files for one target language. |
|
|
307
|
+
| `extractors` | `Extractor[]` | API surface extractors to register. Each extractor parses a live SDK and produces an `ApiSurface` JSON used for compat verification. |
|
|
308
|
+
| `emitterProject` | `string` | Path to the emitter project directory. Used by skills to scaffold new emitters, tests, and smoke runners in the correct location. |
|
|
309
|
+
| `smokeRunners` | `Record<string, string>` | Map from language key to custom smoke runner script path. Overrides the built-in `sdk-test.ts` for `oagen verify`. Can also be set per-invocation with `--smoke-runner`. |
|
|
310
|
+
| `operationIdTransform` | `(id: string) => string` | Custom transform for operation IDs. Receives the raw `operationId` from the spec; return the desired operation name. No additional casing conversion is applied. When omitted, `operationId` values are converted to `camelCase`. |
|
|
311
|
+
| `schemaNameTransform` | `(name: string) => string` | Custom transform for schema (model/enum) names. Applied after the built-in `cleanSchemaName` normalization. Receives the cleaned PascalCase name; return the desired name. |
|
|
312
|
+
| `transformSpec` | `(spec: OpenApiDocument) => OpenApiDocument` | Pre-IR overlay applied to the bundled OpenAPI document before any IR extraction. Use to patch around upstream spec quirks that would otherwise emit a breaking SDK change. See [`transformSpec` — Pre-IR Spec Overlay](advanced/transform-spec.md). |
|
|
313
|
+
| `docUrl` | `string` | Base URL for documentation links. When set, relative markdown paths in descriptions (e.g. `[User](/reference/authkit/user)`) are expanded to full URLs (e.g. `[User](https://workos.com/docs/reference/authkit/user)`). |
|
|
314
|
+
| `operationHints` | `Record<string, OperationHint>` | Per-operation overrides keyed by `"METHOD /path"`. Override derived method names, remount to a different service, or split union-body operations into typed wrappers. See [Operation Resolution](architecture/ir-types.md#operation-resolution). |
|
|
315
|
+
| `mountRules` | `Record<string, string>` | Service-level remounting. Maps IR service name to target service/namespace (PascalCase). All operations in the source service are mounted on the target unless overridden per-operation in `operationHints`. |
|
|
316
|
+
| `modelHints` | `Record<string, string>` | Pin specific models to a specific IR service for placement. Maps IR model name (post-`cleanSchemaName`/`schemaNameTransform`) → IR service name (PascalCase). Overrides the default "first service to reference the model wins" assignment. Both names must exist in the parsed spec; unknown names throw at generation time. |
|
|
@@ -168,3 +168,29 @@ The `failOn` level determines which unapproved changes cause `oagen verify` to f
|
|
|
168
168
|
| `none` | Never fails for compat |
|
|
169
169
|
| `breaking` | Unapproved breaking changes |
|
|
170
170
|
| `soft-risk` | Unapproved breaking OR soft-risk changes |
|
|
171
|
+
|
|
172
|
+
## Remediation Hints
|
|
173
|
+
|
|
174
|
+
Some changes have a recognized upstream root cause that the spec author can fix. When the differ detects one, it attaches a `remediation` string to the classified change. The hint surfaces in both the machine-readable report (`CompatReportChange.remediation`) and the human-readable summary (printed as `hint: ...` underneath the change).
|
|
175
|
+
|
|
176
|
+
The classification, severity, and fail-threshold semantics are unchanged — `remediation` is purely advisory, telling the spec author how to make the change non-breaking.
|
|
177
|
+
|
|
178
|
+
### Detected patterns
|
|
179
|
+
|
|
180
|
+
| Pattern | Trigger | Hint |
|
|
181
|
+
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
182
|
+
| **Schema fork** | A `return_type_changed` or `field_type_changed` where the new type was newly added in the candidate snapshot _and_ its field set is a non-strict superset of the prior type's field set. | "Schema `NewType` looks like `OldType` with additional fields. Consider adding the new fields to `OldType` instead of forking a new schema." Forking forces a breaking type-name change in typed SDKs; extending the existing schema is additive. |
|
|
183
|
+
|
|
184
|
+
### Example: schema fork
|
|
185
|
+
|
|
186
|
+
A path's response `$ref` is redirected from `MembershipBaseList` to a brand-new `MembershipBaseWithUserList` whose only difference is an added `user` field. The differ reports a `return_type_changed` (breaking) for the affected method, plus the remediation hint pointing the spec author at the additive alternative.
|
|
187
|
+
|
|
188
|
+
If the upstream fix can't land in time, `transformSpec` is the local escape hatch — see [`transformSpec` — Pre-IR Spec Overlay](../advanced/transform-spec.md).
|
|
189
|
+
|
|
190
|
+
### Adding new patterns
|
|
191
|
+
|
|
192
|
+
Detection rules live in `detectForkedSchemas` (`src/compat/differ.ts`). To add a rule:
|
|
193
|
+
|
|
194
|
+
1. Add a heuristic that examines the existing `changes` list plus the baseline/candidate snapshots.
|
|
195
|
+
2. When the pattern matches, set `change.remediation` on the relevant existing change. Don't introduce a new change for the same pattern — pile remediation onto what's already classified.
|
|
196
|
+
3. Keep the rule scoped: it should match a recognizable upstream antipattern, not generic shape changes.
|
|
@@ -74,6 +74,12 @@ Each classified change includes a provenance bucket explaining where the drift c
|
|
|
74
74
|
- `normalization_change` — Naming normalization produced different output
|
|
75
75
|
- `unknown` — Source of drift not determined
|
|
76
76
|
|
|
77
|
+
### Remediation Hints
|
|
78
|
+
|
|
79
|
+
When a change matches a recognized upstream antipattern (e.g. forking a new schema instead of extending the existing one), the differ attaches a `remediation` string to the classified change. The hint surfaces in both the machine-readable report and the human-readable summary, telling the spec author how to make the change non-breaking. The classification, severity, and fail-threshold semantics are unchanged.
|
|
80
|
+
|
|
81
|
+
See [Compatibility Policy → Remediation Hints](compatibility-policy.md#remediation-hints) for the list of detected patterns and how to add new ones.
|
|
82
|
+
|
|
77
83
|
## Further Reading
|
|
78
84
|
|
|
79
85
|
- [CLI Reference](../cli.md) — `compat-extract`, `compat-diff`, `compat-summary` argument tables
|
package/docs/index.md
CHANGED
|
@@ -21,6 +21,7 @@ Read these when you need migration or verification workflows beyond basic genera
|
|
|
21
21
|
- [Workflows](architecture/workflows.md)
|
|
22
22
|
- [Extractor Contract](architecture/extractor-contract.md)
|
|
23
23
|
- [Non-Additive Changes](architecture/non-additive-changes.md)
|
|
24
|
+
- [`transformSpec` — Pre-IR Spec Overlay](advanced/transform-spec.md)
|
|
24
25
|
|
|
25
26
|
## Agent Task Guides
|
|
26
27
|
|