Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
import { z } from "zod";
import { CustomReadmeSectionSchema } from "./CustomReadmeSectionSchema.js";

const JS_IDENTIFIER_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
const jsIdentifier = z.string().refine((val) => JS_IDENTIFIER_REGEX.test(val), {
message: "Must be a valid JavaScript identifier (e.g., 'acme', 'MySDK')"
});

export const NamingObjectSchema = z.strictObject({
namespace: z.optional(jsIdentifier),
client: z.optional(z.string()),
error: z.optional(z.string()),
timeoutError: z.optional(z.string()),
environment: z.optional(z.string()),
environmentUrls: z.optional(z.string()),
version: z.optional(z.string())
});

export type NamingObjectSchema = z.infer<typeof NamingObjectSchema>;

// Accepts either a string shorthand (e.g., `naming: acme`) or a full object.
// The string form is equivalent to `naming: { namespace: "acme" }`.
export const NamingConfigSchema = z.union([jsIdentifier, NamingObjectSchema]);

export type NamingConfigSchema = z.infer<typeof NamingConfigSchema>;

// The full set of configuration options supported by the TypeScript SDK generator.
export const TypescriptCustomConfigSchema = z.strictObject({
neverThrowErrors: z.optional(z.boolean()),
Expand Down Expand Up @@ -50,6 +73,9 @@ export const TypescriptCustomConfigSchema = z.strictObject({
enableInlineTypes: z.optional(z.boolean()),
inlineFileProperties: z.optional(z.boolean()),
inlinePathParameters: z.optional(z.boolean()),
naming: z.optional(NamingConfigSchema),
// @deprecated Use naming.namespace instead
// namespaceExport is kept for backwards compatibility
namespaceExport: z.optional(z.string()),
noSerdeLayer: z.optional(z.boolean()),
private: z.optional(z.boolean()),
Expand Down
6 changes: 5 additions & 1 deletion generators/typescript-v2/ast/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export * from "./ast/core/index.js";
export { TypescriptCustomConfigSchema } from "./custom-config/TypescriptCustomConfigSchema.js";
export {
NamingConfigSchema,
NamingObjectSchema,
TypescriptCustomConfigSchema
} from "./custom-config/TypescriptCustomConfigSchema.js";
export * as ts from "./typescript.js";
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,63 @@ import { camelCase, upperFirst } from "lodash-es";
export function getNamespaceExport({
organization,
workspaceName,
namespaceExport
namespaceExport,
naming
}: {
organization: string;
workspaceName: string;
namespaceExport?: string;
naming?: string | { namespace?: string };
}): string {
return namespaceExport ?? `${upperFirst(camelCase(organization))}${upperFirst(camelCase(workspaceName))}`;
const namingNamespace = typeof naming === "string" ? naming : naming?.namespace;
return (
namingNamespace ??
namespaceExport ??
`${upperFirst(camelCase(organization))}${upperFirst(camelCase(workspaceName))}`
);
}

export interface ResolvedNaming {
namespace: string;
client: string;
error: string;
timeoutError: string;
environment: string;
environmentUrls: string;
version: string;
}

export function resolveNaming({
namespaceExport,
naming
}: {
namespaceExport: string;
naming?:
| string
| {
namespace?: string;
client?: string;
error?: string;
timeoutError?: string;
environment?: string;
environmentUrls?: string;
version?: string;
};
}): ResolvedNaming {
// Normalize string shorthand to object form
const namingObj = typeof naming === "string" ? {} : naming;
// When naming config is explicitly provided, PascalCase the namespace for deriving
// default suffix names (e.g., namespace: "xai" → XaiClient). When no naming config
// is provided, use namespaceExport as-is to preserve backwards compatibility
// (e.g., namespaceExport: "MySDK" → MySDKClient).
const suffixBase = naming != null ? upperFirst(camelCase(namespaceExport)) : namespaceExport;
return {
namespace: namespaceExport,
client: namingObj?.client ?? `${suffixBase}Client`,
error: namingObj?.error ?? `${suffixBase}Error`,
timeoutError: namingObj?.timeoutError ?? `${suffixBase}TimeoutError`,
environment: namingObj?.environment ?? `${suffixBase}Environment`,
environmentUrls: namingObj?.environmentUrls ?? `${suffixBase}EnvironmentUrls`,
version: namingObj?.version ?? `${suffixBase}Version`
};
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "./constructNpmPackage.js";
export { getNamespaceExport } from "./getNamespaceExport.js";
export { getNamespaceExport, type ResolvedNaming, resolveNaming } from "./getNamespaceExport.js";
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from "@fern-api/browser-compatible-base-generator";
import { FernIr } from "@fern-api/dynamic-ir-sdk";
import { TypescriptCustomConfigSchema, ts } from "@fern-api/typescript-ast";
import { constructNpmPackage, getNamespaceExport } from "@fern-api/typescript-browser-compatible-base";
import { constructNpmPackage, getNamespaceExport, resolveNaming } from "@fern-api/typescript-browser-compatible-base";

import { DynamicTypeLiteralMapper } from "./DynamicTypeLiteralMapper.js";
import { FilePropertyMapper } from "./FilePropertyMapper.js";
Expand All @@ -16,6 +16,8 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene
public filePropertyMapper: FilePropertyMapper;
public moduleName: string;
public namespaceExport: string;
private resolvedClientName: string;
private resolvedEnvironmentName: string;

constructor({
ir,
Expand All @@ -34,8 +36,15 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene
this.namespaceExport = getNamespaceExport({
organization: config.organization,
workspaceName: config.workspaceName,
namespaceExport: this.customConfig?.namespaceExport
namespaceExport: this.customConfig?.namespaceExport,
naming: this.customConfig?.naming
});
const resolved = resolveNaming({
namespaceExport: this.namespaceExport,
naming: this.customConfig?.naming
});
this.resolvedClientName = resolved.client;
this.resolvedEnvironmentName = resolved.environment;
}

public clone(): DynamicSnippetsGeneratorContext {
Expand All @@ -53,7 +62,7 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene
}

public getRootClientName(): string {
return `${this.namespaceExport}Client`;
return this.resolvedClientName;
}

public getPropertyName(name: FernIr.Name): string {
Expand Down Expand Up @@ -90,7 +99,7 @@ export class DynamicSnippetsGeneratorContext extends AbstractDynamicSnippetsGene

private getEnvironmentsTypeReference(name: FernIr.Name): ts.Reference {
return ts.reference({
name: `${this.namespaceExport}Environment`,
name: this.resolvedEnvironmentName,
importFrom: this.getModuleImport(),
memberName: this.getTypeName(name)
});
Expand Down
11 changes: 9 additions & 2 deletions generators/typescript/sdk/cli/src/SdkGeneratorCli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FernGeneratorExec } from "@fern-api/base-generator";
import { AbsoluteFilePath } from "@fern-api/fs-utils";
import { Logger } from "@fern-api/logger";
import { getNamespaceExport } from "@fern-api/typescript-base";
import { getNamespaceExport, resolveNaming } from "@fern-api/typescript-base";
import { FernIr } from "@fern-fern/ir-sdk";
import { AbstractGeneratorCli } from "@fern-typescript/abstract-generator-cli";
import {
Expand Down Expand Up @@ -42,6 +42,7 @@ export class SdkGeneratorCli extends AbstractGeneratorCli<SdkCustomConfig> {
isPackagePrivate: parsed?.private ?? false,
neverThrowErrors: parsed?.neverThrowErrors ?? false,
namespaceExport: parsed?.namespaceExport,
naming: parsed?.naming,
outputEsm: parsed?.outputEsm ?? false,
outputSrcOnly: parsed?.outputSrcOnly ?? false,
includeCredentialsOnCrossOriginRequests: parsed?.includeCredentialsOnCrossOriginRequests ?? false,
Expand Down Expand Up @@ -166,10 +167,16 @@ export class SdkGeneratorCli extends AbstractGeneratorCli<SdkCustomConfig> {
const namespaceExport = getNamespaceExport({
organization: config.organization,
workspaceName: config.workspaceName,
namespaceExport: customConfig.namespaceExport
namespaceExport: customConfig.namespaceExport,
naming: customConfig.naming
});
const resolvedNaming = resolveNaming({
namespaceExport,
naming: customConfig.naming
});
const sdkGenerator = new SdkGenerator({
namespaceExport,
naming: resolvedNaming,
intermediateRepresentation,
context: generatorContext,
npmPackage,
Expand Down
15 changes: 15 additions & 0 deletions generators/typescript/sdk/cli/src/custom-config/SdkCustomConfig.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
export interface NamingObjectConfig {
namespace?: string;
client?: string;
error?: string;
timeoutError?: string;
environment?: string;
environmentUrls?: string;
version?: string;
}

// Accepts either a string shorthand (e.g., "acme") or a full object.
// The string form is equivalent to { namespace: "acme" }.
export type NamingConfig = string | NamingObjectConfig;

// this is the parsed config shape. to view the allowed options for generators.yml,
// see SdkCustomConfigSchema.ts
export interface SdkCustomConfig {
useBrandedStringAliases: boolean;
isPackagePrivate: boolean;
neverThrowErrors: boolean;
namespaceExport: string | undefined;
naming: NamingConfig | undefined;
outputEsm: boolean;
outputSourceFiles: boolean;
outputSrcOnly: boolean;
Expand Down
24 changes: 22 additions & 2 deletions generators/typescript/sdk/generator/src/SdkGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,20 @@ interface WebhookVerificationEntry {
webhookNames: [FernIr.WebhookName, ...FernIr.WebhookName[]];
}

export interface ResolvedNaming {
namespace: string;
client: string;
error: string;
timeoutError: string;
environment: string;
environmentUrls: string;
version: string;
}

export declare namespace SdkGenerator {
export interface Init {
namespaceExport: string;
naming: ResolvedNaming;
intermediateRepresentation: FernIr.IntermediateRepresentation;
context: GeneratorContext;
npmPackage: NpmPackage | undefined;
Expand Down Expand Up @@ -163,6 +174,7 @@ export declare namespace SdkGenerator {

export class SdkGenerator {
private namespaceExport: string;
private naming: ResolvedNaming;
private context: GeneratorContext;
private intermediateRepresentation: FernIr.IntermediateRepresentation;
private rawConfig: FernGeneratorExec.GeneratorConfig;
Expand Down Expand Up @@ -248,6 +260,7 @@ export class SdkGenerator {

constructor({
namespaceExport,
naming,
intermediateRepresentation,
context,
npmPackage,
Expand All @@ -261,6 +274,7 @@ export class SdkGenerator {

this.context = context;
this.namespaceExport = namespaceExport;
this.naming = naming;
this.intermediateRepresentation = intermediateRepresentation;

// Auto-enable generateEndpointMetadata when ENDPOINT_SECURITY is set
Expand Down Expand Up @@ -320,6 +334,7 @@ export class SdkGenerator {
this.versionDeclarationReferencer = new VersionDeclarationReferencer({
containingDirectory: apiDirectory,
namespaceExport,
namingOverride: naming.version,
apiVersion: this.intermediateRepresentation.apiVersion,
relativePackagePath: this.relativePackagePath,
relativeTestPath: this.relativeTestPath
Expand All @@ -344,6 +359,7 @@ export class SdkGenerator {
this.sdkClientClassDeclarationReferencer = new SdkClientClassDeclarationReferencer({
containingDirectory: apiDirectory,
namespaceExport,
namingOverride: naming.client,
packageResolver: this.packageResolver
});
this.endpointErrorUnionDeclarationReferencer = new EndpointDeclarationReferencer({
Expand All @@ -370,6 +386,8 @@ export class SdkGenerator {
this.environmentsDeclarationReferencer = new EnvironmentsDeclarationReferencer({
containingDirectory: [],
namespaceExport,
namingOverride: naming.environment,
environmentUrlsNamingOverride: naming.environmentUrls,
npmPackage: this.npmPackage,
environmentsConfig: intermediateRepresentation.environments ?? undefined,
relativePackagePath: this.relativePackagePath,
Expand All @@ -393,11 +411,13 @@ export class SdkGenerator {
});
this.genericAPISdkErrorDeclarationReferencer = new GenericAPISdkErrorDeclarationReferencer({
containingDirectory: [],
namespaceExport
namespaceExport,
namingOverride: naming.error
});
this.timeoutSdkErrorDeclarationReferencer = new TimeoutSdkErrorDeclarationReferencer({
containingDirectory: [],
namespaceExport
namespaceExport,
namingOverride: naming.timeoutError
});
this.nonStatusCodeErrorHandlerDeclarationReferencer = new NonStatusCodeErrorHandlerDeclarationReferencer({
containingDirectory: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export declare namespace AbstractDeclarationReferencer {
namespaceExport: string;
containingDirectory: ExportedDirectory[];
consolidateTypeFiles?: boolean;
namingOverride?: string;
}
}

Expand All @@ -23,14 +24,18 @@ export abstract class AbstractDeclarationReferencer<Name = never> implements Dec
protected containingDirectory: ExportedDirectory[];
protected consolidateTypeFiles: boolean;

protected namingOverride: string | undefined;

constructor({
namespaceExport,
containingDirectory,
consolidateTypeFiles = false
consolidateTypeFiles = false,
namingOverride
}: AbstractDeclarationReferencer.Init) {
this.namespaceExport = namespaceExport;
this.containingDirectory = containingDirectory;
this.consolidateTypeFiles = consolidateTypeFiles;
this.namingOverride = namingOverride;
}

public abstract getExportedFilepath(name: Name): ExportedFilePath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export declare namespace EnvironmentsDeclarationReferencer {
environmentsConfig: FernIr.EnvironmentsConfig | undefined;
relativePackagePath: string;
relativeTestPath: string;
environmentUrlsNamingOverride?: string;
}
}

Expand All @@ -27,19 +28,22 @@ export class EnvironmentsDeclarationReferencer extends AbstractDeclarationRefere
private environmentsConfig: FernIr.EnvironmentsConfig | undefined;
private readonly relativePackagePath: string;
private readonly relativeTestPath: string;
private environmentUrlsNamingOverride: string | undefined;

constructor({
npmPackage,
environmentsConfig,
relativePackagePath,
relativeTestPath,
environmentUrlsNamingOverride,
...superInit
}: EnvironmentsDeclarationReferencer.Init) {
super(superInit);
this.npmPackage = npmPackage;
this.environmentsConfig = environmentsConfig;
this.relativePackagePath = relativePackagePath;
this.relativeTestPath = relativeTestPath;
this.environmentUrlsNamingOverride = environmentUrlsNamingOverride;
}

public getExportedFilepath(): ExportedFilePath {
Expand Down Expand Up @@ -67,11 +71,11 @@ export class EnvironmentsDeclarationReferencer extends AbstractDeclarationRefere
}

public getExportedNameOfEnvironmentsEnum(): string {
return `${this.namespaceExport}Environment`;
return this.namingOverride ?? `${this.namespaceExport}Environment`;
}

public getExportedNameOfEnvironmentUrls(): string {
return `${this.namespaceExport}EnvironmentUrls`;
return this.environmentUrlsNamingOverride ?? `${this.namespaceExport}EnvironmentUrls`;
}

public getExportedNameOfFirstEnvironmentEnum(): { namepaceImport: string; exportedName: string } | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { AbstractSdkErrorDeclarationReferencer } from "./AbstractSdkErrorDeclara

export class GenericAPISdkErrorDeclarationReferencer extends AbstractSdkErrorDeclarationReferencer {
public getExportedName(): string {
return `${this.namespaceExport}Error`;
return this.namingOverride ?? `${this.namespaceExport}Error`;
}
}
Loading
Loading