Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ed7ba69
fix(typescript): match SDK request/auth shape in dynamic snippets
dvdaruri-art Sep 15, 2026
f741603
chore(typescript): address review nits on dynamic snippet auth/flatten
dvdaruri-art Sep 15, 2026
8e66d5e
fix(typescript): drop path params that collide with flattened body fi…
dvdaruri-art Sep 15, 2026
21d16f9
test(cli): update auth wrapperProperty snapshots
dvdaruri-art Sep 15, 2026
b881ed0
test(cli): add TypeScript flattening fixture snapshots
dvdaruri-art Sep 15, 2026
7ad7a45
test(ts-sdk): add flattening seed fixture output
dvdaruri-art Sep 15, 2026
8214279
test(typescript): cover real fixture dynamic snippets
dvdaruri-art Sep 15, 2026
f759dde
refactor(typescript): centralize auth wrapper property handling
dvdaruri-art Sep 15, 2026
a7b956f
test: fix fixture generators.yml branch names
dvdaruri-art Sep 15, 2026
c36a8d5
fix(test): add response examples to TypeScript fixture
dvdaruri-art Sep 15, 2026
418686d
Merge origin/main into devin/1789483869-ts-dynamic-snippets-flatten-b…
dvdaruri-art Sep 15, 2026
8e690b9
test(cli): refresh IR snapshots after main merge
dvdaruri-art Sep 15, 2026
580895c
test: align ts-flatten-request-any-auth example ids for wire tests
dvdaruri-art Sep 15, 2026
e0a6291
chore(typescript): point dynamic-ir-sdk compat TODO at IR 67.27.0
dvdaruri-art Sep 16, 2026
1b0e80a
chore: rerun CI
dvdaruri-art Sep 16, 2026
e9d502f
fix(typescript): use camelCase scheme key for auth wrapper regardless…
dvdaruri-art Sep 16, 2026
a5dbc35
fix(typescript): match SDK oauth wrapper key casing in dynamic snippets
dvdaruri-art Sep 16, 2026
f829feb
Merge branch 'main' into devin/1789483869-ts-dynamic-snippets-flatten…
dvdaruri-art Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions generators/typescript-v2/ast/src/ast/TypeLiteral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ export class TypeLiteral extends AstNode {
});
}

public getObjectFields(): ObjectField[] | undefined {
return this.internalType.type === "object" ? this.internalType.fields : undefined;
}

public static record({ entries }: { entries: RecordEntry[] }): TypeLiteral {
return new this({
type: "record",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ const STRING_TYPE_REFERENCE: FernIr.dynamic.TypeReference = {
value: "STRING"
};

type AuthFields =
| FernIr.dynamic.BasicAuth
| FernIr.dynamic.BearerAuth
| FernIr.dynamic.HeaderAuth
| FernIr.dynamic.OAuth
| FernIr.dynamic.InferredAuth;

type AuthWithWrapperPropertyField = AuthFields & {
wrapperProperty?: FernIr.dynamic.Name | null;
};

// TODO: remove once @fern-api/dynamic-ir-sdk >= 67.27.0 ships wrapperProperty on Auth
function hasWrapperPropertyField(auth: AuthFields): auth is AuthWithWrapperPropertyField {
return "wrapperProperty" in auth;
}

function getAuthWrapperProperty(auth: AuthFields): FernIr.dynamic.Name | undefined {
return hasWrapperPropertyField(auth) ? (auth.wrapperProperty ?? undefined) : undefined;
}

export class EndpointSnippetGenerator {
private context: DynamicSnippetsGeneratorContext;

Expand Down Expand Up @@ -275,16 +295,19 @@ export class EndpointSnippetGenerator {
auth: FernIr.dynamic.BasicAuth;
values: FernIr.dynamic.BasicAuthValues;
}): ts.ObjectField[] {
return [
{
name: this.context.getPropertyName(auth.username),
value: ts.TypeLiteral.string(values.username)
},
{
name: this.context.getPropertyName(auth.password),
value: ts.TypeLiteral.string(values.password)
}
];
return this.wrapAuthFields({
auth,
fields: [
{
name: this.context.getPropertyName(auth.username),
value: ts.TypeLiteral.string(values.username)
},
{
name: this.context.getPropertyName(auth.password),
value: ts.TypeLiteral.string(values.password)
}
]
});
}

private getConstructorBearerAuthArgs({
Expand All @@ -294,12 +317,15 @@ export class EndpointSnippetGenerator {
auth: FernIr.dynamic.BearerAuth;
values: FernIr.dynamic.BearerAuthValues;
}): ts.ObjectField[] {
return [
{
name: this.context.getPropertyName(auth.token),
value: ts.TypeLiteral.string(values.token)
}
];
return this.wrapAuthFields({
auth,
fields: [
{
name: this.context.getPropertyName(auth.token),
value: ts.TypeLiteral.string(values.token)
}
]
});
}

private getConstructorHeaderAuthArgs({
Expand All @@ -309,15 +335,18 @@ export class EndpointSnippetGenerator {
auth: FernIr.dynamic.HeaderAuth;
values: FernIr.dynamic.HeaderAuthValues;
}): ts.ObjectField[] {
return [
{
name: this.context.getPropertyName(auth.header.name.name),
value: this.context.dynamicTypeLiteralMapper.convert({
typeReference: auth.header.typeReference,
value: values.value
})
}
];
return this.wrapAuthFields({
auth,
fields: [
{
name: this.context.getPropertyName(auth.header.name.name),
value: this.context.dynamicTypeLiteralMapper.convert({
typeReference: auth.header.typeReference,
value: values.value
})
}
]
});
}

private getConstructorOAuthArgs({
Expand All @@ -327,14 +356,36 @@ export class EndpointSnippetGenerator {
auth: FernIr.dynamic.OAuth;
values: FernIr.dynamic.OAuthValues;
}): ts.ObjectField[] {
return this.wrapAuthFields({
auth,
fields: [
{
name: this.context.getPropertyName(auth.clientId),
value: ts.TypeLiteral.string(values.clientId)
},
{
name: this.context.getPropertyName(auth.clientSecret),
value: ts.TypeLiteral.string(values.clientSecret)
}
]
});
}

private getAuthWrapperPropertyName(wrapperProperty: FernIr.dynamic.Name): string {
// mirrors @fern-typescript/commons toCamelCase, which the SDK uses for auth wrapper option names
return wrapperProperty.camelCase.unsafeName === "oAuth" ? "oauth" : wrapperProperty.camelCase.unsafeName;
}

private wrapAuthFields({ auth, fields }: { auth: AuthFields; fields: ts.ObjectField[] }): ts.ObjectField[] {
const wrapperProperty = getAuthWrapperProperty(auth);
if (wrapperProperty == null) {
return fields;
}
return [
{
name: this.context.getPropertyName(auth.clientId),
value: ts.TypeLiteral.string(values.clientId)
},
{
name: this.context.getPropertyName(auth.clientSecret),
value: ts.TypeLiteral.string(values.clientSecret)
// SDK auth wrapper option is always camelCase(scheme key), regardless of serde/casing config
name: this.getAuthWrapperPropertyName(wrapperProperty),
value: ts.TypeLiteral.object({ fields })
}
];
}
Expand Down Expand Up @@ -626,8 +677,15 @@ export class EndpointSnippetGenerator {
: [];
this.context.errors.unscope();

const bodyFieldNames = new Set(requestBodyFields.map((field) => field.name));

return ts.TypeLiteral.object({
fields: [...pathParameterFields, ...queryParameterFields, ...headerFields, ...requestBodyFields]
fields: [
...pathParameterFields.filter((field) => !bodyFieldNames.has(field.name)),
...queryParameterFields,
...headerFields,
...requestBodyFields
]
});
}

Expand All @@ -644,7 +702,26 @@ export class EndpointSnippetGenerator {
case "properties":
return this.getInlinedRequestBodyPropertyObjectFields({ parameters: body.value, value });
case "referenced": {
const field = this.getReferencedRequestBodyPropertyObjectField({ body, value });
let literal: ts.TypeLiteral | undefined;
if (
this.context.customConfig?.flattenRequestParameters === true &&
body.bodyType.type === "typeReference" &&
body.bodyType.value.type === "named"
) {
const named = this.context.resolveNamedType({ typeId: body.bodyType.value.value });
if (named?.type === "object") {
literal = this.context.dynamicTypeLiteralMapper.convert({
typeReference: body.bodyType.value,
value,
convertOpts: { isForRequest: true }
});
const fields = literal.getObjectFields();
if (fields != null) {
return fields;
}
}
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
const field = this.getReferencedRequestBodyPropertyObjectField({ body, value, literal });
// an example that omits an optional request body has no value to write, so the
// property is dropped rather than passed explicitly as undefined
return ts.TypeLiteral.isNop(field.value) ? [] : [field];
Expand All @@ -669,14 +746,16 @@ export class EndpointSnippetGenerator {

private getReferencedRequestBodyPropertyObjectField({
body,
value
value,
literal
}: {
body: FernIr.dynamic.ReferencedRequestBody;
value: unknown;
literal?: ts.TypeLiteral;
}): ts.ObjectField {
return {
name: this.context.getPropertyName(body.bodyKey),
value: this.getReferencedRequestBodyPropertyTypeLiteral({ body: body.bodyType, value })
value: literal ?? this.getReferencedRequestBodyPropertyTypeLiteral({ body: body.bodyType, value })
};
}

Expand Down
Loading
Loading