From c54df7ad3e491ccb4cc2283db00eb6ab0d10181d Mon Sep 17 00:00:00 2001 From: Fern Support <126544928+fern-support@users.noreply.github.com> Date: Tue, 24 Feb 2026 01:01:45 -0500 Subject: [PATCH 1/7] chore(typescript): update ts-express seed (#12690) Co-authored-by: fern-support --- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- .../core/schemas/builders/list/list.ts | 38 ++-- .../object-like/getObjectLikeUtils.ts | 20 +- .../core/schemas/builders/object/object.ts | 180 ++++++++++++------ .../core/schemas/builders/record/record.ts | 67 +++---- .../core/schemas/builders/union/union.ts | 11 +- .../core/schemas/utils/isPlainObject.ts | 11 +- 48 files changed, 1544 insertions(+), 1072 deletions(-) diff --git a/seed/ts-express/pagination-uri-path/core/schemas/builders/list/list.ts b/seed/ts-express/pagination-uri-path/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/pagination-uri-path/core/schemas/builders/list/list.ts +++ b/seed/ts-express/pagination-uri-path/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/pagination-uri-path/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/pagination-uri-path/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/pagination-uri-path/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/pagination-uri-path/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/pagination-uri-path/core/schemas/builders/object/object.ts b/seed/ts-express/pagination-uri-path/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/pagination-uri-path/core/schemas/builders/object/object.ts +++ b/seed/ts-express/pagination-uri-path/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/pagination-uri-path/core/schemas/builders/record/record.ts b/seed/ts-express/pagination-uri-path/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/pagination-uri-path/core/schemas/builders/record/record.ts +++ b/seed/ts-express/pagination-uri-path/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/pagination-uri-path/core/schemas/builders/union/union.ts b/seed/ts-express/pagination-uri-path/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/pagination-uri-path/core/schemas/builders/union/union.ts +++ b/seed/ts-express/pagination-uri-path/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/pagination-uri-path/core/schemas/utils/isPlainObject.ts b/seed/ts-express/pagination-uri-path/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/pagination-uri-path/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/pagination-uri-path/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } diff --git a/seed/ts-express/path-parameters/core/schemas/builders/list/list.ts b/seed/ts-express/path-parameters/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/path-parameters/core/schemas/builders/list/list.ts +++ b/seed/ts-express/path-parameters/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/path-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/path-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/path-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/path-parameters/core/schemas/builders/object/object.ts b/seed/ts-express/path-parameters/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/path-parameters/core/schemas/builders/object/object.ts +++ b/seed/ts-express/path-parameters/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/path-parameters/core/schemas/builders/record/record.ts b/seed/ts-express/path-parameters/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/path-parameters/core/schemas/builders/record/record.ts +++ b/seed/ts-express/path-parameters/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/path-parameters/core/schemas/builders/union/union.ts b/seed/ts-express/path-parameters/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/path-parameters/core/schemas/builders/union/union.ts +++ b/seed/ts-express/path-parameters/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/path-parameters/core/schemas/utils/isPlainObject.ts b/seed/ts-express/path-parameters/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/path-parameters/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/path-parameters/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } diff --git a/seed/ts-express/property-access/core/schemas/builders/list/list.ts b/seed/ts-express/property-access/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/property-access/core/schemas/builders/list/list.ts +++ b/seed/ts-express/property-access/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/property-access/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/property-access/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/property-access/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/property-access/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/property-access/core/schemas/builders/object/object.ts b/seed/ts-express/property-access/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/property-access/core/schemas/builders/object/object.ts +++ b/seed/ts-express/property-access/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/property-access/core/schemas/builders/record/record.ts b/seed/ts-express/property-access/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/property-access/core/schemas/builders/record/record.ts +++ b/seed/ts-express/property-access/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/property-access/core/schemas/builders/union/union.ts b/seed/ts-express/property-access/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/property-access/core/schemas/builders/union/union.ts +++ b/seed/ts-express/property-access/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/property-access/core/schemas/utils/isPlainObject.ts b/seed/ts-express/property-access/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/property-access/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/property-access/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } diff --git a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/list/list.ts b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/list/list.ts +++ b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object/object.ts b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object/object.ts +++ b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/record/record.ts b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/record/record.ts +++ b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/union/union.ts b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/union/union.ts +++ b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/utils/isPlainObject.ts b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/query-parameters-openapi-as-objects/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } diff --git a/seed/ts-express/query-parameters-openapi/core/schemas/builders/list/list.ts b/seed/ts-express/query-parameters-openapi/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/query-parameters-openapi/core/schemas/builders/list/list.ts +++ b/seed/ts-express/query-parameters-openapi/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/query-parameters-openapi/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/query-parameters-openapi/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/query-parameters-openapi/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/query-parameters-openapi/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/query-parameters-openapi/core/schemas/builders/object/object.ts b/seed/ts-express/query-parameters-openapi/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/query-parameters-openapi/core/schemas/builders/object/object.ts +++ b/seed/ts-express/query-parameters-openapi/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/query-parameters-openapi/core/schemas/builders/record/record.ts b/seed/ts-express/query-parameters-openapi/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/query-parameters-openapi/core/schemas/builders/record/record.ts +++ b/seed/ts-express/query-parameters-openapi/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/query-parameters-openapi/core/schemas/builders/union/union.ts b/seed/ts-express/query-parameters-openapi/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/query-parameters-openapi/core/schemas/builders/union/union.ts +++ b/seed/ts-express/query-parameters-openapi/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/query-parameters-openapi/core/schemas/utils/isPlainObject.ts b/seed/ts-express/query-parameters-openapi/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/query-parameters-openapi/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/query-parameters-openapi/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } diff --git a/seed/ts-express/query-parameters/core/schemas/builders/list/list.ts b/seed/ts-express/query-parameters/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/query-parameters/core/schemas/builders/list/list.ts +++ b/seed/ts-express/query-parameters/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/query-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/query-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/query-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/query-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/query-parameters/core/schemas/builders/object/object.ts b/seed/ts-express/query-parameters/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/query-parameters/core/schemas/builders/object/object.ts +++ b/seed/ts-express/query-parameters/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/query-parameters/core/schemas/builders/record/record.ts b/seed/ts-express/query-parameters/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/query-parameters/core/schemas/builders/record/record.ts +++ b/seed/ts-express/query-parameters/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/query-parameters/core/schemas/builders/union/union.ts b/seed/ts-express/query-parameters/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/query-parameters/core/schemas/builders/union/union.ts +++ b/seed/ts-express/query-parameters/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/query-parameters/core/schemas/utils/isPlainObject.ts b/seed/ts-express/query-parameters/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/query-parameters/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/query-parameters/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } diff --git a/seed/ts-express/request-parameters/core/schemas/builders/list/list.ts b/seed/ts-express/request-parameters/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/request-parameters/core/schemas/builders/list/list.ts +++ b/seed/ts-express/request-parameters/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/request-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/request-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/request-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/request-parameters/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/request-parameters/core/schemas/builders/object/object.ts b/seed/ts-express/request-parameters/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/request-parameters/core/schemas/builders/object/object.ts +++ b/seed/ts-express/request-parameters/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/request-parameters/core/schemas/builders/record/record.ts b/seed/ts-express/request-parameters/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/request-parameters/core/schemas/builders/record/record.ts +++ b/seed/ts-express/request-parameters/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/request-parameters/core/schemas/builders/union/union.ts b/seed/ts-express/request-parameters/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/request-parameters/core/schemas/builders/union/union.ts +++ b/seed/ts-express/request-parameters/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/request-parameters/core/schemas/utils/isPlainObject.ts b/seed/ts-express/request-parameters/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/request-parameters/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/request-parameters/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } diff --git a/seed/ts-express/required-nullable/core/schemas/builders/list/list.ts b/seed/ts-express/required-nullable/core/schemas/builders/list/list.ts index 7c8fd6e87db9..191922f17453 100644 --- a/seed/ts-express/required-nullable/core/schemas/builders/list/list.ts +++ b/seed/ts-express/required-nullable/core/schemas/builders/list/list.ts @@ -44,30 +44,20 @@ function validateAndTransformArray( }; } - const maybeValidItems = value.map((item, index) => transformItem(item, index)); + const result: Parsed[] = []; + const errors: ValidationError[] = []; - return maybeValidItems.reduce>( - (acc, item) => { - if (acc.ok && item.ok) { - return { - ok: true, - value: [...acc.value, item.value], - }; - } - - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } - if (!item.ok) { - errors.push(...item.errors); - } + for (let i = 0; i < value.length; i++) { + const item = transformItem(value[i], i); + if (item.ok) { + result.push(item.value); + } else { + errors.push(...item.errors); + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: [] }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/required-nullable/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/required-nullable/core/schemas/builders/object-like/getObjectLikeUtils.ts index ed96cf771cbb..9f2777f6f2d9 100644 --- a/seed/ts-express/required-nullable/core/schemas/builders/object-like/getObjectLikeUtils.ts +++ b/seed/ts-express/required-nullable/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -5,6 +5,9 @@ import { isPlainObject } from "../../utils/isPlainObject"; import { getSchemaUtils } from "../schema-utils/index"; import type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { return { withParsedProperties: (properties) => withParsedProperties(schema, properties), @@ -26,15 +29,14 @@ export function withParsedProperties>( - (processed, [key, value]) => { - return { - ...processed, - [key]: typeof value === "function" ? value(parsedObject.value) : value, - }; - }, - {}, - ); + const additionalProperties: Record = {}; + for (const key in properties) { + if (_hasOwn.call(properties, key)) { + const value = properties[key as keyof Properties]; + additionalProperties[key] = + typeof value === "function" ? (value as Function)(parsedObject.value) : value; + } + } return { ok: true, diff --git a/seed/ts-express/required-nullable/core/schemas/builders/object/object.ts b/seed/ts-express/required-nullable/core/schemas/builders/object/object.ts index 024a96e54a56..eb48a1116e66 100644 --- a/seed/ts-express/required-nullable/core/schemas/builders/object/object.ts +++ b/seed/ts-express/required-nullable/core/schemas/builders/object/object.ts @@ -19,6 +19,9 @@ import type { PropertySchemas, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + interface ObjectPropertyWithRawKey { rawKey: string; parsedKey: string; @@ -28,79 +31,128 @@ interface ObjectPropertyWithRawKey { export function object>( schemas: T, ): inferObjectSchemaFromPropertySchemas { - const baseSchema: BaseObjectSchema< - inferRawObjectFromPropertySchemas, - inferParsedObjectFromPropertySchemas - > = { - _getRawProperties: () => - Object.entries(schemas).map(([parsedKey, propertySchema]) => - isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, - ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], - _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + // All property metadata is lazily computed on first use. + // This keeps schema construction free of iteration, which matters when + // many schemas are defined but only some are exercised at runtime. + // Required-key computation is also deferred because lazy() schemas may + // not be resolved at construction time. - parse: (raw, opts) => { - const rawKeyToProperty: Record = {}; - const requiredKeys: string[] = []; + let _rawKeyToProperty: Record | undefined; + function getRawKeyToProperty(): Record { + if (_rawKeyToProperty == null) { + _rawKeyToProperty = {}; for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); const valueSchema: Schema = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.valueSchema : schemaOrObjectProperty; - const property: ObjectPropertyWithRawKey = { + _rawKeyToProperty[rawKey] = { rawKey, parsedKey: parsedKey as string, valueSchema, }; + } + } + return _rawKeyToProperty; + } - rawKeyToProperty[rawKey] = property; + let _parseRequiredKeys: string[] | undefined; + let _jsonRequiredKeys: string[] | undefined; + let _parseRequiredKeysSet: Set | undefined; + let _jsonRequiredKeysSet: Set | undefined; + function getParseRequiredKeys(): string[] { + if (_parseRequiredKeys == null) { + _parseRequiredKeys = []; + _jsonRequiredKeys = []; + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.rawKey + : (parsedKey as string); + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; if (isSchemaRequired(valueSchema)) { - requiredKeys.push(rawKey); + _parseRequiredKeys.push(rawKey); + _jsonRequiredKeys.push(parsedKey as string); } } + _parseRequiredKeysSet = new Set(_parseRequiredKeys); + _jsonRequiredKeysSet = new Set(_jsonRequiredKeys); + } + return _parseRequiredKeys; + } + + function getJsonRequiredKeys(): string[] { + if (_jsonRequiredKeys == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeys!; + } + function getParseRequiredKeysSet(): Set { + if (_parseRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _parseRequiredKeysSet!; + } + + function getJsonRequiredKeysSet(): Set { + if (_jsonRequiredKeysSet == null) { + getParseRequiredKeys(); + } + return _jsonRequiredKeysSet!; + } + + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey, + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: raw, - requiredKeys, + requiredKeys: getParseRequiredKeys(), + requiredKeysSet: getParseRequiredKeysSet(), getProperty: (rawKey) => { - const property = rawKeyToProperty[rawKey]; + const property = getRawKeyToProperty()[rawKey]; if (property == null) { return undefined; } return { transformedKey: property.parsedKey, - transform: (propertyValue) => - property.valueSchema.parse(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, rawKey]; + return property.valueSchema.parse(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, json: (parsed, opts) => { - const requiredKeys: string[] = []; - - for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { - const valueSchema: Schema = isProperty(schemaOrObjectProperty) - ? schemaOrObjectProperty.valueSchema - : schemaOrObjectProperty; - - if (isSchemaRequired(valueSchema)) { - requiredKeys.push(parsedKey as string); - } - } - + const breadcrumbsPrefix = opts?.breadcrumbsPrefix ?? []; return validateAndTransformObject({ value: parsed, - requiredKeys, + requiredKeys: getJsonRequiredKeys(), + requiredKeysSet: getJsonRequiredKeysSet(), getProperty: ( parsedKey, ): { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined => { @@ -114,26 +166,30 @@ export function object - property.valueSchema.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.valueSchema.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } else { return { transformedKey: parsedKey, - transform: (propertyValue) => - property.json(propertyValue, { + transform: (propertyValue) => { + const childBreadcrumbs = [...breadcrumbsPrefix, parsedKey]; + return property.json(propertyValue, { ...opts, - breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], - }), + breadcrumbsPrefix: childBreadcrumbs, + }); + }, }; } }, unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, skipValidation: opts?.skipValidation, - breadcrumbsPrefix: opts?.breadcrumbsPrefix, + breadcrumbsPrefix, omitUndefined: opts?.omitUndefined, }); }, @@ -152,6 +208,7 @@ export function object({ value, requiredKeys, + requiredKeysSet, getProperty, unrecognizedObjectKeys = "fail", skipValidation = false, @@ -159,6 +216,7 @@ function validateAndTransformObject({ }: { value: unknown; requiredKeys: string[]; + requiredKeysSet: Set; getProperty: ( preTransformedKey: string, ) => { transformedKey: string; transform: (propertyValue: object) => MaybeValid } | undefined; @@ -179,15 +237,23 @@ function validateAndTransformObject({ }; } - const missingRequiredKeys = new Set(requiredKeys); + // Track which required keys have been seen. + // Use a counter instead of copying the Set to avoid per-call allocation. + let missingRequiredCount = requiredKeys.length; const errors: ValidationError[] = []; const transformed: Record = {}; - for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + for (const preTransformedKey in value) { + if (!_hasOwn.call(value, preTransformedKey)) { + continue; + } + const preTransformedItemValue = value[preTransformedKey]; const property = getProperty(preTransformedKey); if (property != null) { - missingRequiredKeys.delete(preTransformedKey); + if (missingRequiredCount > 0 && requiredKeysSet.has(preTransformedKey)) { + missingRequiredCount--; + } const value = property.transform(preTransformedItemValue as object); if (value.ok) { @@ -213,14 +279,16 @@ function validateAndTransformObject({ } } - errors.push( - ...requiredKeys - .filter((key) => missingRequiredKeys.has(key)) - .map((key) => ({ - path: breadcrumbsPrefix, - message: `Missing required key "${key}"`, - })), - ); + if (missingRequiredCount > 0) { + for (const key of requiredKeys) { + if (!(key in (value as Record))) { + errors.push({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + }); + } + } + } if (errors.length === 0 || skipValidation) { return { diff --git a/seed/ts-express/required-nullable/core/schemas/builders/record/record.ts b/seed/ts-express/required-nullable/core/schemas/builders/record/record.ts index ba5307a6a45c..f11dfae0ec67 100644 --- a/seed/ts-express/required-nullable/core/schemas/builders/record/record.ts +++ b/seed/ts-express/required-nullable/core/schemas/builders/record/record.ts @@ -1,11 +1,13 @@ import { type MaybeValid, type Schema, SchemaType, type ValidationError } from "../../Schema"; -import { entries } from "../../utils/entries"; import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; import { isPlainObject } from "../../utils/isPlainObject"; import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; import { getSchemaUtils } from "../schema-utils/index"; import type { BaseRecordSchema, RecordSchema } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function record( keySchema: Schema, valueSchema: Schema, @@ -79,51 +81,42 @@ function validateAndTransformRecord>>( - (accPromise, [stringKey, value]) => { - if (value === undefined) { - return accPromise; - } - - const acc = accPromise; - - let key: string | number = stringKey; - if (isKeyNumeric) { - const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; - if (!Number.isNaN(numberKey)) { - key = numberKey; - } - } - const transformedKey = transformKey(key); + const result = {} as Record; + const errors: ValidationError[] = []; - const transformedValue = transformValue(value, key); + for (const stringKey in value) { + if (!_hasOwn.call(value, stringKey)) { + continue; + } + const entryValue = (value as Record)[stringKey]; + if (entryValue === undefined) { + continue; + } - if (acc.ok && transformedKey.ok && transformedValue.ok) { - return { - ok: true, - value: { - ...acc.value, - [transformedKey.value]: transformedValue.value, - }, - }; + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!Number.isNaN(numberKey)) { + key = numberKey; } + } + const transformedKey = transformKey(key); + const transformedValue = transformValue(entryValue, key); - const errors: ValidationError[] = []; - if (!acc.ok) { - errors.push(...acc.errors); - } + if (transformedKey.ok && transformedValue.ok) { + result[transformedKey.value] = transformedValue.value; + } else { if (!transformedKey.ok) { errors.push(...transformedKey.errors); } if (!transformedValue.ok) { errors.push(...transformedValue.errors); } + } + } - return { - ok: false, - errors, - }; - }, - { ok: true, value: {} as Record }, - ); + if (errors.length === 0) { + return { ok: true, value: result }; + } + return { ok: false, errors }; } diff --git a/seed/ts-express/required-nullable/core/schemas/builders/union/union.ts b/seed/ts-express/required-nullable/core/schemas/builders/union/union.ts index 7da4271a27c0..b324fa2de27e 100644 --- a/seed/ts-express/required-nullable/core/schemas/builders/union/union.ts +++ b/seed/ts-express/required-nullable/core/schemas/builders/union/union.ts @@ -16,6 +16,9 @@ import type { UnionSubtypes, } from "./types"; +// eslint-disable-next-line @typescript-eslint/unbound-method +const _hasOwn = Object.prototype.hasOwnProperty; + export function union, U extends UnionSubtypes>( discriminant: D, union: U, @@ -112,7 +115,13 @@ function transformAndValidateUnion< }; } - const { [discriminant]: discriminantValue, ...additionalProperties } = value; + const discriminantValue = value[discriminant]; + const additionalProperties: Record = {}; + for (const key in value) { + if (_hasOwn.call(value, key) && key !== discriminant) { + additionalProperties[key] = value[key]; + } + } if (discriminantValue == null) { return { diff --git a/seed/ts-express/required-nullable/core/schemas/utils/isPlainObject.ts b/seed/ts-express/required-nullable/core/schemas/utils/isPlainObject.ts index db82a722c35b..32a17e05f01e 100644 --- a/seed/ts-express/required-nullable/core/schemas/utils/isPlainObject.ts +++ b/seed/ts-express/required-nullable/core/schemas/utils/isPlainObject.ts @@ -4,14 +4,11 @@ export function isPlainObject(value: unknown): value is Record return false; } - if (Object.getPrototypeOf(value) === null) { + const proto = Object.getPrototypeOf(value); + if (proto === null) { return true; } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - - return Object.getPrototypeOf(value) === proto; + // Check that the prototype chain has exactly one level (i.e., proto is Object.prototype) + return Object.getPrototypeOf(proto) === null; } From f3c302cbfc7018139543a912d68eb29160a0889c Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Tue, 24 Feb 2026 01:08:05 -0500 Subject: [PATCH 2/7] fix(typescript): replace lodash-es template with Eta for template file processing (#12696) Co-authored-by: unknown <> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- generators/typescript/sdk/versions.yml | 12 ++++++++++++ .../typescript/utils/commons/package.json | 1 + .../utils/commons/src/writeTemplateFiles.ts | 7 ++++--- .../FormDataWrapper.template.ts | 4 ++-- .../core-utilities/tests/setup.template.ts | 18 +++++++++--------- .../formDataWrapper.test.template.ts | 4 ++-- pnpm-lock.yaml | 9 +++++++++ 7 files changed, 39 insertions(+), 16 deletions(-) diff --git a/generators/typescript/sdk/versions.yml b/generators/typescript/sdk/versions.yml index 1689affebade..d0a697a6bc25 100644 --- a/generators/typescript/sdk/versions.yml +++ b/generators/typescript/sdk/versions.yml @@ -1,4 +1,16 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 3.49.2 + changelogEntry: + - summary: | + Replace lodash-es `template` with Eta for template file processing. The lodash + templating engine crashes on backticks (template literals) because it uses + `new Function()` internally. Eta is a modern, lightweight, zero-dependency + templating engine that properly handles backticks and uses the same `<% %>` + syntax, requiring no changes to existing template files. + type: fix + createdAt: "2026-02-24" + irVersion: 63 + - version: 3.49.1 changelogEntry: - summary: | diff --git a/generators/typescript/utils/commons/package.json b/generators/typescript/utils/commons/package.json index f97e3cd51ea7..ffd14bc07967 100644 --- a/generators/typescript/utils/commons/package.json +++ b/generators/typescript/utils/commons/package.json @@ -39,6 +39,7 @@ "@fern-fern/ir-sdk": "^64.0.0", "decompress": "catalog:", "esutils": "catalog:", + "eta": "^4.5.1", "glob": "catalog:", "immer": "^10.1.1", "lodash-es": "catalog:", diff --git a/generators/typescript/utils/commons/src/writeTemplateFiles.ts b/generators/typescript/utils/commons/src/writeTemplateFiles.ts index 8e0523811107..860b3e1eb85b 100644 --- a/generators/typescript/utils/commons/src/writeTemplateFiles.ts +++ b/generators/typescript/utils/commons/src/writeTemplateFiles.ts @@ -1,7 +1,9 @@ +import { Eta } from "eta"; import * as fs from "fs/promises"; -import { template } from "lodash-es"; import * as path from "path"; +const eta = new Eta({ autoEscape: false, useWith: true, autoTrim: false }); + export async function writeTemplateFiles(directory: string, templateVariables: Record): Promise { const templateFiles = await findTemplateFiles(directory); @@ -36,8 +38,7 @@ async function processTemplateFile( templateVariables: Record ): Promise { const templateContent = await fs.readFile(templateFilePath, "utf8"); - const compiledTemplate = template(templateContent); - const content = compiledTemplate(templateVariables); + const content = eta.renderString(templateContent, templateVariables); const outputFilePath = templateFilePath.replace(/\.template\./, "."); await fs.writeFile(outputFilePath, content, "utf8"); await fs.unlink(templateFilePath); diff --git a/generators/typescript/utils/core-utilities/src/core/form-data-utils/FormDataWrapper.template.ts b/generators/typescript/utils/core-utilities/src/core/form-data-utils/FormDataWrapper.template.ts index 2477eb175ff6..f4bf09b0cee7 100644 --- a/generators/typescript/utils/core-utilities/src/core/form-data-utils/FormDataWrapper.template.ts +++ b/generators/typescript/utils/core-utilities/src/core/form-data-utils/FormDataWrapper.template.ts @@ -300,7 +300,7 @@ async function streamToBuffer(stream: unknown): Promise { } throw new Error( - "Unsupported stream type: " + typeof stream + ". Expected Node.js Readable stream or Web ReadableStream.", + `Unsupported stream type: ${typeof stream}. Expected Node.js Readable stream or Web ReadableStream.`, ); } @@ -335,4 +335,4 @@ async function convertToBlob(value: unknown, contentType?: string): Promise "expected " + actualType + " not to contain " + this.utils.printExpected(expectedHeaders), + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { const messages: string[] = []; if (missingHeaders.length > 0) { - messages.push("Missing headers: " + this.utils.printExpected(missingHeaders.join(", "))); + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); } if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + ": expected " + this.utils.printExpected(expected) + " but got " + this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + actualType + " to contain " + this.utils.printExpected(expectedHeaders) + "\n\n" + messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } @@ -125,27 +125,27 @@ expect.extend({ if (pass) { return { - message: () => "expected " + actualType + " not to contain " + this.utils.printExpected(expectedHeaders), + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { const messages: string[] = []; if (missingHeaders.length > 0) { - messages.push("Missing headers: " + this.utils.printExpected(missingHeaders.join(", "))); + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); } if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + ": expected " + this.utils.printExpected(expected) + " but got " + this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + actualType + " to contain " + this.utils.printExpected(expectedHeaders) + "\n\n" + messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } @@ -167,4 +167,4 @@ declare global { } export {}; -<% } %> \ No newline at end of file +<% } %> diff --git a/generators/typescript/utils/core-utilities/tests/unit/form-data-utils/formDataWrapper.test.template.ts b/generators/typescript/utils/core-utilities/tests/unit/form-data-utils/formDataWrapper.test.template.ts index 40f4328314b7..58475212e182 100644 --- a/generators/typescript/utils/core-utilities/tests/unit/form-data-utils/formDataWrapper.test.template.ts +++ b/generators/typescript/utils/core-utilities/tests/unit/form-data-utils/formDataWrapper.test.template.ts @@ -93,7 +93,7 @@ describe("CrossPlatformFormData", () => { for await (const chunk of request.body) { data += decoder.decode(chunk); } - expect(data).toContain("Content-Disposition: form-data; name=\"file\"; filename=\"" + expectedFileName + "\""); + expect(data).toContain(`Content-Disposition: form-data; name="file"; filename="${expectedFileName}"`); }); }); @@ -503,4 +503,4 @@ describe("FormDataWrapper", () => { }); }); }); -<% } %> \ No newline at end of file +<% } %> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ec6bc29c400..a23b80d72fae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3963,6 +3963,9 @@ importers: esutils: specifier: 'catalog:' version: 2.0.3 + eta: + specifier: ^4.5.1 + version: 4.5.1 glob: specifier: 'catalog:' version: 11.1.0 @@ -12140,6 +12143,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eta@4.5.1: + resolution: {integrity: sha512-EaNCGm+8XEIU7YNcc+THptWAO5NfKBHHARxt+wxZljj9bTr/+arRoOm9/MpGt4n6xn9fLnPFRSoLD0WFYGFUxQ==} + engines: {node: '>=20'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -18991,6 +18998,8 @@ snapshots: esutils@2.0.3: {} + eta@4.5.1: {} + etag@1.8.1: {} event-target-shim@5.0.1: {} From b1304811375e87be5a16fd80739b11801f359980 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Tue, 24 Feb 2026 01:17:37 -0500 Subject: [PATCH 3/7] fix(csharp): replace lodash-es template with Eta for template file processing (#12697) Co-authored-by: unknown <> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- generators/csharp/base/package.json | 1 + .../csharp/base/src/project/CsharpProject.ts | 42 ++++++++++--------- generators/csharp/sdk/versions.yml | 11 +++++ pnpm-lock.yaml | 3 ++ 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/generators/csharp/base/package.json b/generators/csharp/base/package.json index c307279871c0..56bf63ddfe6c 100644 --- a/generators/csharp/base/package.json +++ b/generators/csharp/base/package.json @@ -40,6 +40,7 @@ "@fern-fern/ir-sdk": "^62.3.0", "@types/lodash-es": "catalog:", "@types/node": "catalog:", + "eta": "^4.5.1", "lodash-es": "catalog:", "typescript": "catalog:", "vitest": "catalog:" diff --git a/generators/csharp/base/src/project/CsharpProject.ts b/generators/csharp/base/src/project/CsharpProject.ts index 72cc0409953c..6e67cbf42465 100644 --- a/generators/csharp/base/src/project/CsharpProject.ts +++ b/generators/csharp/base/src/project/CsharpProject.ts @@ -2,14 +2,16 @@ import { AbstractProject, FernGeneratorExec, File, SourceFetcher } from "@fern-a import { Generation, WithGeneration } from "@fern-api/csharp-codegen"; import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; import { loggingExeca } from "@fern-api/logging-execa"; +import { Eta } from "eta"; import { access, mkdir, readFile, unlink, writeFile } from "fs/promises"; -import { template } from "lodash-es"; import path from "path"; import { AsIsFiles } from "../AsIs.js"; import { GeneratorContext } from "../context/GeneratorContext.js"; import { findDotnetToolPath } from "../findDotNetToolPath.js"; import { CSharpFile } from "./CSharpFile.js"; +const eta = new Eta({ autoEscape: false, useWith: true, autoTrim: false }); + export const CORE_DIRECTORY_NAME = "Core"; export const PUBLIC_CORE_DIRECTORY_NAME = "Public"; /** @@ -285,22 +287,24 @@ export class CsharpProject extends AbstractProject { } const githubWorkflowTemplate = (await readFile(getAsIsFilepath(AsIsFiles.CiYaml))).toString(); - const githubWorkflow = template(githubWorkflowTemplate)({ - projectName: this.name, - libraryPath: path.posix.join(libraryPath, this.name), - libraryProjectFilePath: path.posix.join(libraryPath, this.name, `${this.name}.csproj`), - testProjectFilePath: path.posix.join( - testPath, - this.names.files.testProject, - `${this.names.files.testProject}.csproj` - ), - shouldWritePublishBlock: this.context.publishConfig != null, - nugetTokenEnvvar: - this.context.publishConfig?.apiKeyEnvironmentVariable == null || - this.context.publishConfig?.apiKeyEnvironmentVariable === "" - ? "NUGET_API_TOKEN" - : this.context.publishConfig.apiKeyEnvironmentVariable - }).replaceAll("\\{", "{"); + const githubWorkflow = eta + .renderString(githubWorkflowTemplate, { + projectName: this.name, + libraryPath: path.posix.join(libraryPath, this.name), + libraryProjectFilePath: path.posix.join(libraryPath, this.name, `${this.name}.csproj`), + testProjectFilePath: path.posix.join( + testPath, + this.names.files.testProject, + `${this.names.files.testProject}.csproj` + ), + shouldWritePublishBlock: this.context.publishConfig != null, + nugetTokenEnvvar: + this.context.publishConfig?.apiKeyEnvironmentVariable == null || + this.context.publishConfig?.apiKeyEnvironmentVariable === "" + ? "NUGET_API_TOKEN" + : this.context.publishConfig.apiKeyEnvironmentVariable + }) + .replaceAll("\\{", "{"); const ghDir = join(this.absolutePathToOutputDirectory, RelativeFilePath.of(".github/workflows")); await mkdir(ghDir, { recursive: true }); await writeFile(join(ghDir, RelativeFilePath.of("ci.yml")), githubWorkflow); @@ -436,7 +440,7 @@ dotnet_diagnostic.IDE0005.severity = error const testCsProjTemplateContents = ( await readFile(getAsIsFilepath(AsIsFiles.Test.TemplateTestCsProj)) ).toString(); - const testCsProjContents = template(testCsProjTemplateContents)({ + const testCsProjContents = eta.renderString(testCsProjTemplateContents, { projectName: this.name, testProjectName }); @@ -699,7 +703,7 @@ dotnet_diagnostic.IDE0005.severity = error } function replaceTemplate({ contents, variables }: { contents: string; variables: Record }): string { - return template(contents)(variables); + return eta.renderString(contents, variables); } function getAsIsFilepath(filename: string): string { diff --git a/generators/csharp/sdk/versions.yml b/generators/csharp/sdk/versions.yml index 97aa0e838a2d..6bc62c90c3a5 100644 --- a/generators/csharp/sdk/versions.yml +++ b/generators/csharp/sdk/versions.yml @@ -1,4 +1,15 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 2.22.3 + changelogEntry: + - summary: | + Replace lodash-es template engine with Eta for processing template files. + Eta is a modern, lightweight, TypeScript-native engine with zero dependencies + that uses the same `<% %>` / `<%= %>` syntax. This resolves crashes when + template files contain backticks (template literals). + type: fix + createdAt: "2026-02-24" + irVersion: 62 + - version: 2.22.2 changelogEntry: - summary: | diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a23b80d72fae..fac80de358f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -973,6 +973,9 @@ importers: '@types/node': specifier: 'catalog:' version: 18.15.3 + eta: + specifier: ^4.5.1 + version: 4.5.1 lodash-es: specifier: 'catalog:' version: 4.17.23 From 2b2de56f5a9f72e5adccf852fce2fefc6fc200e2 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Tue, 24 Feb 2026 01:48:30 -0500 Subject: [PATCH 4/7] chore(typescript): update oxfmt to 0.35.0, oxlint to 1.50.0, and oxlint-tsgolint to 0.14.2 (#12700) Co-authored-by: unknown <> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- docker/seed/Dockerfile.ts | 6 +- generators/typescript/express/cli/Dockerfile | 12 +- generators/typescript/express/versions.yml | 8 + generators/typescript/sdk/cli/Dockerfile | 6 +- generators/typescript/sdk/versions.yml | 8 + .../typescript-project/TypescriptProject.ts | 6 +- .../simple-api/use-oxc/.fern/metadata.json | 18 +- .../use-oxc/.github/workflows/ci.yml | 54 ++-- .../ts-sdk/simple-api/use-oxc/CONTRIBUTING.md | 3 - seed/ts-sdk/simple-api/use-oxc/README.md | 255 ------------------ seed/ts-sdk/simple-api/use-oxc/package.json | 42 +-- .../simple-api/use-oxc/pnpm-workspace.yaml | 2 +- seed/ts-sdk/simple-api/use-oxc/reference.md | 51 ---- .../use-oxc/scripts/rename-to-esm-files.js | 2 +- .../simple-api/use-oxc/src/BaseClient.ts | 28 +- seed/ts-sdk/simple-api/use-oxc/src/Client.ts | 8 +- .../src/api/resources/user/client/Client.ts | 29 +- .../src/api/resources/user/client/index.ts | 2 +- .../use-oxc/src/auth/BearerAuthProvider.ts | 33 ++- .../use-oxc/src/core/fetcher/getFetchFn.ts | 2 + .../src/core/fetcher/getResponseBody.ts | 5 +- .../simple-api/use-oxc/src/core/headers.ts | 4 +- .../simple-api/use-oxc/src/environments.ts | 10 +- .../use-oxc/src/errors/SeedSimpleApiError.ts | 31 +-- .../src/errors/handleNonStatusCodeError.ts | 48 ++-- .../simple-api/use-oxc/tests/custom.test.ts | 17 +- seed/ts-sdk/simple-api/use-oxc/tests/setup.ts | 20 +- .../simple-api/use-oxc/tests/tsconfig.json | 6 +- .../tests/unit/fetcher/Fetcher.test.ts | 3 + .../unit/fetcher/getResponseBody.test.ts | 2 + .../unit/fetcher/requestWithRetries.test.ts | 28 +- .../use-oxc/tests/wire/user.test.ts | 30 ++- .../simple-api/use-oxc/tsconfig.base.json | 6 +- .../simple-api/use-oxc/tsconfig.cjs.json | 6 +- .../simple-api/use-oxc/tsconfig.esm.json | 6 +- seed/ts-sdk/simple-api/use-oxc/tsconfig.json | 2 +- .../simple-api/use-oxc/vitest.config.mts | 57 ++-- .../simple-api/use-oxfmt/.fern/metadata.json | 16 +- .../use-oxfmt/.github/workflows/ci.yml | 54 ++-- .../simple-api/use-oxfmt/CONTRIBUTING.md | 3 - seed/ts-sdk/simple-api/use-oxfmt/README.md | 255 ------------------ seed/ts-sdk/simple-api/use-oxfmt/package.json | 40 +-- .../simple-api/use-oxfmt/pnpm-workspace.yaml | 2 +- seed/ts-sdk/simple-api/use-oxfmt/reference.md | 51 ---- .../simple-api/use-oxfmt/src/BaseClient.ts | 31 +-- .../ts-sdk/simple-api/use-oxfmt/src/Client.ts | 10 +- .../src/api/resources/user/client/Client.ts | 32 +-- .../src/api/resources/user/client/index.ts | 2 +- .../use-oxfmt/src/auth/BearerAuthProvider.ts | 33 ++- .../use-oxfmt/src/core/fetcher/getFetchFn.ts | 2 + .../src/core/fetcher/getResponseBody.ts | 5 +- .../simple-api/use-oxfmt/src/core/headers.ts | 4 +- .../simple-api/use-oxfmt/src/environments.ts | 10 +- .../src/errors/SeedSimpleApiError.ts | 35 +-- .../src/errors/handleNonStatusCodeError.ts | 50 ++-- .../simple-api/use-oxfmt/tests/custom.test.ts | 17 +- .../simple-api/use-oxfmt/tests/setup.ts | 18 +- .../simple-api/use-oxfmt/tests/tsconfig.json | 6 +- .../tests/unit/fetcher/Fetcher.test.ts | 3 + .../unit/fetcher/getResponseBody.test.ts | 2 + .../tests/unit/fetcher/makeRequest.test.ts | 2 +- .../unit/fetcher/requestWithRetries.test.ts | 30 +-- .../use-oxfmt/tests/wire/user.test.ts | 30 ++- .../simple-api/use-oxfmt/tsconfig.base.json | 6 +- .../simple-api/use-oxfmt/tsconfig.cjs.json | 6 +- .../simple-api/use-oxfmt/tsconfig.esm.json | 6 +- .../ts-sdk/simple-api/use-oxfmt/tsconfig.json | 2 +- .../simple-api/use-oxfmt/vitest.config.mts | 57 ++-- seed/ts-sdk/simple-api/use-oxlint/README.md | 254 ----------------- .../ts-sdk/simple-api/use-oxlint/package.json | 4 +- .../ts-sdk/simple-api/use-oxlint/reference.md | 50 ---- .../use-oxlint/scripts/rename-to-esm-files.js | 2 +- .../simple-api/use-oxlint/tests/setup.ts | 18 +- 73 files changed, 535 insertions(+), 1469 deletions(-) diff --git a/docker/seed/Dockerfile.ts b/docker/seed/Dockerfile.ts index b7c00adf89f8..e79055947048 100644 --- a/docker/seed/Dockerfile.ts +++ b/docker/seed/Dockerfile.ts @@ -12,10 +12,10 @@ RUN corepack prepare yarn@1.22.22 RUN pnpm add -g typescript@~5.7.2 \ prettier@3.7.4 \ - oxfmt@0.27.0 \ + oxfmt@0.35.0 \ @biomejs/biome@2.4.3 \ - oxlint@1.42.0 \ - oxlint-tsgolint@0.11.4 \ + oxlint@1.50.0 \ + oxlint-tsgolint@0.14.2 \ @types/node@^18.19.70 \ webpack@^5.97.1 \ msw@2.11.2 \ diff --git a/generators/typescript/express/cli/Dockerfile b/generators/typescript/express/cli/Dockerfile index 65d6f3c28e7f..40544b2adf1c 100644 --- a/generators/typescript/express/cli/Dockerfile +++ b/generators/typescript/express/cli/Dockerfile @@ -11,10 +11,10 @@ ENV PATH=$PNPM_HOME:$PATH RUN npm install -g pnpm@10.20.0 --force RUN npm install -g yarn@1.22.22 --force RUN pnpm install -g prettier@3.7.4 -RUN pnpm install -g oxfmt@0.27.0 +RUN pnpm install -g oxfmt@0.35.0 RUN pnpm install -g @biomejs/biome@2.4.3 -RUN pnpm install -g oxlint@1.42.0 -RUN pnpm install -g oxlint-tsgolint@0.11.4 +RUN pnpm install -g oxlint@1.50.0 +RUN pnpm install -g oxlint-tsgolint@0.14.2 WORKDIR /tmp/cache-warm @@ -30,9 +30,9 @@ RUN echo '{ \ "express": "4.18.2", \ "@biomejs/biome": "2.4.3", \ "prettier": "3.7.4", \ - "oxfmt": "0.27.0", \ - "oxlint": "1.42.0", \ - "oxlint-tsgolint": "0.11.4", \ + "oxfmt": "0.35.0", \ + "oxlint": "1.50.0", \ + "oxlint-tsgolint": "0.14.2", \ "typescript": "5.7.2", \ "url-join": "4.0.1" \ } \ diff --git a/generators/typescript/express/versions.yml b/generators/typescript/express/versions.yml index 6a5dab3b65b2..2b891f804ddb 100644 --- a/generators/typescript/express/versions.yml +++ b/generators/typescript/express/versions.yml @@ -1,4 +1,12 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- changelogEntry: + - summary: | + Update oxfmt to 0.35.0, oxlint to 1.50.0, and oxlint-tsgolint to 0.14.2. + type: chore + irVersion: 61 + createdAt: "2026-02-24" + version: 0.19.3 + - changelogEntry: - summary: | Update @biomejs/biome from 2.3.11 to 2.4.3. diff --git a/generators/typescript/sdk/cli/Dockerfile b/generators/typescript/sdk/cli/Dockerfile index 311b5a85b729..cdabbebc8a7e 100644 --- a/generators/typescript/sdk/cli/Dockerfile +++ b/generators/typescript/sdk/cli/Dockerfile @@ -12,10 +12,10 @@ RUN npm install -g pnpm@10.20.0 --force RUN npm install -g yarn@1.22.22 --force RUN pnpm add -g typescript@~5.7.2 \ prettier@3.7.4 \ - oxfmt@0.27.0 \ + oxfmt@0.35.0 \ @biomejs/biome@2.4.3 \ - oxlint@1.42.0 \ - oxlint-tsgolint@0.11.4 \ + oxlint@1.50.0 \ + oxlint-tsgolint@0.14.2 \ @fern-api/generator-cli@0.5.3 \ @types/node@^18.19.70 \ ts-loader@^9.5.1 \ diff --git a/generators/typescript/sdk/versions.yml b/generators/typescript/sdk/versions.yml index d0a697a6bc25..fb5d97f31d28 100644 --- a/generators/typescript/sdk/versions.yml +++ b/generators/typescript/sdk/versions.yml @@ -1,4 +1,12 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 3.49.3 + changelogEntry: + - summary: | + Update oxfmt to 0.35.0, oxlint to 1.50.0, and oxlint-tsgolint to 0.14.2. + type: chore + createdAt: "2026-02-24" + irVersion: 63 + - version: 3.49.2 changelogEntry: - summary: | diff --git a/generators/typescript/utils/commons/src/typescript-project/TypescriptProject.ts b/generators/typescript/utils/commons/src/typescript-project/TypescriptProject.ts index eeea0745c010..c623e8a8052c 100644 --- a/generators/typescript/utils/commons/src/typescript-project/TypescriptProject.ts +++ b/generators/typescript/utils/commons/src/typescript-project/TypescriptProject.ts @@ -384,14 +384,14 @@ export abstract class TypescriptProject { deps["@biomejs/biome"] = "2.4.3"; } if (this.linter === "oxlint") { - deps["oxlint"] = "1.42.0"; - deps["oxlint-tsgolint"] = "0.11.4"; + deps["oxlint"] = "1.50.0"; + deps["oxlint-tsgolint"] = "0.14.2"; } if (this.formatter === "prettier") { deps["prettier"] = "3.7.4"; } if (this.formatter === "oxfmt") { - deps["oxfmt"] = "0.27.0"; + deps["oxfmt"] = "0.35.0"; } return deps; } diff --git a/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json b/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json index 66905e17f541..592db7f1c3b1 100644 --- a/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json +++ b/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json @@ -1,10 +1,10 @@ { - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "latest", - "generatorConfig": { - "linter": "oxlint", - "formatter": "oxfmt" - }, - "sdkVersion": "0.0.1" -} + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-typescript-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "linter": "oxlint", + "formatter": "oxfmt" + }, + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml b/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml index f270d341b2c2..a98d4d00ff0e 100644 --- a/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml +++ b/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml @@ -3,40 +3,40 @@ name: ci on: [push] jobs: - compile: - runs-on: ubuntu-latest + compile: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 + - name: Set up node + uses: actions/setup-node@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Compile - run: pnpm build + - name: Compile + run: pnpm build - test: - runs-on: ubuntu-latest + test: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 + - name: Set up node + uses: actions/setup-node@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Test - run: pnpm test + - name: Test + run: pnpm test diff --git a/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md b/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md index ac478f0bb42e..fe5bc2f77e0b 100644 --- a/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md +++ b/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md @@ -34,7 +34,6 @@ pnpm test ``` Run specific test types: - - `pnpm test:unit` - Run unit tests - `pnpm test:wire` - Run wire/integration tests @@ -67,7 +66,6 @@ pnpm run check:fix ### Generated Files The following directories contain generated code: - - `src/api/` - API client classes and types - `src/serialization/` - Serialization/deserialization logic - Most TypeScript files in `src/` @@ -98,7 +96,6 @@ If you want to change how code is generated for all users of this SDK: 4. Submit a pull request with your changes to the generator This approach is best for: - - Bug fixes in generated code - New features that would benefit all users - Improvements to code generation patterns diff --git a/seed/ts-sdk/simple-api/use-oxc/README.md b/seed/ts-sdk/simple-api/use-oxc/README.md index b31eac333998..e69de29bb2d1 100644 --- a/seed/ts-sdk/simple-api/use-oxc/README.md +++ b/seed/ts-sdk/simple-api/use-oxc/README.md @@ -1,255 +0,0 @@ -# Seed TypeScript Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) -[![npm shield](https://img.shields.io/npm/v/@fern/simple-api)](https://www.npmjs.com/package/@fern/simple-api) - -The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Aborting Requests](#aborting-requests) - - [Access Raw Response Data](#access-raw-response-data) - - [Logging](#logging) - - [Runtime Compatibility](#runtime-compatibility) -- [Contributing](#contributing) - -## Installation - -```sh -npm i -s @fern/simple-api -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```typescript -import { SeedSimpleApiClient, SeedSimpleApiEnvironment } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ environment: SeedSimpleApiEnvironment.Production, token: "YOUR_TOKEN" }); -await client.user.get("id"); -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```typescript -import { SeedSimpleApiError } from "@fern/simple-api"; - -try { - await client.user.get(...); -} catch (err) { - if (err instanceof SeedSimpleApiError) { - console.log(err.statusCode); - console.log(err.message); - console.log(err.body); - console.log(err.rawResponse); - } -} -``` - -## Advanced - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `headers` request option. - -```typescript -import { SeedSimpleApiClient } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - headers: { - 'X-Custom-Header': 'custom value' - } -}); - -const response = await client.user.get(..., { - headers: { - 'X-Custom-Header': 'custom value' - } -}); -``` - -### Additional Query String Parameters - -If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. - -```typescript -const response = await client.user.get(..., { - queryParams: { - 'customQueryParamKey': 'custom query param value' - } -}); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` request option to configure this behavior. - -```typescript -const response = await client.user.get(..., { - maxRetries: 0 // override maxRetries at the request level -}); -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. - -```typescript -const response = await client.user.get(..., { - timeoutInSeconds: 30 // override timeout to 30s -}); -``` - -### Aborting Requests - -The SDK allows users to abort requests at any point by passing in an abort signal. - -```typescript -const controller = new AbortController(); -const response = await client.user.get(..., { - abortSignal: controller.signal -}); -controller.abort(); // aborts the request -``` - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. -The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. - -```typescript -const { data, rawResponse } = await client.user.get(...).withRawResponse(); - -console.log(data); -console.log(rawResponse.headers['X-My-Header']); -``` - -### Logging - -The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. - -```typescript -import { SeedSimpleApiClient, logging } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - logging: { - level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info - logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger - silent: false, // defaults to true, set to false to enable logging - } -}); -``` - -The `logging` object can have the following properties: - -- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. -- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. -- `silent`: Whether to silence the logger. Defaults to `true`. - -The `level` property can be one of the following values: - -- `logging.LogLevel.Debug` -- `logging.LogLevel.Info` -- `logging.LogLevel.Warn` -- `logging.LogLevel.Error` - -To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. - -
-Custom logger examples - -Here's an example using the popular `winston` logging library. - -```ts -import winston from 'winston'; - -const winstonLogger = winston.createLogger({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => winstonLogger.debug(msg, ...args), - info: (msg, ...args) => winstonLogger.info(msg, ...args), - warn: (msg, ...args) => winstonLogger.warn(msg, ...args), - error: (msg, ...args) => winstonLogger.error(msg, ...args), -}; -``` - -Here's an example using the popular `pino` logging library. - -```ts -import pino from 'pino'; - -const pinoLogger = pino({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => pinoLogger.debug(args, msg), - info: (msg, ...args) => pinoLogger.info(args, msg), - warn: (msg, ...args) => pinoLogger.warn(args, msg), - error: (msg, ...args) => pinoLogger.error(args, msg), -}; -``` - -
- -### Runtime Compatibility - -The SDK works in the following runtimes: - -- Node.js 18+ -- Vercel -- Cloudflare Workers -- Deno v1.25+ -- Bun 1.0+ -- React Native - -### Customizing Fetch Client - -The SDK provides a way for you to customize the underlying HTTP client / Fetch function. If you're running in an -unsupported environment, this provides a way for you to break glass and ensure the SDK works. - -```typescript -import { SeedSimpleApiClient } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - fetcher: // provide your implementation here -}); -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/simple-api/use-oxc/package.json b/seed/ts-sdk/simple-api/use-oxc/package.json index bf74e9ec7ab2..232e36da907d 100644 --- a/seed/ts-sdk/simple-api/use-oxc/package.json +++ b/seed/ts-sdk/simple-api/use-oxc/package.json @@ -6,22 +6,9 @@ "type": "git", "url": "git+https://github.com/simple-api/fern.git" }, - "files": [ - "dist", - "reference.md", - "README.md", - "LICENSE" - ], "type": "commonjs", - "sideEffects": false, "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", - "browser": { - "fs": false, - "os": false, - "path": false, - "stream": false - }, "types": "./dist/cjs/index.d.ts", "exports": { ".": { @@ -38,6 +25,12 @@ }, "./package.json": "./package.json" }, + "files": [ + "dist", + "reference.md", + "README.md", + "LICENSE" + ], "scripts": { "format": "oxfmt --no-error-on-unmatched-pattern .", "format:check": "oxfmt --check --no-error-on-unmatched-pattern .", @@ -54,18 +47,25 @@ }, "dependencies": {}, "devDependencies": { - "@types/node": "^18.19.70", - "msw": "2.11.2", - "oxfmt": "0.27.0", - "oxlint": "1.42.0", - "oxlint-tsgolint": "0.11.4", + "webpack": "^5.97.1", "ts-loader": "^9.5.1", - "typescript": "~5.7.2", "vitest": "^3.2.4", - "webpack": "^5.97.1" + "msw": "2.11.2", + "@types/node": "^18.19.70", + "typescript": "~5.7.2", + "oxlint": "1.50.0", + "oxlint-tsgolint": "0.14.2", + "oxfmt": "0.35.0" + }, + "browser": { + "fs": false, + "os": false, + "path": false, + "stream": false }, + "packageManager": "pnpm@10.20.0", "engines": { "node": ">=18.0.0" }, - "packageManager": "pnpm@10.20.0" + "sideEffects": false } diff --git a/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml b/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml index 339da38e3da8..6e4c395107df 100644 --- a/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml +++ b/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml @@ -1 +1 @@ -packages: ["."] +packages: ['.'] \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/reference.md b/seed/ts-sdk/simple-api/use-oxc/reference.md index d3121dd7b2dd..e69de29bb2d1 100644 --- a/seed/ts-sdk/simple-api/use-oxc/reference.md +++ b/seed/ts-sdk/simple-api/use-oxc/reference.md @@ -1,51 +0,0 @@ -# Reference - -## User - -
client.user.get(id) -> SeedSimpleApi.User -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.user.get("id"); -``` - -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `string` - -
-
- -
-
- -**requestOptions:** `UserClient.RequestOptions` - -
-
-
-
- -
-
-
diff --git a/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js b/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js index 561ec17d82d2..dc1df1cbbacb 100644 --- a/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js +++ b/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js @@ -56,7 +56,7 @@ async function updateFileContents(file) { // Handle dynamic imports (yield import, await import, regular import()) const dynamicRegex = new RegExp( - `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, + `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, "g", ); newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); diff --git a/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts b/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts index f38b01529e40..c32dc24af40b 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts @@ -38,26 +38,18 @@ export interface BaseRequestOptions { export type NormalizedClientOptions = T & { logging: core.logging.Logger; authProvider?: core.AuthProvider; -}; +} -export type NormalizedClientOptionsWithAuth = - NormalizedClientOptions & { - authProvider: core.AuthProvider; - }; +export type NormalizedClientOptionsWithAuth = NormalizedClientOptions & { + authProvider: core.AuthProvider; +} export function normalizeClientOptions( - options: T, + options: T ): NormalizedClientOptions { const headers = mergeHeaders( - { - "X-Fern-Language": "JavaScript", - "X-Fern-SDK-Name": "@fern/simple-api", - "X-Fern-SDK-Version": "0.0.1", - "User-Agent": "@fern/simple-api/0.0.1", - "X-Fern-Runtime": core.RUNTIME.type, - "X-Fern-Runtime-Version": core.RUNTIME.version, - }, - options?.headers, + { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/simple-api", "X-Fern-SDK-Version": "0.0.1", "User-Agent": "@fern/simple-api/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version }, + options?.headers ); return { @@ -68,7 +60,7 @@ export function normalizeClientOptions( - options: T, + options: T ): NormalizedClientOptionsWithAuth { const normalized = normalizeClientOptions(options) as NormalizedClientOptionsWithAuth; const normalizedWithNoOpAuthProvider = withNoOpAuthProvider(normalized); @@ -77,10 +69,10 @@ export function normalizeClientOptionsWithAuth( - options: NormalizedClientOptions, + options: NormalizedClientOptions ): NormalizedClientOptionsWithAuth { return { ...options, - authProvider: new core.NoOpAuthProvider(), + authProvider: new core.NoOpAuthProvider() }; } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/Client.ts b/seed/ts-sdk/simple-api/use-oxc/src/Client.ts index 3049d3294978..68e367690017 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/Client.ts @@ -9,7 +9,8 @@ import * as environments from "./environments.js"; export declare namespace SeedSimpleApiClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions {} + export interface RequestOptions extends BaseRequestOptions { + } } export class SeedSimpleApiClient { @@ -17,7 +18,10 @@ export class SeedSimpleApiClient { protected _user: UserClient | undefined; constructor(options: SeedSimpleApiClient.Options) { - this._options = normalizeClientOptionsWithAuth(options); + + + this._options = normalizeClientOptionsWithAuth(options); + } public get user(): UserClient { diff --git a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts index 65fa1acc87e6..bc8c41bb099e 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts @@ -12,14 +12,18 @@ import * as SeedSimpleApi from "../../../index.js"; export declare namespace UserClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions {} + export interface RequestOptions extends BaseRequestOptions { + } } export class UserClient { protected readonly _options: NormalizedClientOptionsWithAuth; constructor(options: UserClient.Options) { - this._options = normalizeClientOptionsWithAuth(options); + + + this._options = normalizeClientOptionsWithAuth(options); + } /** @@ -33,22 +37,11 @@ export class UserClient { return core.HttpResponsePromise.fromPromise(this.__get(id, requestOptions)); } - private async __get( - id: string, - requestOptions?: UserClient.RequestOptions, - ): Promise> { + private async __get(id: string, requestOptions?: UserClient.RequestOptions): Promise> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); - let _headers: core.Fetcher.Args["headers"] = mergeHeaders( - _authRequest.headers, - this._options?.headers, - requestOptions?.headers, - ); + let _headers: core.Fetcher.Args["headers"] = mergeHeaders(_authRequest.headers, this._options?.headers, requestOptions?.headers); const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)), - `/users/${core.url.encodePathParam(id)}`, - ), + url: core.url.join(await core.Supplier.get(this._options.baseUrl) ?? await core.Supplier.get(this._options.environment), `/users/${core.url.encodePathParam(id)}`), method: "GET", headers: _headers, queryParameters: requestOptions?.queryParams, @@ -56,7 +49,7 @@ export class UserClient { maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, abortSignal: requestOptions?.abortSignal, fetchFn: this._options?.fetch, - logging: this._options.logging, + logging: this._options.logging }); if (_response.ok) { return { data: _response.body as SeedSimpleApi.User, rawResponse: _response.rawResponse }; @@ -66,7 +59,7 @@ export class UserClient { throw new errors.SeedSimpleApiError({ statusCode: _response.error.statusCode, body: _response.error.body, - rawResponse: _response.rawResponse, + rawResponse: _response.rawResponse }); } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts index cb0ff5c3b541..2234b9cae16d 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts @@ -1 +1 @@ -export {}; +export { }; diff --git a/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts b/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts index 63b42583d7c1..e199d99d0963 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts @@ -16,28 +16,27 @@ export class BearerAuthProvider implements core.AuthProvider { return options?.[TOKEN_PARAM] != null; } - public async getAuthRequest({ - endpointMetadata, - }: { - endpointMetadata?: core.EndpointMetadata; - } = {}): Promise { - const token = await core.Supplier.get(this.options[TOKEN_PARAM]); - if (token == null) { - throw new errors.SeedSimpleApiError({ - message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, - }); - } - - return { - headers: { Authorization: `Bearer ${token}` }, - }; + public async getAuthRequest({ endpointMetadata }: { + endpointMetadata?: core.EndpointMetadata; + } = {}): Promise { + + const token = await core.Supplier.get(this.options[TOKEN_PARAM]); + if (token == null) { + throw new errors.SeedSimpleApiError({ + message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, + }); + } + + return { + headers: { Authorization: `Bearer ${token}` }, + }; + } } export namespace BearerAuthProvider { export const AUTH_SCHEME = "bearer" as const; - export const AUTH_CONFIG_ERROR_MESSAGE: string = - `Please provide '${TOKEN_PARAM}' when initializing the client` as const; + export const AUTH_CONFIG_ERROR_MESSAGE: string = `Please provide '${TOKEN_PARAM}' when initializing the client` as const; export type Options = AuthOptions; export type AuthOptions = { [TOKEN_PARAM]: core.Supplier }; diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts index 9f845b956392..8d88fe84d06d 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts @@ -1,3 +1,5 @@ + export async function getFetchFn(): Promise { return fetch; } + diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts index 708d55728f2b..5f6162e98085 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts @@ -1,6 +1,7 @@ import { fromJson } from "../json.js"; import { getBinaryResponse } from "./BinaryResponse.js"; + export async function getResponseBody(response: Response, responseType?: string): Promise { switch (responseType) { case "binary-response": @@ -30,9 +31,9 @@ export async function getResponseBody(response: Response, responseType?: string) }, }; } - + return response.body; - + case "text": return await response.text(); } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts index be45c4552a35..cb327036972e 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts @@ -1,4 +1,6 @@ -export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { +export function mergeHeaders( + ...headersArray: (Record | null | undefined)[] +): Record { const result: Record = {}; for (const [key, value] of headersArray diff --git a/seed/ts-sdk/simple-api/use-oxc/src/environments.ts b/seed/ts-sdk/simple-api/use-oxc/src/environments.ts index 0955c381185b..aca434160feb 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/environments.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/environments.ts @@ -1,10 +1,8 @@ // This file was auto-generated by Fern from our API Definition. export const SeedSimpleApiEnvironment = { - Production: "https://api.example.com", - Staging: "https://staging-api.example.com", -} as const; + Production: "https://api.example.com", + Staging: "https://staging-api.example.com", + } as const; -export type SeedSimpleApiEnvironment = - | typeof SeedSimpleApiEnvironment.Production - | typeof SeedSimpleApiEnvironment.Staging; +export type SeedSimpleApiEnvironment = typeof SeedSimpleApiEnvironment.Production | typeof SeedSimpleApiEnvironment.Staging; diff --git a/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts b/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts index b6792821f7ba..c7ff68ac2c8a 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts @@ -8,17 +8,12 @@ export class SeedSimpleApiError extends Error { public readonly body?: unknown; public readonly rawResponse?: core.RawResponse; - constructor({ - message, - statusCode, - body, - rawResponse, - }: { - message?: string; - statusCode?: number; - body?: unknown; - rawResponse?: core.RawResponse; - }) { + constructor({ message, statusCode, body, rawResponse }: { + message?: string; + statusCode?: number; + body?: unknown; + rawResponse?: core.RawResponse; + }) { super(buildMessage({ message, statusCode, body })); Object.setPrototypeOf(this, new.target.prototype); if (Error.captureStackTrace) { @@ -32,15 +27,11 @@ export class SeedSimpleApiError extends Error { } } -function buildMessage({ - message, - statusCode, - body, -}: { - message: string | undefined; - statusCode: number | undefined; - body: unknown | undefined; -}): string { +function buildMessage({ message, statusCode, body }: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; + }): string { let lines: string[] = []; if (message != null) { lines.push(message); diff --git a/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts index 48aa6f183f5d..bc8dce0450df 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts @@ -3,35 +3,25 @@ import * as core from "../core/index.js"; import * as errors from "./index.js"; -export function handleNonStatusCodeError( - error: core.Fetcher.Error, - rawResponse: core.RawResponse, - method: string, - path: string, -): never { +export function handleNonStatusCodeError(error: core.Fetcher.Error, rawResponse: core.RawResponse, method: string, path: string): never { switch (error.reason) { - case "non-json": - throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - body: error.rawBody, - rawResponse: rawResponse, - }); - case "body-is-null": - throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - rawResponse: rawResponse, - }); - case "timeout": - throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); - case "unknown": - throw new errors.SeedSimpleApiError({ - message: error.errorMessage, - rawResponse: rawResponse, - }); - default: - throw new errors.SeedSimpleApiError({ - message: "Unknown error", - rawResponse: rawResponse, - }); + case "non-json": throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + body: error.rawBody, + rawResponse: rawResponse + }); + case "body-is-null": throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + rawResponse: rawResponse + }); + case "timeout": throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); + case "unknown": throw new errors.SeedSimpleApiError({ + message: error.errorMessage, + rawResponse: rawResponse + }); + default: throw new errors.SeedSimpleApiError({ + message: "Unknown error", + rawResponse: rawResponse + }); } } diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts index 7f5e031c8396..6927092387b6 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts @@ -1,13 +1,14 @@ + /** - * This is a custom test file, if you wish to add more tests - * to your SDK. - * Be sure to mark this file in `.fernignore`. - * - * If you include example requests/responses in your fern definition, - * you will have tests automatically generated for you. - */ +* This is a custom test file, if you wish to add more tests +* to your SDK. +* Be sure to mark this file in `.fernignore`. +* +* If you include example requests/responses in your fern definition, +* you will have tests automatically generated for you. +*/ describe("test", () => { it("default", () => { expect(true).toBe(true); }); -}); +}); \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts b/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts index 201f5fbc968f..e2a8edf51c0b 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts @@ -1,3 +1,4 @@ + import { expect } from "vitest"; interface CustomMatchers { @@ -52,39 +53,30 @@ expect.extend({ if (pass) { return { - message: () => - "expected " + actualType + " not to contain " + this.utils.printExpected(expectedHeaders), + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { const messages: string[] = []; if (missingHeaders.length > 0) { - messages.push("Missing headers: " + this.utils.printExpected(missingHeaders.join(", "))); + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); } if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + - ": expected " + - this.utils.printExpected(expected) + - " but got " + - this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + - actualType + - " to contain " + - this.utils.printExpected(expectedHeaders) + - "\n\n" + - messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } }, }); + diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json b/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json index a477df47920c..b8eeea994bfa 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json @@ -4,8 +4,8 @@ "outDir": null, "rootDir": "..", "baseUrl": "..", - "types": ["vitest/globals"] + "types": ["vitest/globals"] }, - "include": ["../src", "../tests"], + "include": ["../src","../tests"], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts index 6c17624228bb..4632efa6f501 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts @@ -76,6 +76,8 @@ describe("Test fetcherImpl", () => { } }); + + it("should receive file as stream", async () => { const url = "https://httpbin.org/post/file"; const mockArgs: Fetcher.Args = { @@ -259,4 +261,5 @@ describe("Test fetcherImpl", () => { expect(body.bodyUsed).toBe(true); } }); + }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts index ad6be7fc2c9b..8ef1cf11c85b 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts @@ -73,6 +73,7 @@ describe("Test getResponseBody", () => { } }); + it("should handle streaming response type", async () => { const encoder = new TextEncoder(); const testData = "test stream data"; @@ -94,4 +95,5 @@ describe("Test getResponseBody", () => { const streamContent = decoder.decode(value); expect(streamContent).toBe(testData); }); + }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts index 53e5c25fe648..01519cf17132 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts @@ -13,20 +13,20 @@ describe("requestWithRetries", () => { Math.random = vi.fn(() => 0.5); vi.useFakeTimers({ - toFake: [ - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "Date", - "performance", - "requestAnimationFrame", - "cancelAnimationFrame", - "requestIdleCallback", - "cancelIdleCallback", - ], + toFake: [ + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "Date", + "performance", + "requestAnimationFrame", + "cancelAnimationFrame", + "requestIdleCallback", + "cancelIdleCallback" + ] }); }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts index f5c1d699da35..8d74497c00e3 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts @@ -4,18 +4,28 @@ import { SeedSimpleApiClient } from "../../src/Client"; import { mockServerPool } from "../mock-server/MockServerPool"; describe("UserClient", () => { + test("get", async () => { const server = mockServerPool.createServer(); - const client = new SeedSimpleApiClient({ maxRetries: 0, token: "test", environment: server.baseUrl }); + const client = new SeedSimpleApiClient({ "maxRetries" : 0 , "token" : "test" , "environment" : server.baseUrl }); + + const rawResponseBody = { "id" : "id" , "name" : "name" , "email" : "email" }; + server + .mockEndpoint() + .get("/users/id").respondWith() + .statusCode(200).jsonBody(rawResponseBody) + .build(); - const rawResponseBody = { id: "id", name: "name", email: "email" }; - server.mockEndpoint().get("/users/id").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.user.get("id"); - expect(response).toEqual({ - id: "id", - name: "name", - email: "email", - }); + + + const response = await client.user.get("id"); + expect(response).toEqual({ + id: "id", + name: "name", + email: "email" +}); + + }); + }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json index d7627675de20..e48f3fe29c34 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json @@ -13,6 +13,8 @@ "isolatedModules": true, "isolatedDeclarations": true }, - "include": ["src"], + "include": [ + "src" + ], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json index 5c11446f5984..baf12c360e23 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json @@ -4,6 +4,8 @@ "module": "CommonJS", "outDir": "dist/cjs" }, - "include": ["src"], + "include": [ + "src" + ], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json index 6ce909748b2c..5f8ac6fcca67 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json @@ -5,6 +5,8 @@ "outDir": "dist/esm", "verbatimModuleSyntax": true }, - "include": ["src"], + "include": [ + "src" + ], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.json index d77fdf00d259..ad43234b056f 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.json @@ -1,3 +1,3 @@ { "extends": "./tsconfig.cjs.json" -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts b/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts index ba2ec4f9d45a..e21d088aa14c 100644 --- a/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts +++ b/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts @@ -1,28 +1,31 @@ -import { defineConfig } from "vitest/config"; -export default defineConfig({ - test: { - projects: [ - { + + + import { defineConfig } from "vitest/config"; + export default defineConfig({ test: { - globals: true, - name: "unit", - environment: "node", - root: "./tests", - include: ["**/*.test.{js,ts,jsx,tsx}"], - exclude: ["wire/**"], - setupFiles: ["./setup.ts"], - }, - }, - { - test: { - globals: true, - name: "wire", - environment: "node", - root: "./tests/wire", - setupFiles: ["../setup.ts", "../mock-server/setup.ts"], - }, - }, - ], - passWithNoTests: true, - }, -}); + projects: [ + { + test: { + globals: true, + name: "unit", + environment: "node", + root: "./tests", + include: ["**/*.test.{js,ts,jsx,tsx}"], + exclude: ["wire/**"], + setupFiles: ["./setup.ts"] + } + }, + { + test: { + globals: true, + name: "wire", + environment: "node", + root: "./tests/wire", + setupFiles: ["../setup.ts", "../mock-server/setup.ts"] + } + }, + ], + passWithNoTests: true + } + }); + \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json b/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json index cbb40406f13b..36d4fa17cb4a 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json @@ -1,9 +1,9 @@ { - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "latest", - "generatorConfig": { - "formatter": "oxfmt" - }, - "sdkVersion": "0.0.1" -} + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-typescript-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "formatter": "oxfmt" + }, + "sdkVersion": "0.0.1" +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml b/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml index f270d341b2c2..a98d4d00ff0e 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml +++ b/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml @@ -3,40 +3,40 @@ name: ci on: [push] jobs: - compile: - runs-on: ubuntu-latest + compile: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 + - name: Set up node + uses: actions/setup-node@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Compile - run: pnpm build + - name: Compile + run: pnpm build - test: - runs-on: ubuntu-latest + test: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 + - name: Set up node + uses: actions/setup-node@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Test - run: pnpm test + - name: Test + run: pnpm test diff --git a/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md b/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md index ac478f0bb42e..fe5bc2f77e0b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md +++ b/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md @@ -34,7 +34,6 @@ pnpm test ``` Run specific test types: - - `pnpm test:unit` - Run unit tests - `pnpm test:wire` - Run wire/integration tests @@ -67,7 +66,6 @@ pnpm run check:fix ### Generated Files The following directories contain generated code: - - `src/api/` - API client classes and types - `src/serialization/` - Serialization/deserialization logic - Most TypeScript files in `src/` @@ -98,7 +96,6 @@ If you want to change how code is generated for all users of this SDK: 4. Submit a pull request with your changes to the generator This approach is best for: - - Bug fixes in generated code - New features that would benefit all users - Improvements to code generation patterns diff --git a/seed/ts-sdk/simple-api/use-oxfmt/README.md b/seed/ts-sdk/simple-api/use-oxfmt/README.md index b31eac333998..e69de29bb2d1 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/README.md +++ b/seed/ts-sdk/simple-api/use-oxfmt/README.md @@ -1,255 +0,0 @@ -# Seed TypeScript Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) -[![npm shield](https://img.shields.io/npm/v/@fern/simple-api)](https://www.npmjs.com/package/@fern/simple-api) - -The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Aborting Requests](#aborting-requests) - - [Access Raw Response Data](#access-raw-response-data) - - [Logging](#logging) - - [Runtime Compatibility](#runtime-compatibility) -- [Contributing](#contributing) - -## Installation - -```sh -npm i -s @fern/simple-api -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```typescript -import { SeedSimpleApiClient, SeedSimpleApiEnvironment } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ environment: SeedSimpleApiEnvironment.Production, token: "YOUR_TOKEN" }); -await client.user.get("id"); -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```typescript -import { SeedSimpleApiError } from "@fern/simple-api"; - -try { - await client.user.get(...); -} catch (err) { - if (err instanceof SeedSimpleApiError) { - console.log(err.statusCode); - console.log(err.message); - console.log(err.body); - console.log(err.rawResponse); - } -} -``` - -## Advanced - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `headers` request option. - -```typescript -import { SeedSimpleApiClient } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - headers: { - 'X-Custom-Header': 'custom value' - } -}); - -const response = await client.user.get(..., { - headers: { - 'X-Custom-Header': 'custom value' - } -}); -``` - -### Additional Query String Parameters - -If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. - -```typescript -const response = await client.user.get(..., { - queryParams: { - 'customQueryParamKey': 'custom query param value' - } -}); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` request option to configure this behavior. - -```typescript -const response = await client.user.get(..., { - maxRetries: 0 // override maxRetries at the request level -}); -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. - -```typescript -const response = await client.user.get(..., { - timeoutInSeconds: 30 // override timeout to 30s -}); -``` - -### Aborting Requests - -The SDK allows users to abort requests at any point by passing in an abort signal. - -```typescript -const controller = new AbortController(); -const response = await client.user.get(..., { - abortSignal: controller.signal -}); -controller.abort(); // aborts the request -``` - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. -The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. - -```typescript -const { data, rawResponse } = await client.user.get(...).withRawResponse(); - -console.log(data); -console.log(rawResponse.headers['X-My-Header']); -``` - -### Logging - -The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. - -```typescript -import { SeedSimpleApiClient, logging } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - logging: { - level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info - logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger - silent: false, // defaults to true, set to false to enable logging - } -}); -``` - -The `logging` object can have the following properties: - -- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. -- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. -- `silent`: Whether to silence the logger. Defaults to `true`. - -The `level` property can be one of the following values: - -- `logging.LogLevel.Debug` -- `logging.LogLevel.Info` -- `logging.LogLevel.Warn` -- `logging.LogLevel.Error` - -To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. - -
-Custom logger examples - -Here's an example using the popular `winston` logging library. - -```ts -import winston from 'winston'; - -const winstonLogger = winston.createLogger({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => winstonLogger.debug(msg, ...args), - info: (msg, ...args) => winstonLogger.info(msg, ...args), - warn: (msg, ...args) => winstonLogger.warn(msg, ...args), - error: (msg, ...args) => winstonLogger.error(msg, ...args), -}; -``` - -Here's an example using the popular `pino` logging library. - -```ts -import pino from 'pino'; - -const pinoLogger = pino({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => pinoLogger.debug(args, msg), - info: (msg, ...args) => pinoLogger.info(args, msg), - warn: (msg, ...args) => pinoLogger.warn(args, msg), - error: (msg, ...args) => pinoLogger.error(args, msg), -}; -``` - -
- -### Runtime Compatibility - -The SDK works in the following runtimes: - -- Node.js 18+ -- Vercel -- Cloudflare Workers -- Deno v1.25+ -- Bun 1.0+ -- React Native - -### Customizing Fetch Client - -The SDK provides a way for you to customize the underlying HTTP client / Fetch function. If you're running in an -unsupported environment, this provides a way for you to break glass and ensure the SDK works. - -```typescript -import { SeedSimpleApiClient } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - fetcher: // provide your implementation here -}); -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/simple-api/use-oxfmt/package.json b/seed/ts-sdk/simple-api/use-oxfmt/package.json index e6b03dcc5e21..e860872011b2 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/package.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/package.json @@ -6,22 +6,9 @@ "type": "git", "url": "git+https://github.com/simple-api/fern.git" }, - "files": [ - "dist", - "reference.md", - "README.md", - "LICENSE" - ], "type": "commonjs", - "sideEffects": false, "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", - "browser": { - "fs": false, - "os": false, - "path": false, - "stream": false - }, "types": "./dist/cjs/index.d.ts", "exports": { ".": { @@ -38,6 +25,12 @@ }, "./package.json": "./package.json" }, + "files": [ + "dist", + "reference.md", + "README.md", + "LICENSE" + ], "scripts": { "format": "oxfmt --no-error-on-unmatched-pattern .", "format:check": "oxfmt --check --no-error-on-unmatched-pattern .", @@ -54,17 +47,24 @@ }, "dependencies": {}, "devDependencies": { - "@biomejs/biome": "2.4.3", - "@types/node": "^18.19.70", - "msw": "2.11.2", - "oxfmt": "0.27.0", + "webpack": "^5.97.1", "ts-loader": "^9.5.1", - "typescript": "~5.7.2", "vitest": "^3.2.4", - "webpack": "^5.97.1" + "msw": "2.11.2", + "@types/node": "^18.19.70", + "typescript": "~5.7.2", + "@biomejs/biome": "2.4.3", + "oxfmt": "0.35.0" + }, + "browser": { + "fs": false, + "os": false, + "path": false, + "stream": false }, + "packageManager": "pnpm@10.20.0", "engines": { "node": ">=18.0.0" }, - "packageManager": "pnpm@10.20.0" + "sideEffects": false } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml b/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml index 339da38e3da8..6e4c395107df 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml +++ b/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml @@ -1 +1 @@ -packages: ["."] +packages: ['.'] \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/reference.md b/seed/ts-sdk/simple-api/use-oxfmt/reference.md index d3121dd7b2dd..e69de29bb2d1 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/reference.md +++ b/seed/ts-sdk/simple-api/use-oxfmt/reference.md @@ -1,51 +0,0 @@ -# Reference - -## User - -
client.user.get(id) -> SeedSimpleApi.User -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.user.get("id"); -``` - -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `string` - -
-
- -
-
- -**requestOptions:** `UserClient.RequestOptions` - -
-
-
-
- -
-
-
diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts index 92a7a11f0513..c32dc24af40b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts @@ -2,8 +2,9 @@ import { BearerAuthProvider } from "./auth/BearerAuthProvider.js"; import * as core from "./core/index.js"; +import type { AuthProvider } from "./core/auth/index.js"; import { mergeHeaders } from "./core/headers.js"; -import type * as environments from "./environments.js"; +import * as environments from "./environments.js"; export type BaseClientOptions = { environment: core.Supplier; @@ -37,26 +38,18 @@ export interface BaseRequestOptions { export type NormalizedClientOptions = T & { logging: core.logging.Logger; authProvider?: core.AuthProvider; -}; +} -export type NormalizedClientOptionsWithAuth = - NormalizedClientOptions & { - authProvider: core.AuthProvider; - }; +export type NormalizedClientOptionsWithAuth = NormalizedClientOptions & { + authProvider: core.AuthProvider; +} export function normalizeClientOptions( - options: T, + options: T ): NormalizedClientOptions { const headers = mergeHeaders( - { - "X-Fern-Language": "JavaScript", - "X-Fern-SDK-Name": "@fern/simple-api", - "X-Fern-SDK-Version": "0.0.1", - "User-Agent": "@fern/simple-api/0.0.1", - "X-Fern-Runtime": core.RUNTIME.type, - "X-Fern-Runtime-Version": core.RUNTIME.version, - }, - options?.headers, + { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/simple-api", "X-Fern-SDK-Version": "0.0.1", "User-Agent": "@fern/simple-api/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version }, + options?.headers ); return { @@ -67,7 +60,7 @@ export function normalizeClientOptions( - options: T, + options: T ): NormalizedClientOptionsWithAuth { const normalized = normalizeClientOptions(options) as NormalizedClientOptionsWithAuth; const normalizedWithNoOpAuthProvider = withNoOpAuthProvider(normalized); @@ -76,10 +69,10 @@ export function normalizeClientOptionsWithAuth( - options: NormalizedClientOptions, + options: NormalizedClientOptions ): NormalizedClientOptionsWithAuth { return { ...options, - authProvider: new core.NoOpAuthProvider(), + authProvider: new core.NoOpAuthProvider() }; } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts index 5ed3c9c18ab1..68e367690017 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts @@ -3,11 +3,14 @@ import { UserClient } from "./api/resources/user/client/Client.js"; import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; import { normalizeClientOptionsWithAuth, type NormalizedClientOptionsWithAuth } from "./BaseClient.js"; +import * as core from "./core/index.js"; +import * as environments from "./environments.js"; export declare namespace SeedSimpleApiClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions {} + export interface RequestOptions extends BaseRequestOptions { + } } export class SeedSimpleApiClient { @@ -15,7 +18,10 @@ export class SeedSimpleApiClient { protected _user: UserClient | undefined; constructor(options: SeedSimpleApiClient.Options) { - this._options = normalizeClientOptionsWithAuth(options); + + + this._options = normalizeClientOptionsWithAuth(options); + } public get user(): UserClient { diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts index dd4cd50ede2a..bc8c41bb099e 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts @@ -4,21 +4,26 @@ import type { BaseClientOptions, BaseRequestOptions } from "../../../../BaseClie import { normalizeClientOptionsWithAuth, type NormalizedClientOptionsWithAuth } from "../../../../BaseClient.js"; import * as core from "../../../../core/index.js"; import { mergeHeaders } from "../../../../core/headers.js"; +import * as environments from "../../../../environments.js"; import { handleNonStatusCodeError } from "../../../../errors/handleNonStatusCodeError.js"; import * as errors from "../../../../errors/index.js"; -import type * as SeedSimpleApi from "../../../index.js"; +import * as SeedSimpleApi from "../../../index.js"; export declare namespace UserClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions {} + export interface RequestOptions extends BaseRequestOptions { + } } export class UserClient { protected readonly _options: NormalizedClientOptionsWithAuth; constructor(options: UserClient.Options) { - this._options = normalizeClientOptionsWithAuth(options); + + + this._options = normalizeClientOptionsWithAuth(options); + } /** @@ -32,22 +37,11 @@ export class UserClient { return core.HttpResponsePromise.fromPromise(this.__get(id, requestOptions)); } - private async __get( - id: string, - requestOptions?: UserClient.RequestOptions, - ): Promise> { + private async __get(id: string, requestOptions?: UserClient.RequestOptions): Promise> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); - const _headers: core.Fetcher.Args["headers"] = mergeHeaders( - _authRequest.headers, - this._options?.headers, - requestOptions?.headers, - ); + let _headers: core.Fetcher.Args["headers"] = mergeHeaders(_authRequest.headers, this._options?.headers, requestOptions?.headers); const _response = await core.fetcher({ - url: core.url.join( - (await core.Supplier.get(this._options.baseUrl)) ?? - (await core.Supplier.get(this._options.environment)), - `/users/${core.url.encodePathParam(id)}`, - ), + url: core.url.join(await core.Supplier.get(this._options.baseUrl) ?? await core.Supplier.get(this._options.environment), `/users/${core.url.encodePathParam(id)}`), method: "GET", headers: _headers, queryParameters: requestOptions?.queryParams, @@ -55,7 +49,7 @@ export class UserClient { maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, abortSignal: requestOptions?.abortSignal, fetchFn: this._options?.fetch, - logging: this._options.logging, + logging: this._options.logging }); if (_response.ok) { return { data: _response.body as SeedSimpleApi.User, rawResponse: _response.rawResponse }; @@ -65,7 +59,7 @@ export class UserClient { throw new errors.SeedSimpleApiError({ statusCode: _response.error.statusCode, body: _response.error.body, - rawResponse: _response.rawResponse, + rawResponse: _response.rawResponse }); } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts index cb0ff5c3b541..2234b9cae16d 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts @@ -1 +1 @@ -export {}; +export { }; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts index 63b42583d7c1..e199d99d0963 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts @@ -16,28 +16,27 @@ export class BearerAuthProvider implements core.AuthProvider { return options?.[TOKEN_PARAM] != null; } - public async getAuthRequest({ - endpointMetadata, - }: { - endpointMetadata?: core.EndpointMetadata; - } = {}): Promise { - const token = await core.Supplier.get(this.options[TOKEN_PARAM]); - if (token == null) { - throw new errors.SeedSimpleApiError({ - message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, - }); - } - - return { - headers: { Authorization: `Bearer ${token}` }, - }; + public async getAuthRequest({ endpointMetadata }: { + endpointMetadata?: core.EndpointMetadata; + } = {}): Promise { + + const token = await core.Supplier.get(this.options[TOKEN_PARAM]); + if (token == null) { + throw new errors.SeedSimpleApiError({ + message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, + }); + } + + return { + headers: { Authorization: `Bearer ${token}` }, + }; + } } export namespace BearerAuthProvider { export const AUTH_SCHEME = "bearer" as const; - export const AUTH_CONFIG_ERROR_MESSAGE: string = - `Please provide '${TOKEN_PARAM}' when initializing the client` as const; + export const AUTH_CONFIG_ERROR_MESSAGE: string = `Please provide '${TOKEN_PARAM}' when initializing the client` as const; export type Options = AuthOptions; export type AuthOptions = { [TOKEN_PARAM]: core.Supplier }; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts index 9f845b956392..8d88fe84d06d 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts @@ -1,3 +1,5 @@ + export async function getFetchFn(): Promise { return fetch; } + diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts index 708d55728f2b..5f6162e98085 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts @@ -1,6 +1,7 @@ import { fromJson } from "../json.js"; import { getBinaryResponse } from "./BinaryResponse.js"; + export async function getResponseBody(response: Response, responseType?: string): Promise { switch (responseType) { case "binary-response": @@ -30,9 +31,9 @@ export async function getResponseBody(response: Response, responseType?: string) }, }; } - + return response.body; - + case "text": return await response.text(); } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts index be45c4552a35..cb327036972e 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts @@ -1,4 +1,6 @@ -export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { +export function mergeHeaders( + ...headersArray: (Record | null | undefined)[] +): Record { const result: Record = {}; for (const [key, value] of headersArray diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts index 0955c381185b..aca434160feb 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts @@ -1,10 +1,8 @@ // This file was auto-generated by Fern from our API Definition. export const SeedSimpleApiEnvironment = { - Production: "https://api.example.com", - Staging: "https://staging-api.example.com", -} as const; + Production: "https://api.example.com", + Staging: "https://staging-api.example.com", + } as const; -export type SeedSimpleApiEnvironment = - | typeof SeedSimpleApiEnvironment.Production - | typeof SeedSimpleApiEnvironment.Staging; +export type SeedSimpleApiEnvironment = typeof SeedSimpleApiEnvironment.Production | typeof SeedSimpleApiEnvironment.Staging; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts index e3d83943c4fa..c7ff68ac2c8a 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts @@ -1,6 +1,6 @@ // This file was auto-generated by Fern from our API Definition. -import type * as core from "../core/index.js"; +import * as core from "../core/index.js"; import { toJson } from "../core/json.js"; export class SeedSimpleApiError extends Error { @@ -8,17 +8,12 @@ export class SeedSimpleApiError extends Error { public readonly body?: unknown; public readonly rawResponse?: core.RawResponse; - constructor({ - message, - statusCode, - body, - rawResponse, - }: { - message?: string; - statusCode?: number; - body?: unknown; - rawResponse?: core.RawResponse; - }) { + constructor({ message, statusCode, body, rawResponse }: { + message?: string; + statusCode?: number; + body?: unknown; + rawResponse?: core.RawResponse; + }) { super(buildMessage({ message, statusCode, body })); Object.setPrototypeOf(this, new.target.prototype); if (Error.captureStackTrace) { @@ -32,16 +27,12 @@ export class SeedSimpleApiError extends Error { } } -function buildMessage({ - message, - statusCode, - body, -}: { - message: string | undefined; - statusCode: number | undefined; - body: unknown | undefined; -}): string { - const lines: string[] = []; +function buildMessage({ message, statusCode, body }: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; + }): string { + let lines: string[] = []; if (message != null) { lines.push(message); } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts index d24a21941f05..bc8dce0450df 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts @@ -1,37 +1,27 @@ // This file was auto-generated by Fern from our API Definition. -import type * as core from "../core/index.js"; +import * as core from "../core/index.js"; import * as errors from "./index.js"; -export function handleNonStatusCodeError( - error: core.Fetcher.Error, - rawResponse: core.RawResponse, - method: string, - path: string, -): never { +export function handleNonStatusCodeError(error: core.Fetcher.Error, rawResponse: core.RawResponse, method: string, path: string): never { switch (error.reason) { - case "non-json": - throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - body: error.rawBody, - rawResponse: rawResponse, - }); - case "body-is-null": - throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - rawResponse: rawResponse, - }); - case "timeout": - throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); - case "unknown": - throw new errors.SeedSimpleApiError({ - message: error.errorMessage, - rawResponse: rawResponse, - }); - default: - throw new errors.SeedSimpleApiError({ - message: "Unknown error", - rawResponse: rawResponse, - }); + case "non-json": throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + body: error.rawBody, + rawResponse: rawResponse + }); + case "body-is-null": throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + rawResponse: rawResponse + }); + case "timeout": throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); + case "unknown": throw new errors.SeedSimpleApiError({ + message: error.errorMessage, + rawResponse: rawResponse + }); + default: throw new errors.SeedSimpleApiError({ + message: "Unknown error", + rawResponse: rawResponse + }); } } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts index 7f5e031c8396..6927092387b6 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts @@ -1,13 +1,14 @@ + /** - * This is a custom test file, if you wish to add more tests - * to your SDK. - * Be sure to mark this file in `.fernignore`. - * - * If you include example requests/responses in your fern definition, - * you will have tests automatically generated for you. - */ +* This is a custom test file, if you wish to add more tests +* to your SDK. +* Be sure to mark this file in `.fernignore`. +* +* If you include example requests/responses in your fern definition, +* you will have tests automatically generated for you. +*/ describe("test", () => { it("default", () => { expect(true).toBe(true); }); -}); +}); \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts index 3bd2e38ca2ca..e2a8edf51c0b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts @@ -1,3 +1,4 @@ + import { expect } from "vitest"; interface CustomMatchers { @@ -52,8 +53,7 @@ expect.extend({ if (pass) { return { - message: () => - `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { @@ -66,25 +66,17 @@ expect.extend({ if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + - ": expected " + - this.utils.printExpected(expected) + - " but got " + - this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + - actualType + - " to contain " + - this.utils.printExpected(expectedHeaders) + - "\n\n" + - messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } }, }); + diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json b/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json index a477df47920c..b8eeea994bfa 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json @@ -4,8 +4,8 @@ "outDir": null, "rootDir": "..", "baseUrl": "..", - "types": ["vitest/globals"] + "types": ["vitest/globals"] }, - "include": ["../src", "../tests"], + "include": ["../src","../tests"], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts index 6c17624228bb..4632efa6f501 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts @@ -76,6 +76,8 @@ describe("Test fetcherImpl", () => { } }); + + it("should receive file as stream", async () => { const url = "https://httpbin.org/post/file"; const mockArgs: Fetcher.Args = { @@ -259,4 +261,5 @@ describe("Test fetcherImpl", () => { expect(body.bodyUsed).toBe(true); } }); + }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts index ad6be7fc2c9b..8ef1cf11c85b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts @@ -73,6 +73,7 @@ describe("Test getResponseBody", () => { } }); + it("should handle streaming response type", async () => { const encoder = new TextEncoder(); const testData = "test stream data"; @@ -94,4 +95,5 @@ describe("Test getResponseBody", () => { const streamContent = decoder.decode(value); expect(streamContent).toBe(testData); }); + }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts index 56549d63fd4b..7de9e90414b5 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts @@ -1,5 +1,5 @@ import { makeRequest } from "../../../src/core/fetcher/makeRequest"; -import type { Mock } from "vitest"; +import { Mock } from "vitest"; describe("Test makeRequest", () => { const mockPostUrl = "https://httpbin.org/post"; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts index ba577dc38620..01519cf17132 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts @@ -1,5 +1,5 @@ import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; -import type { Mock, MockInstance } from "vitest"; +import { Mock, MockInstance } from "vitest"; describe("requestWithRetries", () => { let mockFetch: Mock; @@ -13,20 +13,20 @@ describe("requestWithRetries", () => { Math.random = vi.fn(() => 0.5); vi.useFakeTimers({ - toFake: [ - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "Date", - "performance", - "requestAnimationFrame", - "cancelAnimationFrame", - "requestIdleCallback", - "cancelIdleCallback", - ], + toFake: [ + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "Date", + "performance", + "requestAnimationFrame", + "cancelAnimationFrame", + "requestIdleCallback", + "cancelIdleCallback" + ] }); }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts index f5c1d699da35..8d74497c00e3 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts @@ -4,18 +4,28 @@ import { SeedSimpleApiClient } from "../../src/Client"; import { mockServerPool } from "../mock-server/MockServerPool"; describe("UserClient", () => { + test("get", async () => { const server = mockServerPool.createServer(); - const client = new SeedSimpleApiClient({ maxRetries: 0, token: "test", environment: server.baseUrl }); + const client = new SeedSimpleApiClient({ "maxRetries" : 0 , "token" : "test" , "environment" : server.baseUrl }); + + const rawResponseBody = { "id" : "id" , "name" : "name" , "email" : "email" }; + server + .mockEndpoint() + .get("/users/id").respondWith() + .statusCode(200).jsonBody(rawResponseBody) + .build(); - const rawResponseBody = { id: "id", name: "name", email: "email" }; - server.mockEndpoint().get("/users/id").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); - - const response = await client.user.get("id"); - expect(response).toEqual({ - id: "id", - name: "name", - email: "email", - }); + + + const response = await client.user.get("id"); + expect(response).toEqual({ + id: "id", + name: "name", + email: "email" +}); + + }); + }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json index d7627675de20..e48f3fe29c34 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json @@ -13,6 +13,8 @@ "isolatedModules": true, "isolatedDeclarations": true }, - "include": ["src"], + "include": [ + "src" + ], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json index 5c11446f5984..baf12c360e23 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json @@ -4,6 +4,8 @@ "module": "CommonJS", "outDir": "dist/cjs" }, - "include": ["src"], + "include": [ + "src" + ], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json index 6ce909748b2c..5f8ac6fcca67 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json @@ -5,6 +5,8 @@ "outDir": "dist/esm", "verbatimModuleSyntax": true }, - "include": ["src"], + "include": [ + "src" + ], "exclude": [] -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json index d77fdf00d259..ad43234b056f 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json @@ -1,3 +1,3 @@ { "extends": "./tsconfig.cjs.json" -} +} \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts b/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts index ba2ec4f9d45a..e21d088aa14c 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts +++ b/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts @@ -1,28 +1,31 @@ -import { defineConfig } from "vitest/config"; -export default defineConfig({ - test: { - projects: [ - { + + + import { defineConfig } from "vitest/config"; + export default defineConfig({ test: { - globals: true, - name: "unit", - environment: "node", - root: "./tests", - include: ["**/*.test.{js,ts,jsx,tsx}"], - exclude: ["wire/**"], - setupFiles: ["./setup.ts"], - }, - }, - { - test: { - globals: true, - name: "wire", - environment: "node", - root: "./tests/wire", - setupFiles: ["../setup.ts", "../mock-server/setup.ts"], - }, - }, - ], - passWithNoTests: true, - }, -}); + projects: [ + { + test: { + globals: true, + name: "unit", + environment: "node", + root: "./tests", + include: ["**/*.test.{js,ts,jsx,tsx}"], + exclude: ["wire/**"], + setupFiles: ["./setup.ts"] + } + }, + { + test: { + globals: true, + name: "wire", + environment: "node", + root: "./tests/wire", + setupFiles: ["../setup.ts", "../mock-server/setup.ts"] + } + }, + ], + passWithNoTests: true + } + }); + \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxlint/README.md b/seed/ts-sdk/simple-api/use-oxlint/README.md index 987b93560859..e69de29bb2d1 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/README.md +++ b/seed/ts-sdk/simple-api/use-oxlint/README.md @@ -1,254 +0,0 @@ -# Seed TypeScript Library - -[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) -[![npm shield](https://img.shields.io/npm/v/@fern/simple-api)](https://www.npmjs.com/package/@fern/simple-api) - -The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. - -## Table of Contents - -- [Installation](#installation) -- [Reference](#reference) -- [Usage](#usage) -- [Exception Handling](#exception-handling) -- [Advanced](#advanced) - - [Additional Headers](#additional-headers) - - [Additional Query String Parameters](#additional-query-string-parameters) - - [Retries](#retries) - - [Timeouts](#timeouts) - - [Aborting Requests](#aborting-requests) - - [Access Raw Response Data](#access-raw-response-data) - - [Logging](#logging) - - [Runtime Compatibility](#runtime-compatibility) -- [Contributing](#contributing) - -## Installation - -```sh -npm i -s @fern/simple-api -``` - -## Reference - -A full reference for this library is available [here](./reference.md). - -## Usage - -Instantiate and use the client with the following: - -```typescript -import { SeedSimpleApiClient, SeedSimpleApiEnvironment } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ environment: SeedSimpleApiEnvironment.Production, token: "YOUR_TOKEN" }); -await client.user.get("id"); -``` - -## Exception Handling - -When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error -will be thrown. - -```typescript -import { SeedSimpleApiError } from "@fern/simple-api"; - -try { - await client.user.get(...); -} catch (err) { - if (err instanceof SeedSimpleApiError) { - console.log(err.statusCode); - console.log(err.message); - console.log(err.body); - console.log(err.rawResponse); - } -} -``` - -## Advanced - -### Additional Headers - -If you would like to send additional headers as part of the request, use the `headers` request option. - -```typescript -import { SeedSimpleApiClient } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - headers: { - 'X-Custom-Header': 'custom value' - } -}); - -const response = await client.user.get(..., { - headers: { - 'X-Custom-Header': 'custom value' - } -}); -``` - -### Additional Query String Parameters - -If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. - -```typescript -const response = await client.user.get(..., { - queryParams: { - 'customQueryParamKey': 'custom query param value' - } -}); -``` - -### Retries - -The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long -as the request is deemed retryable and the number of retry attempts has not grown larger than the configured -retry limit (default: 2). - -A request is deemed retryable when any of the following HTTP status codes is returned: - -- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) -- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) -- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) - -Use the `maxRetries` request option to configure this behavior. - -```typescript -const response = await client.user.get(..., { - maxRetries: 0 // override maxRetries at the request level -}); -``` - -### Timeouts - -The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. - -```typescript -const response = await client.user.get(..., { - timeoutInSeconds: 30 // override timeout to 30s -}); -``` - -### Aborting Requests - -The SDK allows users to abort requests at any point by passing in an abort signal. - -```typescript -const controller = new AbortController(); -const response = await client.user.get(..., { - abortSignal: controller.signal -}); -controller.abort(); // aborts the request -``` - -### Access Raw Response Data - -The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. -The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. - -```typescript -const { data, rawResponse } = await client.user.get(...).withRawResponse(); - -console.log(data); -console.log(rawResponse.headers['X-My-Header']); -``` - -### Logging - -The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. - -```typescript -import { SeedSimpleApiClient, logging } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - logging: { - level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info - logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger - silent: false, // defaults to true, set to false to enable logging - } -}); -``` -The `logging` object can have the following properties: -- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. -- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. -- `silent`: Whether to silence the logger. Defaults to `true`. - -The `level` property can be one of the following values: -- `logging.LogLevel.Debug` -- `logging.LogLevel.Info` -- `logging.LogLevel.Warn` -- `logging.LogLevel.Error` - -To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. - -
-Custom logger examples - -Here's an example using the popular `winston` logging library. -```ts -import winston from 'winston'; - -const winstonLogger = winston.createLogger({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => winstonLogger.debug(msg, ...args), - info: (msg, ...args) => winstonLogger.info(msg, ...args), - warn: (msg, ...args) => winstonLogger.warn(msg, ...args), - error: (msg, ...args) => winstonLogger.error(msg, ...args), -}; -``` - -Here's an example using the popular `pino` logging library. - -```ts -import pino from 'pino'; - -const pinoLogger = pino({...}); - -const logger: logging.ILogger = { - debug: (msg, ...args) => pinoLogger.debug(args, msg), - info: (msg, ...args) => pinoLogger.info(args, msg), - warn: (msg, ...args) => pinoLogger.warn(args, msg), - error: (msg, ...args) => pinoLogger.error(args, msg), -}; -``` -
- - -### Runtime Compatibility - - -The SDK works in the following runtimes: - - - -- Node.js 18+ -- Vercel -- Cloudflare Workers -- Deno v1.25+ -- Bun 1.0+ -- React Native - -### Customizing Fetch Client - -The SDK provides a way for you to customize the underlying HTTP client / Fetch function. If you're running in an -unsupported environment, this provides a way for you to break glass and ensure the SDK works. - -```typescript -import { SeedSimpleApiClient } from "@fern/simple-api"; - -const client = new SeedSimpleApiClient({ - ... - fetcher: // provide your implementation here -}); -``` - -## Contributing - -While we value open-source contributions to this SDK, this library is generated programmatically. -Additions made directly to this library would have to be moved over to our generation code, -otherwise they would be overwritten upon the next generated release. Feel free to open a PR as -a proof of concept, but know that we will not be able to merge it as-is. We suggest opening -an issue first to discuss with us! - -On the other hand, contributions to the README are always very welcome! \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxlint/package.json b/seed/ts-sdk/simple-api/use-oxlint/package.json index 0cb6e0cdf84d..4864d72d6560 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/package.json +++ b/seed/ts-sdk/simple-api/use-oxlint/package.json @@ -54,8 +54,8 @@ "@types/node": "^18.19.70", "typescript": "~5.7.2", "@biomejs/biome": "2.4.3", - "oxlint": "1.42.0", - "oxlint-tsgolint": "0.11.4" + "oxlint": "1.50.0", + "oxlint-tsgolint": "0.14.2" }, "browser": { "fs": false, diff --git a/seed/ts-sdk/simple-api/use-oxlint/reference.md b/seed/ts-sdk/simple-api/use-oxlint/reference.md index 4c28e3b1b8d7..e69de29bb2d1 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/reference.md +++ b/seed/ts-sdk/simple-api/use-oxlint/reference.md @@ -1,50 +0,0 @@ -# Reference -## User -
client.user.get(id) -> SeedSimpleApi.User -
-
- -#### 🔌 Usage - -
-
- -
-
- -```typescript -await client.user.get("id"); - -``` -
-
-
-
- -#### ⚙️ Parameters - -
-
- -
-
- -**id:** `string` - -
-
- -
-
- -**requestOptions:** `UserClient.RequestOptions` - -
-
-
-
- - -
-
-
diff --git a/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js b/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js index 561ec17d82d2..dc1df1cbbacb 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js +++ b/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js @@ -56,7 +56,7 @@ async function updateFileContents(file) { // Handle dynamic imports (yield import, await import, regular import()) const dynamicRegex = new RegExp( - `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, + `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, "g", ); newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); diff --git a/seed/ts-sdk/simple-api/use-oxlint/tests/setup.ts b/seed/ts-sdk/simple-api/use-oxlint/tests/setup.ts index 201f5fbc968f..a5651f81ba10 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/tests/setup.ts +++ b/seed/ts-sdk/simple-api/use-oxlint/tests/setup.ts @@ -52,37 +52,27 @@ expect.extend({ if (pass) { return { - message: () => - "expected " + actualType + " not to contain " + this.utils.printExpected(expectedHeaders), + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { const messages: string[] = []; if (missingHeaders.length > 0) { - messages.push("Missing headers: " + this.utils.printExpected(missingHeaders.join(", "))); + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); } if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + - ": expected " + - this.utils.printExpected(expected) + - " but got " + - this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + - actualType + - " to contain " + - this.utils.printExpected(expectedHeaders) + - "\n\n" + - messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } From ef422021f4372693ab8fb097ff138a0aa547add5 Mon Sep 17 00:00:00 2001 From: patrick thornton <70873350+patrickthornton@users.noreply.github.com> Date: Tue, 24 Feb 2026 01:49:08 -0500 Subject: [PATCH 5/7] fix(python): handle `filename` being different from `exported_filename` (#12627) * client-filename fix, release 4.59.1 * [proposal] generated client.py is a barebones child class of the base --------- Co-authored-by: Aditya Arolkar --- generators/python/sdk/versions.yml | 11 ++ .../client_generator/generated_root_client.py | 5 +- .../client_generator/root_client_generator.py | 22 +++- .../generators/sdk/sdk_generator.py | 102 +++++++++++++++++- .../examples/client-filename/.fernignore | 1 - .../client-filename/src/seed/client.py | 58 ++++++++++ seed/python-sdk/seed.yml | 1 - 7 files changed, 193 insertions(+), 7 deletions(-) delete mode 100644 seed/python-sdk/examples/client-filename/.fernignore create mode 100644 seed/python-sdk/examples/client-filename/src/seed/client.py diff --git a/generators/python/sdk/versions.yml b/generators/python/sdk/versions.yml index d9bad59755bf..bc146d1496d3 100644 --- a/generators/python/sdk/versions.yml +++ b/generators/python/sdk/versions.yml @@ -1,11 +1,22 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json # For unreleased changes, use unreleased.yml +- version: 4.59.2 + changelogEntry: + - summary: | + Generate an appropriate `client.py` file when `client.exported_filename` differs from `client.filename`. + Previously, the `__init__.py` would import from the exported filename (e.g., `client.py`) but + that file was never created, causing mypy `import-not-found` errors. + type: fix + createdAt: "2026-02-20" + irVersion: 65 + - version: 4.59.1 changelogEntry: - summary: Fix wire test imports to respect package_name custom config, preventing import errors when users specify custom package names type: fix createdAt: "2026-02-23" irVersion: 65 + - version: 4.59.0 changelogEntry: - summary: | diff --git a/generators/python/src/fern_python/generators/sdk/client_generator/generated_root_client.py b/generators/python/src/fern_python/generators/sdk/client_generator/generated_root_client.py index c2edaeb6960e..db9fb7d4bab9 100644 --- a/generators/python/src/fern_python/generators/sdk/client_generator/generated_root_client.py +++ b/generators/python/src/fern_python/generators/sdk/client_generator/generated_root_client.py @@ -1,5 +1,5 @@ -from dataclasses import dataclass -from typing import List +from dataclasses import dataclass, field +from typing import List, Optional from fern_python.codegen import AST from fern_python.generators.sdk.core_utilities.client_wrapper_generator import ( @@ -11,6 +11,7 @@ class RootClient: class_reference: AST.ClassReference parameters: List[ConstructorParameter] + init_parameters: Optional[List[ConstructorParameter]] = field(default=None) @dataclass diff --git a/generators/python/src/fern_python/generators/sdk/client_generator/root_client_generator.py b/generators/python/src/fern_python/generators/sdk/client_generator/root_client_generator.py index a33c4990abe4..15308e6660fa 100644 --- a/generators/python/src/fern_python/generators/sdk/client_generator/root_client_generator.py +++ b/generators/python/src/fern_python/generators/sdk/client_generator/root_client_generator.py @@ -181,6 +181,8 @@ def __init__( # like os.getenv("..."). use_kwargs_snippets=(has_inferred_auth or is_oauth_client_credentials), base_url_example_value=base_url_example_value, + sync_init_parameters=self._get_constructor_parameters(is_async=False), + async_init_parameters=self._get_constructor_parameters(is_async=True), ) self._generated_root_client = root_client_builder.build() @@ -1565,11 +1567,19 @@ def __init__( oauth_token_override: bool = False, use_kwargs_snippets: bool = False, base_url_example_value: Optional[AST.Expression] = None, + sync_init_parameters: Optional[Sequence[ConstructorParameter]] = None, + async_init_parameters: Optional[Sequence[ConstructorParameter]] = None, ): self._module_path = module_path self._class_name = class_name self._async_class_name = async_class_name self._constructor_parameters: List[ConstructorParameter] = list(constructor_parameters) + self._sync_init_parameters: Optional[List[ConstructorParameter]] = ( + list(sync_init_parameters) if sync_init_parameters is not None else None + ) + self._async_init_parameters: Optional[List[ConstructorParameter]] = ( + list(async_init_parameters) if async_init_parameters is not None else None + ) self._oauth_token_override = oauth_token_override self._use_kwargs_snippets = use_kwargs_snippets self._base_url_example_value = base_url_example_value @@ -1726,7 +1736,15 @@ def build_default_snippet_kwargs() -> List[typing.Tuple[str, AST.Expression]]: return GeneratedRootClient( async_instantiations=async_instantiations, - async_client=RootClient(class_reference=async_class_reference, parameters=self._constructor_parameters), + async_client=RootClient( + class_reference=async_class_reference, + parameters=self._constructor_parameters, + init_parameters=self._async_init_parameters, + ), sync_instantiations=sync_instantiations, - sync_client=RootClient(class_reference=sync_class_reference, parameters=self._constructor_parameters), + sync_client=RootClient( + class_reference=sync_class_reference, + parameters=self._constructor_parameters, + init_parameters=self._sync_init_parameters, + ), ) diff --git a/generators/python/src/fern_python/generators/sdk/sdk_generator.py b/generators/python/src/fern_python/generators/sdk/sdk_generator.py index 45ffac02674a..fdef3d3e2d06 100644 --- a/generators/python/src/fern_python/generators/sdk/sdk_generator.py +++ b/generators/python/src/fern_python/generators/sdk/sdk_generator.py @@ -3,7 +3,7 @@ from typing import Literal, Optional, Sequence, Tuple, Union, cast from .client_generator.client_generator import ClientGenerator -from .client_generator.generated_root_client import GeneratedRootClient +from .client_generator.generated_root_client import GeneratedRootClient, RootClient from .client_generator.inferred_auth_token_provider_generator import InferredAuthTokenProviderGenerator from .client_generator.oauth_token_provider_generator import OAuthTokenProviderGenerator from .client_generator.raw_client_generator import RawClientGenerator @@ -250,6 +250,17 @@ def run( oauth_scheme=oauth_scheme, ) + # If exported_filename differs from filename, generate an inheritance-based wrapper + actual_filename = custom_config.client_filename or custom_config.client.filename + if custom_config.client.exported_filename != actual_filename: + self._generate_exported_client_wrapper( + context=context, + custom_config=custom_config, + project=project, + generated_root_client=generated_root_client, + generator_exec_wrapper=generator_exec_wrapper, + ) + # Since you can customize the client export, we handle it here to capture the generated # and non-generated cases. If we were to base this off exporting the class declaration # we would have to handle the case where the exported client is not generated. @@ -558,6 +569,95 @@ def _generate_root_client( project.write_source_file(source_file=raw_client_source_file, filepath=raw_client_filepath) return generated_root_client + def _generate_exported_client_wrapper( + self, + context: SdkGeneratorContext, + custom_config: SDKCustomConfig, + project: Project, + generated_root_client: GeneratedRootClient, + generator_exec_wrapper: GeneratorExecWrapper, + ) -> None: + exported_module = custom_config.client.exported_filename.removesuffix(".py") + exported_sync_class = context.get_class_name_for_exported_root_client() + exported_async_class = "Async" + exported_sync_class + + filepath = Filepath( + directories=(), + file=Filepath.FilepathPart(module_name=exported_module), + ) + source_file = context.source_file_factory.create( + project=project, filepath=filepath, generator_exec_wrapper=generator_exec_wrapper + ) + + generated_filepath = context.get_filepath_for_generated_root_client() + generated_sync_name = context.get_class_name_for_generated_root_client() + generated_async_name = "Async" + generated_sync_name + + sync_base_class_ref = AST.ClassReference( + import_=AST.ReferenceImport( + module=generated_filepath.to_module(), + named_import=generated_sync_name, + ), + qualified_name_excluding_import=(), + ) + async_base_class_ref = AST.ClassReference( + import_=AST.ReferenceImport( + module=generated_filepath.to_module(), + named_import=generated_async_name, + ), + qualified_name_excluding_import=(), + ) + + sync_class = self._create_wrapper_class_declaration( + class_name=exported_sync_class, + base_class_ref=sync_base_class_ref, + root_client=generated_root_client.sync_client, + ) + async_class = self._create_wrapper_class_declaration( + class_name=exported_async_class, + base_class_ref=async_base_class_ref, + root_client=generated_root_client.async_client, + ) + + source_file.add_class_declaration(declaration=sync_class, should_export=True) + source_file.add_class_declaration(declaration=async_class, should_export=True) + + project.write_source_file(source_file=source_file, filepath=filepath) + + @staticmethod + def _create_wrapper_class_declaration( + *, + class_name: str, + base_class_ref: AST.ClassReference, + root_client: "RootClient", + ) -> AST.ClassDeclaration: + params = root_client.init_parameters if root_client.init_parameters is not None else root_client.parameters + + named_params = [ + AST.NamedFunctionParameter( + name=param.constructor_parameter_name, + type_hint=param.type_hint, + initializer=param.initializer, + ) + for param in params + ] + + def write_super_init(writer: AST.NodeWriter) -> None: + writer.write_line("super().__init__(") + with writer.indent(): + for param in params: + writer.write_line(f"{param.constructor_parameter_name}={param.constructor_parameter_name},") + writer.write_line(")") + + return AST.ClassDeclaration( + name=class_name, + extends=[base_class_ref], + constructor=AST.ClassConstructor( + signature=AST.FunctionSignature(named_parameters=named_params), + body=AST.CodeWriter(write_super_init), + ), + ) + def _generate_subpackage_client( self, context: SdkGeneratorContext, diff --git a/seed/python-sdk/examples/client-filename/.fernignore b/seed/python-sdk/examples/client-filename/.fernignore deleted file mode 100644 index 158580aa1f4f..000000000000 --- a/seed/python-sdk/examples/client-filename/.fernignore +++ /dev/null @@ -1 +0,0 @@ -src/seed/client.py \ No newline at end of file diff --git a/seed/python-sdk/examples/client-filename/src/seed/client.py b/seed/python-sdk/examples/client-filename/src/seed/client.py new file mode 100644 index 000000000000..0e59bcdc68e1 --- /dev/null +++ b/seed/python-sdk/examples/client-filename/src/seed/client.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx +from .base_client import AsyncBaseSeedExhaustive, BaseSeedExhaustive +from .core.logging import LogConfig, Logger +from .environment import SeedExhaustiveEnvironment + + +class SeedExhaustive(BaseSeedExhaustive): + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: typing.Optional[SeedExhaustiveEnvironment] = None, + token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + super().__init__( + base_url=base_url, + environment=environment, + token=token, + headers=headers, + timeout=timeout, + follow_redirects=follow_redirects, + httpx_client=httpx_client, + logging=logging, + ) + + +class AsyncSeedExhaustive(AsyncBaseSeedExhaustive): + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: typing.Optional[SeedExhaustiveEnvironment] = None, + token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + super().__init__( + base_url=base_url, + environment=environment, + token=token, + headers=headers, + timeout=timeout, + follow_redirects=follow_redirects, + httpx_client=httpx_client, + logging=logging, + ) diff --git a/seed/python-sdk/seed.yml b/seed/python-sdk/seed.yml index bdaae9b5694d..4f479f8fa0f7 100644 --- a/seed/python-sdk/seed.yml +++ b/seed/python-sdk/seed.yml @@ -424,7 +424,6 @@ scripts: allowedFailures: - any-auth - endpoint-security-auth - - examples:client-filename - examples:legacy-wire-tests - exhaustive:additional_init_exports - exhaustive:deps_with_min_python_version From 8a15301c07af4c3b93f351d810b58c11d3e0fd16 Mon Sep 17 00:00:00 2001 From: Fern Support <126544928+fern-support@users.noreply.github.com> Date: Tue, 24 Feb 2026 03:09:20 -0500 Subject: [PATCH 6/7] chore(typescript): update ts-sdk seed (#12706) Co-authored-by: patrickthornton --- .../form-data-utils/formDataWrapper.test.ts | 2 +- .../no-linter-and-formatter/tests/setup.ts | 9 +- .../simple-api/no-scripts/tests/setup.ts | 9 +- .../simple-api/use-oxc/.fern/metadata.json | 18 +- .../use-oxc/.github/workflows/ci.yml | 54 ++-- .../ts-sdk/simple-api/use-oxc/CONTRIBUTING.md | 3 + seed/ts-sdk/simple-api/use-oxc/README.md | 255 ++++++++++++++++++ seed/ts-sdk/simple-api/use-oxc/package.json | 40 +-- .../simple-api/use-oxc/pnpm-workspace.yaml | 2 +- seed/ts-sdk/simple-api/use-oxc/reference.md | 51 ++++ .../use-oxc/scripts/rename-to-esm-files.js | 2 +- .../simple-api/use-oxc/src/BaseClient.ts | 28 +- seed/ts-sdk/simple-api/use-oxc/src/Client.ts | 8 +- .../src/api/resources/user/client/Client.ts | 29 +- .../src/api/resources/user/client/index.ts | 2 +- .../use-oxc/src/auth/BearerAuthProvider.ts | 33 +-- .../use-oxc/src/core/fetcher/getFetchFn.ts | 2 - .../src/core/fetcher/getResponseBody.ts | 5 +- .../simple-api/use-oxc/src/core/headers.ts | 4 +- .../simple-api/use-oxc/src/environments.ts | 10 +- .../use-oxc/src/errors/SeedSimpleApiError.ts | 31 ++- .../src/errors/handleNonStatusCodeError.ts | 48 ++-- .../simple-api/use-oxc/tests/custom.test.ts | 17 +- seed/ts-sdk/simple-api/use-oxc/tests/setup.ts | 2 - .../simple-api/use-oxc/tests/tsconfig.json | 6 +- .../tests/unit/fetcher/Fetcher.test.ts | 3 - .../unit/fetcher/getResponseBody.test.ts | 2 - .../unit/fetcher/requestWithRetries.test.ts | 28 +- .../use-oxc/tests/wire/user.test.ts | 30 +-- .../simple-api/use-oxc/tsconfig.base.json | 6 +- .../simple-api/use-oxc/tsconfig.cjs.json | 6 +- .../simple-api/use-oxc/tsconfig.esm.json | 6 +- seed/ts-sdk/simple-api/use-oxc/tsconfig.json | 2 +- .../simple-api/use-oxc/vitest.config.mts | 57 ++-- .../simple-api/use-oxfmt/.fern/metadata.json | 16 +- .../use-oxfmt/.github/workflows/ci.yml | 54 ++-- .../simple-api/use-oxfmt/CONTRIBUTING.md | 3 + seed/ts-sdk/simple-api/use-oxfmt/README.md | 255 ++++++++++++++++++ seed/ts-sdk/simple-api/use-oxfmt/package.json | 40 +-- .../simple-api/use-oxfmt/pnpm-workspace.yaml | 2 +- seed/ts-sdk/simple-api/use-oxfmt/reference.md | 51 ++++ .../simple-api/use-oxfmt/src/BaseClient.ts | 31 ++- .../ts-sdk/simple-api/use-oxfmt/src/Client.ts | 10 +- .../src/api/resources/user/client/Client.ts | 32 ++- .../src/api/resources/user/client/index.ts | 2 +- .../use-oxfmt/src/auth/BearerAuthProvider.ts | 33 +-- .../use-oxfmt/src/core/fetcher/getFetchFn.ts | 2 - .../src/core/fetcher/getResponseBody.ts | 5 +- .../simple-api/use-oxfmt/src/core/headers.ts | 4 +- .../simple-api/use-oxfmt/src/environments.ts | 10 +- .../src/errors/SeedSimpleApiError.ts | 35 ++- .../src/errors/handleNonStatusCodeError.ts | 50 ++-- .../simple-api/use-oxfmt/tests/custom.test.ts | 17 +- .../simple-api/use-oxfmt/tests/setup.ts | 2 - .../simple-api/use-oxfmt/tests/tsconfig.json | 6 +- .../tests/unit/fetcher/Fetcher.test.ts | 3 - .../unit/fetcher/getResponseBody.test.ts | 2 - .../tests/unit/fetcher/makeRequest.test.ts | 2 +- .../unit/fetcher/requestWithRetries.test.ts | 30 +-- .../use-oxfmt/tests/wire/user.test.ts | 30 +-- .../simple-api/use-oxfmt/tsconfig.base.json | 6 +- .../simple-api/use-oxfmt/tsconfig.cjs.json | 6 +- .../simple-api/use-oxfmt/tsconfig.esm.json | 6 +- .../ts-sdk/simple-api/use-oxfmt/tsconfig.json | 2 +- .../simple-api/use-oxfmt/vitest.config.mts | 57 ++-- seed/ts-sdk/simple-api/use-oxlint/README.md | 254 +++++++++++++++++ .../ts-sdk/simple-api/use-oxlint/reference.md | 50 ++++ .../use-oxlint/scripts/rename-to-esm-files.js | 2 +- .../use-prettier-no-linter/tests/setup.ts | 18 +- .../simple-api/use-prettier/tests/setup.ts | 16 +- 70 files changed, 1428 insertions(+), 526 deletions(-) diff --git a/seed/ts-sdk/file-upload/form-data-node16/tests/unit/form-data-utils/formDataWrapper.test.ts b/seed/ts-sdk/file-upload/form-data-node16/tests/unit/form-data-utils/formDataWrapper.test.ts index 28c9f9bba385..0822f91a5f75 100644 --- a/seed/ts-sdk/file-upload/form-data-node16/tests/unit/form-data-utils/formDataWrapper.test.ts +++ b/seed/ts-sdk/file-upload/form-data-node16/tests/unit/form-data-utils/formDataWrapper.test.ts @@ -92,7 +92,7 @@ describe("CrossPlatformFormData", () => { for await (const chunk of request.body) { data += decoder.decode(chunk); } - expect(data).toContain(`Content-Disposition: form-data; name=\"file\"; filename=\"${expectedFileName}\"`); + expect(data).toContain(`Content-Disposition: form-data; name="file"; filename="${expectedFileName}"`); }); }); diff --git a/seed/ts-sdk/simple-api/no-linter-and-formatter/tests/setup.ts b/seed/ts-sdk/simple-api/no-linter-and-formatter/tests/setup.ts index f29a177d96be..e2a8edf51c0b 100644 --- a/seed/ts-sdk/simple-api/no-linter-and-formatter/tests/setup.ts +++ b/seed/ts-sdk/simple-api/no-linter-and-formatter/tests/setup.ts @@ -53,29 +53,30 @@ expect.extend({ if (pass) { return { - message: () => "expected " + actualType + " not to contain " + this.utils.printExpected(expectedHeaders), + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { const messages: string[] = []; if (missingHeaders.length > 0) { - messages.push("Missing headers: " + this.utils.printExpected(missingHeaders.join(", "))); + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); } if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + ": expected " + this.utils.printExpected(expected) + " but got " + this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + actualType + " to contain " + this.utils.printExpected(expectedHeaders) + "\n\n" + messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } }, }); + diff --git a/seed/ts-sdk/simple-api/no-scripts/tests/setup.ts b/seed/ts-sdk/simple-api/no-scripts/tests/setup.ts index f29a177d96be..e2a8edf51c0b 100644 --- a/seed/ts-sdk/simple-api/no-scripts/tests/setup.ts +++ b/seed/ts-sdk/simple-api/no-scripts/tests/setup.ts @@ -53,29 +53,30 @@ expect.extend({ if (pass) { return { - message: () => "expected " + actualType + " not to contain " + this.utils.printExpected(expectedHeaders), + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { const messages: string[] = []; if (missingHeaders.length > 0) { - messages.push("Missing headers: " + this.utils.printExpected(missingHeaders.join(", "))); + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); } if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + ": expected " + this.utils.printExpected(expected) + " but got " + this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + actualType + " to contain " + this.utils.printExpected(expectedHeaders) + "\n\n" + messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } }, }); + diff --git a/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json b/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json index 592db7f1c3b1..66905e17f541 100644 --- a/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json +++ b/seed/ts-sdk/simple-api/use-oxc/.fern/metadata.json @@ -1,10 +1,10 @@ { - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "latest", - "generatorConfig": { - "linter": "oxlint", - "formatter": "oxfmt" - }, - "sdkVersion": "0.0.1" -} \ No newline at end of file + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-typescript-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "linter": "oxlint", + "formatter": "oxfmt" + }, + "sdkVersion": "0.0.1" +} diff --git a/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml b/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml index a98d4d00ff0e..f270d341b2c2 100644 --- a/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml +++ b/seed/ts-sdk/simple-api/use-oxc/.github/workflows/ci.yml @@ -3,40 +3,40 @@ name: ci on: [push] jobs: - compile: - runs-on: ubuntu-latest + compile: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 + - name: Set up node + uses: actions/setup-node@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Compile - run: pnpm build + - name: Compile + run: pnpm build - test: - runs-on: ubuntu-latest + test: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 - - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Set up node + uses: actions/setup-node@v6 - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Test - run: pnpm test + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Test + run: pnpm test diff --git a/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md b/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md index fe5bc2f77e0b..ac478f0bb42e 100644 --- a/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md +++ b/seed/ts-sdk/simple-api/use-oxc/CONTRIBUTING.md @@ -34,6 +34,7 @@ pnpm test ``` Run specific test types: + - `pnpm test:unit` - Run unit tests - `pnpm test:wire` - Run wire/integration tests @@ -66,6 +67,7 @@ pnpm run check:fix ### Generated Files The following directories contain generated code: + - `src/api/` - API client classes and types - `src/serialization/` - Serialization/deserialization logic - Most TypeScript files in `src/` @@ -96,6 +98,7 @@ If you want to change how code is generated for all users of this SDK: 4. Submit a pull request with your changes to the generator This approach is best for: + - Bug fixes in generated code - New features that would benefit all users - Improvements to code generation patterns diff --git a/seed/ts-sdk/simple-api/use-oxc/README.md b/seed/ts-sdk/simple-api/use-oxc/README.md index e69de29bb2d1..b31eac333998 100644 --- a/seed/ts-sdk/simple-api/use-oxc/README.md +++ b/seed/ts-sdk/simple-api/use-oxc/README.md @@ -0,0 +1,255 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) +[![npm shield](https://img.shields.io/npm/v/@fern/simple-api)](https://www.npmjs.com/package/@fern/simple-api) + +The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Aborting Requests](#aborting-requests) + - [Access Raw Response Data](#access-raw-response-data) + - [Logging](#logging) + - [Runtime Compatibility](#runtime-compatibility) +- [Contributing](#contributing) + +## Installation + +```sh +npm i -s @fern/simple-api +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedSimpleApiClient, SeedSimpleApiEnvironment } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ environment: SeedSimpleApiEnvironment.Production, token: "YOUR_TOKEN" }); +await client.user.get("id"); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedSimpleApiError } from "@fern/simple-api"; + +try { + await client.user.get(...); +} catch (err) { + if (err instanceof SeedSimpleApiError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + console.log(err.rawResponse); + } +} +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `headers` request option. + +```typescript +import { SeedSimpleApiClient } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + headers: { + 'X-Custom-Header': 'custom value' + } +}); + +const response = await client.user.get(..., { + headers: { + 'X-Custom-Header': 'custom value' + } +}); +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. + +```typescript +const response = await client.user.get(..., { + queryParams: { + 'customQueryParamKey': 'custom query param value' + } +}); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.user.get(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.user.get(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.user.get(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. +The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. + +```typescript +const { data, rawResponse } = await client.user.get(...).withRawResponse(); + +console.log(data); +console.log(rawResponse.headers['X-My-Header']); +``` + +### Logging + +The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. + +```typescript +import { SeedSimpleApiClient, logging } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + logging: { + level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info + logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger + silent: false, // defaults to true, set to false to enable logging + } +}); +``` + +The `logging` object can have the following properties: + +- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. +- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. +- `silent`: Whether to silence the logger. Defaults to `true`. + +The `level` property can be one of the following values: + +- `logging.LogLevel.Debug` +- `logging.LogLevel.Info` +- `logging.LogLevel.Warn` +- `logging.LogLevel.Error` + +To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. + +
+Custom logger examples + +Here's an example using the popular `winston` logging library. + +```ts +import winston from 'winston'; + +const winstonLogger = winston.createLogger({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => winstonLogger.debug(msg, ...args), + info: (msg, ...args) => winstonLogger.info(msg, ...args), + warn: (msg, ...args) => winstonLogger.warn(msg, ...args), + error: (msg, ...args) => winstonLogger.error(msg, ...args), +}; +``` + +Here's an example using the popular `pino` logging library. + +```ts +import pino from 'pino'; + +const pinoLogger = pino({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => pinoLogger.debug(args, msg), + info: (msg, ...args) => pinoLogger.info(args, msg), + warn: (msg, ...args) => pinoLogger.warn(args, msg), + error: (msg, ...args) => pinoLogger.error(args, msg), +}; +``` + +
+ +### Runtime Compatibility + +The SDK works in the following runtimes: + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + +### Customizing Fetch Client + +The SDK provides a way for you to customize the underlying HTTP client / Fetch function. If you're running in an +unsupported environment, this provides a way for you to break glass and ensure the SDK works. + +```typescript +import { SeedSimpleApiClient } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + fetcher: // provide your implementation here +}); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/simple-api/use-oxc/package.json b/seed/ts-sdk/simple-api/use-oxc/package.json index 232e36da907d..bc3d507709c3 100644 --- a/seed/ts-sdk/simple-api/use-oxc/package.json +++ b/seed/ts-sdk/simple-api/use-oxc/package.json @@ -6,9 +6,22 @@ "type": "git", "url": "git+https://github.com/simple-api/fern.git" }, + "files": [ + "dist", + "reference.md", + "README.md", + "LICENSE" + ], "type": "commonjs", + "sideEffects": false, "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", + "browser": { + "fs": false, + "os": false, + "path": false, + "stream": false + }, "types": "./dist/cjs/index.d.ts", "exports": { ".": { @@ -25,12 +38,6 @@ }, "./package.json": "./package.json" }, - "files": [ - "dist", - "reference.md", - "README.md", - "LICENSE" - ], "scripts": { "format": "oxfmt --no-error-on-unmatched-pattern .", "format:check": "oxfmt --check --no-error-on-unmatched-pattern .", @@ -47,25 +54,18 @@ }, "dependencies": {}, "devDependencies": { - "webpack": "^5.97.1", - "ts-loader": "^9.5.1", - "vitest": "^3.2.4", - "msw": "2.11.2", "@types/node": "^18.19.70", - "typescript": "~5.7.2", + "msw": "2.11.2", + "oxfmt": "0.35.0", "oxlint": "1.50.0", "oxlint-tsgolint": "0.14.2", - "oxfmt": "0.35.0" - }, - "browser": { - "fs": false, - "os": false, - "path": false, - "stream": false + "ts-loader": "^9.5.1", + "typescript": "~5.7.2", + "vitest": "^3.2.4", + "webpack": "^5.97.1" }, - "packageManager": "pnpm@10.20.0", "engines": { "node": ">=18.0.0" }, - "sideEffects": false + "packageManager": "pnpm@10.20.0" } diff --git a/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml b/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml index 6e4c395107df..339da38e3da8 100644 --- a/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml +++ b/seed/ts-sdk/simple-api/use-oxc/pnpm-workspace.yaml @@ -1 +1 @@ -packages: ['.'] \ No newline at end of file +packages: ["."] diff --git a/seed/ts-sdk/simple-api/use-oxc/reference.md b/seed/ts-sdk/simple-api/use-oxc/reference.md index e69de29bb2d1..d3121dd7b2dd 100644 --- a/seed/ts-sdk/simple-api/use-oxc/reference.md +++ b/seed/ts-sdk/simple-api/use-oxc/reference.md @@ -0,0 +1,51 @@ +# Reference + +## User + +
client.user.get(id) -> SeedSimpleApi.User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.get("id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `string` + +
+
+ +
+
+ +**requestOptions:** `UserClient.RequestOptions` + +
+
+
+
+ +
+
+
diff --git a/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js b/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js index dc1df1cbbacb..561ec17d82d2 100644 --- a/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js +++ b/seed/ts-sdk/simple-api/use-oxc/scripts/rename-to-esm-files.js @@ -56,7 +56,7 @@ async function updateFileContents(file) { // Handle dynamic imports (yield import, await import, regular import()) const dynamicRegex = new RegExp( - `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, + `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, "g", ); newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); diff --git a/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts b/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts index c32dc24af40b..f38b01529e40 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/BaseClient.ts @@ -38,18 +38,26 @@ export interface BaseRequestOptions { export type NormalizedClientOptions = T & { logging: core.logging.Logger; authProvider?: core.AuthProvider; -} +}; -export type NormalizedClientOptionsWithAuth = NormalizedClientOptions & { - authProvider: core.AuthProvider; -} +export type NormalizedClientOptionsWithAuth = + NormalizedClientOptions & { + authProvider: core.AuthProvider; + }; export function normalizeClientOptions( - options: T + options: T, ): NormalizedClientOptions { const headers = mergeHeaders( - { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/simple-api", "X-Fern-SDK-Version": "0.0.1", "User-Agent": "@fern/simple-api/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version }, - options?.headers + { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/simple-api", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/simple-api/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + options?.headers, ); return { @@ -60,7 +68,7 @@ export function normalizeClientOptions( - options: T + options: T, ): NormalizedClientOptionsWithAuth { const normalized = normalizeClientOptions(options) as NormalizedClientOptionsWithAuth; const normalizedWithNoOpAuthProvider = withNoOpAuthProvider(normalized); @@ -69,10 +77,10 @@ export function normalizeClientOptionsWithAuth( - options: NormalizedClientOptions + options: NormalizedClientOptions, ): NormalizedClientOptionsWithAuth { return { ...options, - authProvider: new core.NoOpAuthProvider() + authProvider: new core.NoOpAuthProvider(), }; } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/Client.ts b/seed/ts-sdk/simple-api/use-oxc/src/Client.ts index 68e367690017..3049d3294978 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/Client.ts @@ -9,8 +9,7 @@ import * as environments from "./environments.js"; export declare namespace SeedSimpleApiClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions { - } + export interface RequestOptions extends BaseRequestOptions {} } export class SeedSimpleApiClient { @@ -18,10 +17,7 @@ export class SeedSimpleApiClient { protected _user: UserClient | undefined; constructor(options: SeedSimpleApiClient.Options) { - - - this._options = normalizeClientOptionsWithAuth(options); - + this._options = normalizeClientOptionsWithAuth(options); } public get user(): UserClient { diff --git a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts index bc8c41bb099e..65fa1acc87e6 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/Client.ts @@ -12,18 +12,14 @@ import * as SeedSimpleApi from "../../../index.js"; export declare namespace UserClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions { - } + export interface RequestOptions extends BaseRequestOptions {} } export class UserClient { protected readonly _options: NormalizedClientOptionsWithAuth; constructor(options: UserClient.Options) { - - - this._options = normalizeClientOptionsWithAuth(options); - + this._options = normalizeClientOptionsWithAuth(options); } /** @@ -37,11 +33,22 @@ export class UserClient { return core.HttpResponsePromise.fromPromise(this.__get(id, requestOptions)); } - private async __get(id: string, requestOptions?: UserClient.RequestOptions): Promise> { + private async __get( + id: string, + requestOptions?: UserClient.RequestOptions, + ): Promise> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); - let _headers: core.Fetcher.Args["headers"] = mergeHeaders(_authRequest.headers, this._options?.headers, requestOptions?.headers); + let _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); const _response = await core.fetcher({ - url: core.url.join(await core.Supplier.get(this._options.baseUrl) ?? await core.Supplier.get(this._options.environment), `/users/${core.url.encodePathParam(id)}`), + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `/users/${core.url.encodePathParam(id)}`, + ), method: "GET", headers: _headers, queryParameters: requestOptions?.queryParams, @@ -49,7 +56,7 @@ export class UserClient { maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, abortSignal: requestOptions?.abortSignal, fetchFn: this._options?.fetch, - logging: this._options.logging + logging: this._options.logging, }); if (_response.ok) { return { data: _response.body as SeedSimpleApi.User, rawResponse: _response.rawResponse }; @@ -59,7 +66,7 @@ export class UserClient { throw new errors.SeedSimpleApiError({ statusCode: _response.error.statusCode, body: _response.error.body, - rawResponse: _response.rawResponse + rawResponse: _response.rawResponse, }); } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts index 2234b9cae16d..cb0ff5c3b541 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/api/resources/user/client/index.ts @@ -1 +1 @@ -export { }; +export {}; diff --git a/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts b/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts index e199d99d0963..63b42583d7c1 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/auth/BearerAuthProvider.ts @@ -16,27 +16,28 @@ export class BearerAuthProvider implements core.AuthProvider { return options?.[TOKEN_PARAM] != null; } - public async getAuthRequest({ endpointMetadata }: { - endpointMetadata?: core.EndpointMetadata; - } = {}): Promise { - - const token = await core.Supplier.get(this.options[TOKEN_PARAM]); - if (token == null) { - throw new errors.SeedSimpleApiError({ - message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, - }); - } - - return { - headers: { Authorization: `Bearer ${token}` }, - }; - + public async getAuthRequest({ + endpointMetadata, + }: { + endpointMetadata?: core.EndpointMetadata; + } = {}): Promise { + const token = await core.Supplier.get(this.options[TOKEN_PARAM]); + if (token == null) { + throw new errors.SeedSimpleApiError({ + message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, + }); + } + + return { + headers: { Authorization: `Bearer ${token}` }, + }; } } export namespace BearerAuthProvider { export const AUTH_SCHEME = "bearer" as const; - export const AUTH_CONFIG_ERROR_MESSAGE: string = `Please provide '${TOKEN_PARAM}' when initializing the client` as const; + export const AUTH_CONFIG_ERROR_MESSAGE: string = + `Please provide '${TOKEN_PARAM}' when initializing the client` as const; export type Options = AuthOptions; export type AuthOptions = { [TOKEN_PARAM]: core.Supplier }; diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts index 8d88fe84d06d..9f845b956392 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getFetchFn.ts @@ -1,5 +1,3 @@ - export async function getFetchFn(): Promise { return fetch; } - diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts index 5f6162e98085..708d55728f2b 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/fetcher/getResponseBody.ts @@ -1,7 +1,6 @@ import { fromJson } from "../json.js"; import { getBinaryResponse } from "./BinaryResponse.js"; - export async function getResponseBody(response: Response, responseType?: string): Promise { switch (responseType) { case "binary-response": @@ -31,9 +30,9 @@ export async function getResponseBody(response: Response, responseType?: string) }, }; } - + return response.body; - + case "text": return await response.text(); } diff --git a/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts b/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts index cb327036972e..be45c4552a35 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/core/headers.ts @@ -1,6 +1,4 @@ -export function mergeHeaders( - ...headersArray: (Record | null | undefined)[] -): Record { +export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { const result: Record = {}; for (const [key, value] of headersArray diff --git a/seed/ts-sdk/simple-api/use-oxc/src/environments.ts b/seed/ts-sdk/simple-api/use-oxc/src/environments.ts index aca434160feb..0955c381185b 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/environments.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/environments.ts @@ -1,8 +1,10 @@ // This file was auto-generated by Fern from our API Definition. export const SeedSimpleApiEnvironment = { - Production: "https://api.example.com", - Staging: "https://staging-api.example.com", - } as const; + Production: "https://api.example.com", + Staging: "https://staging-api.example.com", +} as const; -export type SeedSimpleApiEnvironment = typeof SeedSimpleApiEnvironment.Production | typeof SeedSimpleApiEnvironment.Staging; +export type SeedSimpleApiEnvironment = + | typeof SeedSimpleApiEnvironment.Production + | typeof SeedSimpleApiEnvironment.Staging; diff --git a/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts b/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts index c7ff68ac2c8a..b6792821f7ba 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/errors/SeedSimpleApiError.ts @@ -8,12 +8,17 @@ export class SeedSimpleApiError extends Error { public readonly body?: unknown; public readonly rawResponse?: core.RawResponse; - constructor({ message, statusCode, body, rawResponse }: { - message?: string; - statusCode?: number; - body?: unknown; - rawResponse?: core.RawResponse; - }) { + constructor({ + message, + statusCode, + body, + rawResponse, + }: { + message?: string; + statusCode?: number; + body?: unknown; + rawResponse?: core.RawResponse; + }) { super(buildMessage({ message, statusCode, body })); Object.setPrototypeOf(this, new.target.prototype); if (Error.captureStackTrace) { @@ -27,11 +32,15 @@ export class SeedSimpleApiError extends Error { } } -function buildMessage({ message, statusCode, body }: { - message: string | undefined; - statusCode: number | undefined; - body: unknown | undefined; - }): string { +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { let lines: string[] = []; if (message != null) { lines.push(message); diff --git a/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts index bc8dce0450df..48aa6f183f5d 100644 --- a/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts +++ b/seed/ts-sdk/simple-api/use-oxc/src/errors/handleNonStatusCodeError.ts @@ -3,25 +3,35 @@ import * as core from "../core/index.js"; import * as errors from "./index.js"; -export function handleNonStatusCodeError(error: core.Fetcher.Error, rawResponse: core.RawResponse, method: string, path: string): never { +export function handleNonStatusCodeError( + error: core.Fetcher.Error, + rawResponse: core.RawResponse, + method: string, + path: string, +): never { switch (error.reason) { - case "non-json": throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - body: error.rawBody, - rawResponse: rawResponse - }); - case "body-is-null": throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - rawResponse: rawResponse - }); - case "timeout": throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); - case "unknown": throw new errors.SeedSimpleApiError({ - message: error.errorMessage, - rawResponse: rawResponse - }); - default: throw new errors.SeedSimpleApiError({ - message: "Unknown error", - rawResponse: rawResponse - }); + case "non-json": + throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + body: error.rawBody, + rawResponse: rawResponse, + }); + case "body-is-null": + throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + rawResponse: rawResponse, + }); + case "timeout": + throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); + case "unknown": + throw new errors.SeedSimpleApiError({ + message: error.errorMessage, + rawResponse: rawResponse, + }); + default: + throw new errors.SeedSimpleApiError({ + message: "Unknown error", + rawResponse: rawResponse, + }); } } diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts index 6927092387b6..7f5e031c8396 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/custom.test.ts @@ -1,14 +1,13 @@ - /** -* This is a custom test file, if you wish to add more tests -* to your SDK. -* Be sure to mark this file in `.fernignore`. -* -* If you include example requests/responses in your fern definition, -* you will have tests automatically generated for you. -*/ + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ describe("test", () => { it("default", () => { expect(true).toBe(true); }); -}); \ No newline at end of file +}); diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts b/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts index e2a8edf51c0b..a5651f81ba10 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/setup.ts @@ -1,4 +1,3 @@ - import { expect } from "vitest"; interface CustomMatchers { @@ -79,4 +78,3 @@ expect.extend({ } }, }); - diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json b/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json index b8eeea994bfa..a477df47920c 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxc/tests/tsconfig.json @@ -4,8 +4,8 @@ "outDir": null, "rootDir": "..", "baseUrl": "..", - "types": ["vitest/globals"] + "types": ["vitest/globals"] }, - "include": ["../src","../tests"], + "include": ["../src", "../tests"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts index 4632efa6f501..6c17624228bb 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/Fetcher.test.ts @@ -76,8 +76,6 @@ describe("Test fetcherImpl", () => { } }); - - it("should receive file as stream", async () => { const url = "https://httpbin.org/post/file"; const mockArgs: Fetcher.Args = { @@ -261,5 +259,4 @@ describe("Test fetcherImpl", () => { expect(body.bodyUsed).toBe(true); } }); - }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts index 8ef1cf11c85b..ad6be7fc2c9b 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/getResponseBody.test.ts @@ -73,7 +73,6 @@ describe("Test getResponseBody", () => { } }); - it("should handle streaming response type", async () => { const encoder = new TextEncoder(); const testData = "test stream data"; @@ -95,5 +94,4 @@ describe("Test getResponseBody", () => { const streamContent = decoder.decode(value); expect(streamContent).toBe(testData); }); - }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts index 01519cf17132..53e5c25fe648 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/unit/fetcher/requestWithRetries.test.ts @@ -13,20 +13,20 @@ describe("requestWithRetries", () => { Math.random = vi.fn(() => 0.5); vi.useFakeTimers({ - toFake: [ - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "Date", - "performance", - "requestAnimationFrame", - "cancelAnimationFrame", - "requestIdleCallback", - "cancelIdleCallback" - ] + toFake: [ + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "Date", + "performance", + "requestAnimationFrame", + "cancelAnimationFrame", + "requestIdleCallback", + "cancelIdleCallback", + ], }); }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts b/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts index 8d74497c00e3..f5c1d699da35 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts +++ b/seed/ts-sdk/simple-api/use-oxc/tests/wire/user.test.ts @@ -4,28 +4,18 @@ import { SeedSimpleApiClient } from "../../src/Client"; import { mockServerPool } from "../mock-server/MockServerPool"; describe("UserClient", () => { - test("get", async () => { const server = mockServerPool.createServer(); - const client = new SeedSimpleApiClient({ "maxRetries" : 0 , "token" : "test" , "environment" : server.baseUrl }); - - const rawResponseBody = { "id" : "id" , "name" : "name" , "email" : "email" }; - server - .mockEndpoint() - .get("/users/id").respondWith() - .statusCode(200).jsonBody(rawResponseBody) - .build(); + const client = new SeedSimpleApiClient({ maxRetries: 0, token: "test", environment: server.baseUrl }); - - - const response = await client.user.get("id"); - expect(response).toEqual({ - id: "id", - name: "name", - email: "email" -}); - - + const rawResponseBody = { id: "id", name: "name", email: "email" }; + server.mockEndpoint().get("/users/id").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.user.get("id"); + expect(response).toEqual({ + id: "id", + name: "name", + email: "email", + }); }); - }); diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json index e48f3fe29c34..d7627675de20 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.base.json @@ -13,8 +13,6 @@ "isolatedModules": true, "isolatedDeclarations": true }, - "include": [ - "src" - ], + "include": ["src"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json index baf12c360e23..5c11446f5984 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.cjs.json @@ -4,8 +4,6 @@ "module": "CommonJS", "outDir": "dist/cjs" }, - "include": [ - "src" - ], + "include": ["src"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json index 5f8ac6fcca67..6ce909748b2c 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.esm.json @@ -5,8 +5,6 @@ "outDir": "dist/esm", "verbatimModuleSyntax": true }, - "include": [ - "src" - ], + "include": ["src"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxc/tsconfig.json b/seed/ts-sdk/simple-api/use-oxc/tsconfig.json index ad43234b056f..d77fdf00d259 100644 --- a/seed/ts-sdk/simple-api/use-oxc/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxc/tsconfig.json @@ -1,3 +1,3 @@ { "extends": "./tsconfig.cjs.json" -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts b/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts index e21d088aa14c..ba2ec4f9d45a 100644 --- a/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts +++ b/seed/ts-sdk/simple-api/use-oxc/vitest.config.mts @@ -1,31 +1,28 @@ - - - import { defineConfig } from "vitest/config"; - export default defineConfig({ +import { defineConfig } from "vitest/config"; +export default defineConfig({ + test: { + projects: [ + { test: { - projects: [ - { - test: { - globals: true, - name: "unit", - environment: "node", - root: "./tests", - include: ["**/*.test.{js,ts,jsx,tsx}"], - exclude: ["wire/**"], - setupFiles: ["./setup.ts"] - } - }, - { - test: { - globals: true, - name: "wire", - environment: "node", - root: "./tests/wire", - setupFiles: ["../setup.ts", "../mock-server/setup.ts"] - } - }, - ], - passWithNoTests: true - } - }); - \ No newline at end of file + globals: true, + name: "unit", + environment: "node", + root: "./tests", + include: ["**/*.test.{js,ts,jsx,tsx}"], + exclude: ["wire/**"], + setupFiles: ["./setup.ts"], + }, + }, + { + test: { + globals: true, + name: "wire", + environment: "node", + root: "./tests/wire", + setupFiles: ["../setup.ts", "../mock-server/setup.ts"], + }, + }, + ], + passWithNoTests: true, + }, +}); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json b/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json index 36d4fa17cb4a..cbb40406f13b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/.fern/metadata.json @@ -1,9 +1,9 @@ { - "cliVersion": "DUMMY", - "generatorName": "fernapi/fern-typescript-sdk", - "generatorVersion": "latest", - "generatorConfig": { - "formatter": "oxfmt" - }, - "sdkVersion": "0.0.1" -} \ No newline at end of file + "cliVersion": "DUMMY", + "generatorName": "fernapi/fern-typescript-sdk", + "generatorVersion": "latest", + "generatorConfig": { + "formatter": "oxfmt" + }, + "sdkVersion": "0.0.1" +} diff --git a/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml b/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml index a98d4d00ff0e..f270d341b2c2 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml +++ b/seed/ts-sdk/simple-api/use-oxfmt/.github/workflows/ci.yml @@ -3,40 +3,40 @@ name: ci on: [push] jobs: - compile: - runs-on: ubuntu-latest + compile: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 + - name: Set up node + uses: actions/setup-node@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install dependencies + run: pnpm install --frozen-lockfile - - name: Compile - run: pnpm build + - name: Compile + run: pnpm build - test: - runs-on: ubuntu-latest + test: + runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 + steps: + - name: Checkout repo + uses: actions/checkout@v6 - - name: Set up node - uses: actions/setup-node@v6 - - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Set up node + uses: actions/setup-node@v6 - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Test - run: pnpm test + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Test + run: pnpm test diff --git a/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md b/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md index fe5bc2f77e0b..ac478f0bb42e 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md +++ b/seed/ts-sdk/simple-api/use-oxfmt/CONTRIBUTING.md @@ -34,6 +34,7 @@ pnpm test ``` Run specific test types: + - `pnpm test:unit` - Run unit tests - `pnpm test:wire` - Run wire/integration tests @@ -66,6 +67,7 @@ pnpm run check:fix ### Generated Files The following directories contain generated code: + - `src/api/` - API client classes and types - `src/serialization/` - Serialization/deserialization logic - Most TypeScript files in `src/` @@ -96,6 +98,7 @@ If you want to change how code is generated for all users of this SDK: 4. Submit a pull request with your changes to the generator This approach is best for: + - Bug fixes in generated code - New features that would benefit all users - Improvements to code generation patterns diff --git a/seed/ts-sdk/simple-api/use-oxfmt/README.md b/seed/ts-sdk/simple-api/use-oxfmt/README.md index e69de29bb2d1..b31eac333998 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/README.md +++ b/seed/ts-sdk/simple-api/use-oxfmt/README.md @@ -0,0 +1,255 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) +[![npm shield](https://img.shields.io/npm/v/@fern/simple-api)](https://www.npmjs.com/package/@fern/simple-api) + +The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Aborting Requests](#aborting-requests) + - [Access Raw Response Data](#access-raw-response-data) + - [Logging](#logging) + - [Runtime Compatibility](#runtime-compatibility) +- [Contributing](#contributing) + +## Installation + +```sh +npm i -s @fern/simple-api +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedSimpleApiClient, SeedSimpleApiEnvironment } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ environment: SeedSimpleApiEnvironment.Production, token: "YOUR_TOKEN" }); +await client.user.get("id"); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedSimpleApiError } from "@fern/simple-api"; + +try { + await client.user.get(...); +} catch (err) { + if (err instanceof SeedSimpleApiError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + console.log(err.rawResponse); + } +} +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `headers` request option. + +```typescript +import { SeedSimpleApiClient } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + headers: { + 'X-Custom-Header': 'custom value' + } +}); + +const response = await client.user.get(..., { + headers: { + 'X-Custom-Header': 'custom value' + } +}); +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. + +```typescript +const response = await client.user.get(..., { + queryParams: { + 'customQueryParamKey': 'custom query param value' + } +}); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.user.get(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.user.get(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.user.get(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. +The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. + +```typescript +const { data, rawResponse } = await client.user.get(...).withRawResponse(); + +console.log(data); +console.log(rawResponse.headers['X-My-Header']); +``` + +### Logging + +The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. + +```typescript +import { SeedSimpleApiClient, logging } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + logging: { + level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info + logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger + silent: false, // defaults to true, set to false to enable logging + } +}); +``` + +The `logging` object can have the following properties: + +- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. +- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. +- `silent`: Whether to silence the logger. Defaults to `true`. + +The `level` property can be one of the following values: + +- `logging.LogLevel.Debug` +- `logging.LogLevel.Info` +- `logging.LogLevel.Warn` +- `logging.LogLevel.Error` + +To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. + +
+Custom logger examples + +Here's an example using the popular `winston` logging library. + +```ts +import winston from 'winston'; + +const winstonLogger = winston.createLogger({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => winstonLogger.debug(msg, ...args), + info: (msg, ...args) => winstonLogger.info(msg, ...args), + warn: (msg, ...args) => winstonLogger.warn(msg, ...args), + error: (msg, ...args) => winstonLogger.error(msg, ...args), +}; +``` + +Here's an example using the popular `pino` logging library. + +```ts +import pino from 'pino'; + +const pinoLogger = pino({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => pinoLogger.debug(args, msg), + info: (msg, ...args) => pinoLogger.info(args, msg), + warn: (msg, ...args) => pinoLogger.warn(args, msg), + error: (msg, ...args) => pinoLogger.error(args, msg), +}; +``` + +
+ +### Runtime Compatibility + +The SDK works in the following runtimes: + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + +### Customizing Fetch Client + +The SDK provides a way for you to customize the underlying HTTP client / Fetch function. If you're running in an +unsupported environment, this provides a way for you to break glass and ensure the SDK works. + +```typescript +import { SeedSimpleApiClient } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + fetcher: // provide your implementation here +}); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/simple-api/use-oxfmt/package.json b/seed/ts-sdk/simple-api/use-oxfmt/package.json index e860872011b2..f91becae25da 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/package.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/package.json @@ -6,9 +6,22 @@ "type": "git", "url": "git+https://github.com/simple-api/fern.git" }, + "files": [ + "dist", + "reference.md", + "README.md", + "LICENSE" + ], "type": "commonjs", + "sideEffects": false, "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", + "browser": { + "fs": false, + "os": false, + "path": false, + "stream": false + }, "types": "./dist/cjs/index.d.ts", "exports": { ".": { @@ -25,12 +38,6 @@ }, "./package.json": "./package.json" }, - "files": [ - "dist", - "reference.md", - "README.md", - "LICENSE" - ], "scripts": { "format": "oxfmt --no-error-on-unmatched-pattern .", "format:check": "oxfmt --check --no-error-on-unmatched-pattern .", @@ -47,24 +54,17 @@ }, "dependencies": {}, "devDependencies": { - "webpack": "^5.97.1", - "ts-loader": "^9.5.1", - "vitest": "^3.2.4", - "msw": "2.11.2", + "@biomejs/biome": "2.4.3", "@types/node": "^18.19.70", + "msw": "2.11.2", + "oxfmt": "0.35.0", + "ts-loader": "^9.5.1", "typescript": "~5.7.2", - "@biomejs/biome": "2.4.3", - "oxfmt": "0.35.0" - }, - "browser": { - "fs": false, - "os": false, - "path": false, - "stream": false + "vitest": "^3.2.4", + "webpack": "^5.97.1" }, - "packageManager": "pnpm@10.20.0", "engines": { "node": ">=18.0.0" }, - "sideEffects": false + "packageManager": "pnpm@10.20.0" } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml b/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml index 6e4c395107df..339da38e3da8 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml +++ b/seed/ts-sdk/simple-api/use-oxfmt/pnpm-workspace.yaml @@ -1 +1 @@ -packages: ['.'] \ No newline at end of file +packages: ["."] diff --git a/seed/ts-sdk/simple-api/use-oxfmt/reference.md b/seed/ts-sdk/simple-api/use-oxfmt/reference.md index e69de29bb2d1..d3121dd7b2dd 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/reference.md +++ b/seed/ts-sdk/simple-api/use-oxfmt/reference.md @@ -0,0 +1,51 @@ +# Reference + +## User + +
client.user.get(id) -> SeedSimpleApi.User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.get("id"); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `string` + +
+
+ +
+
+ +**requestOptions:** `UserClient.RequestOptions` + +
+
+
+
+ +
+
+
diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts index c32dc24af40b..92a7a11f0513 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/BaseClient.ts @@ -2,9 +2,8 @@ import { BearerAuthProvider } from "./auth/BearerAuthProvider.js"; import * as core from "./core/index.js"; -import type { AuthProvider } from "./core/auth/index.js"; import { mergeHeaders } from "./core/headers.js"; -import * as environments from "./environments.js"; +import type * as environments from "./environments.js"; export type BaseClientOptions = { environment: core.Supplier; @@ -38,18 +37,26 @@ export interface BaseRequestOptions { export type NormalizedClientOptions = T & { logging: core.logging.Logger; authProvider?: core.AuthProvider; -} +}; -export type NormalizedClientOptionsWithAuth = NormalizedClientOptions & { - authProvider: core.AuthProvider; -} +export type NormalizedClientOptionsWithAuth = + NormalizedClientOptions & { + authProvider: core.AuthProvider; + }; export function normalizeClientOptions( - options: T + options: T, ): NormalizedClientOptions { const headers = mergeHeaders( - { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/simple-api", "X-Fern-SDK-Version": "0.0.1", "User-Agent": "@fern/simple-api/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version }, - options?.headers + { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/simple-api", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/simple-api/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + options?.headers, ); return { @@ -60,7 +67,7 @@ export function normalizeClientOptions( - options: T + options: T, ): NormalizedClientOptionsWithAuth { const normalized = normalizeClientOptions(options) as NormalizedClientOptionsWithAuth; const normalizedWithNoOpAuthProvider = withNoOpAuthProvider(normalized); @@ -69,10 +76,10 @@ export function normalizeClientOptionsWithAuth( - options: NormalizedClientOptions + options: NormalizedClientOptions, ): NormalizedClientOptionsWithAuth { return { ...options, - authProvider: new core.NoOpAuthProvider() + authProvider: new core.NoOpAuthProvider(), }; } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts index 68e367690017..5ed3c9c18ab1 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/Client.ts @@ -3,14 +3,11 @@ import { UserClient } from "./api/resources/user/client/Client.js"; import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js"; import { normalizeClientOptionsWithAuth, type NormalizedClientOptionsWithAuth } from "./BaseClient.js"; -import * as core from "./core/index.js"; -import * as environments from "./environments.js"; export declare namespace SeedSimpleApiClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions { - } + export interface RequestOptions extends BaseRequestOptions {} } export class SeedSimpleApiClient { @@ -18,10 +15,7 @@ export class SeedSimpleApiClient { protected _user: UserClient | undefined; constructor(options: SeedSimpleApiClient.Options) { - - - this._options = normalizeClientOptionsWithAuth(options); - + this._options = normalizeClientOptionsWithAuth(options); } public get user(): UserClient { diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts index bc8c41bb099e..dd4cd50ede2a 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/Client.ts @@ -4,26 +4,21 @@ import type { BaseClientOptions, BaseRequestOptions } from "../../../../BaseClie import { normalizeClientOptionsWithAuth, type NormalizedClientOptionsWithAuth } from "../../../../BaseClient.js"; import * as core from "../../../../core/index.js"; import { mergeHeaders } from "../../../../core/headers.js"; -import * as environments from "../../../../environments.js"; import { handleNonStatusCodeError } from "../../../../errors/handleNonStatusCodeError.js"; import * as errors from "../../../../errors/index.js"; -import * as SeedSimpleApi from "../../../index.js"; +import type * as SeedSimpleApi from "../../../index.js"; export declare namespace UserClient { export type Options = BaseClientOptions; - export interface RequestOptions extends BaseRequestOptions { - } + export interface RequestOptions extends BaseRequestOptions {} } export class UserClient { protected readonly _options: NormalizedClientOptionsWithAuth; constructor(options: UserClient.Options) { - - - this._options = normalizeClientOptionsWithAuth(options); - + this._options = normalizeClientOptionsWithAuth(options); } /** @@ -37,11 +32,22 @@ export class UserClient { return core.HttpResponsePromise.fromPromise(this.__get(id, requestOptions)); } - private async __get(id: string, requestOptions?: UserClient.RequestOptions): Promise> { + private async __get( + id: string, + requestOptions?: UserClient.RequestOptions, + ): Promise> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); - let _headers: core.Fetcher.Args["headers"] = mergeHeaders(_authRequest.headers, this._options?.headers, requestOptions?.headers); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); const _response = await core.fetcher({ - url: core.url.join(await core.Supplier.get(this._options.baseUrl) ?? await core.Supplier.get(this._options.environment), `/users/${core.url.encodePathParam(id)}`), + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `/users/${core.url.encodePathParam(id)}`, + ), method: "GET", headers: _headers, queryParameters: requestOptions?.queryParams, @@ -49,7 +55,7 @@ export class UserClient { maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, abortSignal: requestOptions?.abortSignal, fetchFn: this._options?.fetch, - logging: this._options.logging + logging: this._options.logging, }); if (_response.ok) { return { data: _response.body as SeedSimpleApi.User, rawResponse: _response.rawResponse }; @@ -59,7 +65,7 @@ export class UserClient { throw new errors.SeedSimpleApiError({ statusCode: _response.error.statusCode, body: _response.error.body, - rawResponse: _response.rawResponse + rawResponse: _response.rawResponse, }); } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts index 2234b9cae16d..cb0ff5c3b541 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/api/resources/user/client/index.ts @@ -1 +1 @@ -export { }; +export {}; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts index e199d99d0963..63b42583d7c1 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/auth/BearerAuthProvider.ts @@ -16,27 +16,28 @@ export class BearerAuthProvider implements core.AuthProvider { return options?.[TOKEN_PARAM] != null; } - public async getAuthRequest({ endpointMetadata }: { - endpointMetadata?: core.EndpointMetadata; - } = {}): Promise { - - const token = await core.Supplier.get(this.options[TOKEN_PARAM]); - if (token == null) { - throw new errors.SeedSimpleApiError({ - message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, - }); - } - - return { - headers: { Authorization: `Bearer ${token}` }, - }; - + public async getAuthRequest({ + endpointMetadata, + }: { + endpointMetadata?: core.EndpointMetadata; + } = {}): Promise { + const token = await core.Supplier.get(this.options[TOKEN_PARAM]); + if (token == null) { + throw new errors.SeedSimpleApiError({ + message: BearerAuthProvider.AUTH_CONFIG_ERROR_MESSAGE, + }); + } + + return { + headers: { Authorization: `Bearer ${token}` }, + }; } } export namespace BearerAuthProvider { export const AUTH_SCHEME = "bearer" as const; - export const AUTH_CONFIG_ERROR_MESSAGE: string = `Please provide '${TOKEN_PARAM}' when initializing the client` as const; + export const AUTH_CONFIG_ERROR_MESSAGE: string = + `Please provide '${TOKEN_PARAM}' when initializing the client` as const; export type Options = AuthOptions; export type AuthOptions = { [TOKEN_PARAM]: core.Supplier }; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts index 8d88fe84d06d..9f845b956392 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getFetchFn.ts @@ -1,5 +1,3 @@ - export async function getFetchFn(): Promise { return fetch; } - diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts index 5f6162e98085..708d55728f2b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/fetcher/getResponseBody.ts @@ -1,7 +1,6 @@ import { fromJson } from "../json.js"; import { getBinaryResponse } from "./BinaryResponse.js"; - export async function getResponseBody(response: Response, responseType?: string): Promise { switch (responseType) { case "binary-response": @@ -31,9 +30,9 @@ export async function getResponseBody(response: Response, responseType?: string) }, }; } - + return response.body; - + case "text": return await response.text(); } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts index cb327036972e..be45c4552a35 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/core/headers.ts @@ -1,6 +1,4 @@ -export function mergeHeaders( - ...headersArray: (Record | null | undefined)[] -): Record { +export function mergeHeaders(...headersArray: (Record | null | undefined)[]): Record { const result: Record = {}; for (const [key, value] of headersArray diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts index aca434160feb..0955c381185b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/environments.ts @@ -1,8 +1,10 @@ // This file was auto-generated by Fern from our API Definition. export const SeedSimpleApiEnvironment = { - Production: "https://api.example.com", - Staging: "https://staging-api.example.com", - } as const; + Production: "https://api.example.com", + Staging: "https://staging-api.example.com", +} as const; -export type SeedSimpleApiEnvironment = typeof SeedSimpleApiEnvironment.Production | typeof SeedSimpleApiEnvironment.Staging; +export type SeedSimpleApiEnvironment = + | typeof SeedSimpleApiEnvironment.Production + | typeof SeedSimpleApiEnvironment.Staging; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts index c7ff68ac2c8a..e3d83943c4fa 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/SeedSimpleApiError.ts @@ -1,6 +1,6 @@ // This file was auto-generated by Fern from our API Definition. -import * as core from "../core/index.js"; +import type * as core from "../core/index.js"; import { toJson } from "../core/json.js"; export class SeedSimpleApiError extends Error { @@ -8,12 +8,17 @@ export class SeedSimpleApiError extends Error { public readonly body?: unknown; public readonly rawResponse?: core.RawResponse; - constructor({ message, statusCode, body, rawResponse }: { - message?: string; - statusCode?: number; - body?: unknown; - rawResponse?: core.RawResponse; - }) { + constructor({ + message, + statusCode, + body, + rawResponse, + }: { + message?: string; + statusCode?: number; + body?: unknown; + rawResponse?: core.RawResponse; + }) { super(buildMessage({ message, statusCode, body })); Object.setPrototypeOf(this, new.target.prototype); if (Error.captureStackTrace) { @@ -27,12 +32,16 @@ export class SeedSimpleApiError extends Error { } } -function buildMessage({ message, statusCode, body }: { - message: string | undefined; - statusCode: number | undefined; - body: unknown | undefined; - }): string { - let lines: string[] = []; +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { + const lines: string[] = []; if (message != null) { lines.push(message); } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts index bc8dce0450df..d24a21941f05 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/src/errors/handleNonStatusCodeError.ts @@ -1,27 +1,37 @@ // This file was auto-generated by Fern from our API Definition. -import * as core from "../core/index.js"; +import type * as core from "../core/index.js"; import * as errors from "./index.js"; -export function handleNonStatusCodeError(error: core.Fetcher.Error, rawResponse: core.RawResponse, method: string, path: string): never { +export function handleNonStatusCodeError( + error: core.Fetcher.Error, + rawResponse: core.RawResponse, + method: string, + path: string, +): never { switch (error.reason) { - case "non-json": throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - body: error.rawBody, - rawResponse: rawResponse - }); - case "body-is-null": throw new errors.SeedSimpleApiError({ - statusCode: error.statusCode, - rawResponse: rawResponse - }); - case "timeout": throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); - case "unknown": throw new errors.SeedSimpleApiError({ - message: error.errorMessage, - rawResponse: rawResponse - }); - default: throw new errors.SeedSimpleApiError({ - message: "Unknown error", - rawResponse: rawResponse - }); + case "non-json": + throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + body: error.rawBody, + rawResponse: rawResponse, + }); + case "body-is-null": + throw new errors.SeedSimpleApiError({ + statusCode: error.statusCode, + rawResponse: rawResponse, + }); + case "timeout": + throw new errors.SeedSimpleApiTimeoutError(`Timeout exceeded when calling ${method} ${path}.`); + case "unknown": + throw new errors.SeedSimpleApiError({ + message: error.errorMessage, + rawResponse: rawResponse, + }); + default: + throw new errors.SeedSimpleApiError({ + message: "Unknown error", + rawResponse: rawResponse, + }); } } diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts index 6927092387b6..7f5e031c8396 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/custom.test.ts @@ -1,14 +1,13 @@ - /** -* This is a custom test file, if you wish to add more tests -* to your SDK. -* Be sure to mark this file in `.fernignore`. -* -* If you include example requests/responses in your fern definition, -* you will have tests automatically generated for you. -*/ + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ describe("test", () => { it("default", () => { expect(true).toBe(true); }); -}); \ No newline at end of file +}); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts index e2a8edf51c0b..a5651f81ba10 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/setup.ts @@ -1,4 +1,3 @@ - import { expect } from "vitest"; interface CustomMatchers { @@ -79,4 +78,3 @@ expect.extend({ } }, }); - diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json b/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json index b8eeea994bfa..a477df47920c 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/tsconfig.json @@ -4,8 +4,8 @@ "outDir": null, "rootDir": "..", "baseUrl": "..", - "types": ["vitest/globals"] + "types": ["vitest/globals"] }, - "include": ["../src","../tests"], + "include": ["../src", "../tests"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts index 4632efa6f501..6c17624228bb 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/Fetcher.test.ts @@ -76,8 +76,6 @@ describe("Test fetcherImpl", () => { } }); - - it("should receive file as stream", async () => { const url = "https://httpbin.org/post/file"; const mockArgs: Fetcher.Args = { @@ -261,5 +259,4 @@ describe("Test fetcherImpl", () => { expect(body.bodyUsed).toBe(true); } }); - }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts index 8ef1cf11c85b..ad6be7fc2c9b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/getResponseBody.test.ts @@ -73,7 +73,6 @@ describe("Test getResponseBody", () => { } }); - it("should handle streaming response type", async () => { const encoder = new TextEncoder(); const testData = "test stream data"; @@ -95,5 +94,4 @@ describe("Test getResponseBody", () => { const streamContent = decoder.decode(value); expect(streamContent).toBe(testData); }); - }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts index 7de9e90414b5..56549d63fd4b 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/makeRequest.test.ts @@ -1,5 +1,5 @@ import { makeRequest } from "../../../src/core/fetcher/makeRequest"; -import { Mock } from "vitest"; +import type { Mock } from "vitest"; describe("Test makeRequest", () => { const mockPostUrl = "https://httpbin.org/post"; diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts index 01519cf17132..ba577dc38620 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/unit/fetcher/requestWithRetries.test.ts @@ -1,5 +1,5 @@ import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; -import { Mock, MockInstance } from "vitest"; +import type { Mock, MockInstance } from "vitest"; describe("requestWithRetries", () => { let mockFetch: Mock; @@ -13,20 +13,20 @@ describe("requestWithRetries", () => { Math.random = vi.fn(() => 0.5); vi.useFakeTimers({ - toFake: [ - "setTimeout", - "clearTimeout", - "setInterval", - "clearInterval", - "setImmediate", - "clearImmediate", - "Date", - "performance", - "requestAnimationFrame", - "cancelAnimationFrame", - "requestIdleCallback", - "cancelIdleCallback" - ] + toFake: [ + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "setImmediate", + "clearImmediate", + "Date", + "performance", + "requestAnimationFrame", + "cancelAnimationFrame", + "requestIdleCallback", + "cancelIdleCallback", + ], }); }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts b/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts index 8d74497c00e3..f5c1d699da35 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts +++ b/seed/ts-sdk/simple-api/use-oxfmt/tests/wire/user.test.ts @@ -4,28 +4,18 @@ import { SeedSimpleApiClient } from "../../src/Client"; import { mockServerPool } from "../mock-server/MockServerPool"; describe("UserClient", () => { - test("get", async () => { const server = mockServerPool.createServer(); - const client = new SeedSimpleApiClient({ "maxRetries" : 0 , "token" : "test" , "environment" : server.baseUrl }); - - const rawResponseBody = { "id" : "id" , "name" : "name" , "email" : "email" }; - server - .mockEndpoint() - .get("/users/id").respondWith() - .statusCode(200).jsonBody(rawResponseBody) - .build(); + const client = new SeedSimpleApiClient({ maxRetries: 0, token: "test", environment: server.baseUrl }); - - - const response = await client.user.get("id"); - expect(response).toEqual({ - id: "id", - name: "name", - email: "email" -}); - - + const rawResponseBody = { id: "id", name: "name", email: "email" }; + server.mockEndpoint().get("/users/id").respondWith().statusCode(200).jsonBody(rawResponseBody).build(); + + const response = await client.user.get("id"); + expect(response).toEqual({ + id: "id", + name: "name", + email: "email", + }); }); - }); diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json index e48f3fe29c34..d7627675de20 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.base.json @@ -13,8 +13,6 @@ "isolatedModules": true, "isolatedDeclarations": true }, - "include": [ - "src" - ], + "include": ["src"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json index baf12c360e23..5c11446f5984 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.cjs.json @@ -4,8 +4,6 @@ "module": "CommonJS", "outDir": "dist/cjs" }, - "include": [ - "src" - ], + "include": ["src"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json index 5f8ac6fcca67..6ce909748b2c 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.esm.json @@ -5,8 +5,6 @@ "outDir": "dist/esm", "verbatimModuleSyntax": true }, - "include": [ - "src" - ], + "include": ["src"], "exclude": [] -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json index ad43234b056f..d77fdf00d259 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json +++ b/seed/ts-sdk/simple-api/use-oxfmt/tsconfig.json @@ -1,3 +1,3 @@ { "extends": "./tsconfig.cjs.json" -} \ No newline at end of file +} diff --git a/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts b/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts index e21d088aa14c..ba2ec4f9d45a 100644 --- a/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts +++ b/seed/ts-sdk/simple-api/use-oxfmt/vitest.config.mts @@ -1,31 +1,28 @@ - - - import { defineConfig } from "vitest/config"; - export default defineConfig({ +import { defineConfig } from "vitest/config"; +export default defineConfig({ + test: { + projects: [ + { test: { - projects: [ - { - test: { - globals: true, - name: "unit", - environment: "node", - root: "./tests", - include: ["**/*.test.{js,ts,jsx,tsx}"], - exclude: ["wire/**"], - setupFiles: ["./setup.ts"] - } - }, - { - test: { - globals: true, - name: "wire", - environment: "node", - root: "./tests/wire", - setupFiles: ["../setup.ts", "../mock-server/setup.ts"] - } - }, - ], - passWithNoTests: true - } - }); - \ No newline at end of file + globals: true, + name: "unit", + environment: "node", + root: "./tests", + include: ["**/*.test.{js,ts,jsx,tsx}"], + exclude: ["wire/**"], + setupFiles: ["./setup.ts"], + }, + }, + { + test: { + globals: true, + name: "wire", + environment: "node", + root: "./tests/wire", + setupFiles: ["../setup.ts", "../mock-server/setup.ts"], + }, + }, + ], + passWithNoTests: true, + }, +}); diff --git a/seed/ts-sdk/simple-api/use-oxlint/README.md b/seed/ts-sdk/simple-api/use-oxlint/README.md index e69de29bb2d1..987b93560859 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/README.md +++ b/seed/ts-sdk/simple-api/use-oxlint/README.md @@ -0,0 +1,254 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript) +[![npm shield](https://img.shields.io/npm/v/@fern/simple-api)](https://www.npmjs.com/package/@fern/simple-api) + +The Seed TypeScript library provides convenient access to the Seed APIs from TypeScript. + +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Exception Handling](#exception-handling) +- [Advanced](#advanced) + - [Additional Headers](#additional-headers) + - [Additional Query String Parameters](#additional-query-string-parameters) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Aborting Requests](#aborting-requests) + - [Access Raw Response Data](#access-raw-response-data) + - [Logging](#logging) + - [Runtime Compatibility](#runtime-compatibility) +- [Contributing](#contributing) + +## Installation + +```sh +npm i -s @fern/simple-api +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedSimpleApiClient, SeedSimpleApiEnvironment } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ environment: SeedSimpleApiEnvironment.Production, token: "YOUR_TOKEN" }); +await client.user.get("id"); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedSimpleApiError } from "@fern/simple-api"; + +try { + await client.user.get(...); +} catch (err) { + if (err instanceof SeedSimpleApiError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + console.log(err.rawResponse); + } +} +``` + +## Advanced + +### Additional Headers + +If you would like to send additional headers as part of the request, use the `headers` request option. + +```typescript +import { SeedSimpleApiClient } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + headers: { + 'X-Custom-Header': 'custom value' + } +}); + +const response = await client.user.get(..., { + headers: { + 'X-Custom-Header': 'custom value' + } +}); +``` + +### Additional Query String Parameters + +If you would like to send additional query string parameters as part of the request, use the `queryParams` request option. + +```typescript +const response = await client.user.get(..., { + queryParams: { + 'customQueryParamKey': 'custom query param value' + } +}); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.user.get(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.user.get(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.user.get(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Access Raw Response Data + +The SDK provides access to raw response data, including headers, through the `.withRawResponse()` method. +The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property. + +```typescript +const { data, rawResponse } = await client.user.get(...).withRawResponse(); + +console.log(data); +console.log(rawResponse.headers['X-My-Header']); +``` + +### Logging + +The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options. + +```typescript +import { SeedSimpleApiClient, logging } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + logging: { + level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info + logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger + silent: false, // defaults to true, set to false to enable logging + } +}); +``` +The `logging` object can have the following properties: +- `level`: The log level to use. Defaults to `logging.LogLevel.Info`. +- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`. +- `silent`: Whether to silence the logger. Defaults to `true`. + +The `level` property can be one of the following values: +- `logging.LogLevel.Debug` +- `logging.LogLevel.Info` +- `logging.LogLevel.Warn` +- `logging.LogLevel.Error` + +To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface. + +
+Custom logger examples + +Here's an example using the popular `winston` logging library. +```ts +import winston from 'winston'; + +const winstonLogger = winston.createLogger({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => winstonLogger.debug(msg, ...args), + info: (msg, ...args) => winstonLogger.info(msg, ...args), + warn: (msg, ...args) => winstonLogger.warn(msg, ...args), + error: (msg, ...args) => winstonLogger.error(msg, ...args), +}; +``` + +Here's an example using the popular `pino` logging library. + +```ts +import pino from 'pino'; + +const pinoLogger = pino({...}); + +const logger: logging.ILogger = { + debug: (msg, ...args) => pinoLogger.debug(args, msg), + info: (msg, ...args) => pinoLogger.info(args, msg), + warn: (msg, ...args) => pinoLogger.warn(args, msg), + error: (msg, ...args) => pinoLogger.error(args, msg), +}; +``` +
+ + +### Runtime Compatibility + + +The SDK works in the following runtimes: + + + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + +### Customizing Fetch Client + +The SDK provides a way for you to customize the underlying HTTP client / Fetch function. If you're running in an +unsupported environment, this provides a way for you to break glass and ensure the SDK works. + +```typescript +import { SeedSimpleApiClient } from "@fern/simple-api"; + +const client = new SeedSimpleApiClient({ + ... + fetcher: // provide your implementation here +}); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! \ No newline at end of file diff --git a/seed/ts-sdk/simple-api/use-oxlint/reference.md b/seed/ts-sdk/simple-api/use-oxlint/reference.md index e69de29bb2d1..4c28e3b1b8d7 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/reference.md +++ b/seed/ts-sdk/simple-api/use-oxlint/reference.md @@ -0,0 +1,50 @@ +# Reference +## User +
client.user.get(id) -> SeedSimpleApi.User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.get("id"); + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `string` + +
+
+ +
+
+ +**requestOptions:** `UserClient.RequestOptions` + +
+
+
+
+ + +
+
+
diff --git a/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js b/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js index dc1df1cbbacb..561ec17d82d2 100644 --- a/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js +++ b/seed/ts-sdk/simple-api/use-oxlint/scripts/rename-to-esm-files.js @@ -56,7 +56,7 @@ async function updateFileContents(file) { // Handle dynamic imports (yield import, await import, regular import()) const dynamicRegex = new RegExp( - `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.\?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, + `(yield\\s+import|await\\s+import|import)\\s*\\(\\s*['"](\\.\\.?\\/[^'"]+)(\\${oldExt})['"]\\s*\\)`, "g", ); newContent = newContent.replace(dynamicRegex, `$1("$2${newExt}")`); diff --git a/seed/ts-sdk/simple-api/use-prettier-no-linter/tests/setup.ts b/seed/ts-sdk/simple-api/use-prettier-no-linter/tests/setup.ts index 201f5fbc968f..a5651f81ba10 100644 --- a/seed/ts-sdk/simple-api/use-prettier-no-linter/tests/setup.ts +++ b/seed/ts-sdk/simple-api/use-prettier-no-linter/tests/setup.ts @@ -52,37 +52,27 @@ expect.extend({ if (pass) { return { - message: () => - "expected " + actualType + " not to contain " + this.utils.printExpected(expectedHeaders), + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { const messages: string[] = []; if (missingHeaders.length > 0) { - messages.push("Missing headers: " + this.utils.printExpected(missingHeaders.join(", "))); + messages.push(`Missing headers: ${this.utils.printExpected(missingHeaders.join(", "))}`); } if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + - ": expected " + - this.utils.printExpected(expected) + - " but got " + - this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + - actualType + - " to contain " + - this.utils.printExpected(expectedHeaders) + - "\n\n" + - messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } diff --git a/seed/ts-sdk/simple-api/use-prettier/tests/setup.ts b/seed/ts-sdk/simple-api/use-prettier/tests/setup.ts index 3bd2e38ca2ca..a5651f81ba10 100644 --- a/seed/ts-sdk/simple-api/use-prettier/tests/setup.ts +++ b/seed/ts-sdk/simple-api/use-prettier/tests/setup.ts @@ -52,8 +52,7 @@ expect.extend({ if (pass) { return { - message: () => - `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, + message: () => `expected ${actualType} not to contain ${this.utils.printExpected(expectedHeaders)}`, pass: true, }; } else { @@ -66,23 +65,14 @@ expect.extend({ if (mismatchedHeaders.length > 0) { const mismatches = mismatchedHeaders.map( ({ key, expected, actual }) => - key + - ": expected " + - this.utils.printExpected(expected) + - " but got " + - this.utils.printReceived(actual), + `${key}: expected ${this.utils.printExpected(expected)} but got ${this.utils.printReceived(actual)}`, ); messages.push(mismatches.join("\n")); } return { message: () => - "expected " + - actualType + - " to contain " + - this.utils.printExpected(expectedHeaders) + - "\n\n" + - messages.join("\n"), + `expected ${actualType} to contain ${this.utils.printExpected(expectedHeaders)}\n\n${messages.join("\n")}`, pass: false, }; } From 31928b5f5a18474194370a3a7ae8f65cf39b890d Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Tue, 24 Feb 2026 06:22:29 -0500 Subject: [PATCH 7/7] chore(swift): replace IR-generated endpoint path tests with focused unit tests (#12648) * chore(swift): replace per-file snapshots with built-in vitest snapshots in endpoint path formatter tests Co-Authored-By: unknown <> * perf(swift): use pre-generated endpoint fixtures for ~140x test speedup (42s -> 0.3s) Co-Authored-By: unknown <> * refactor(swift): auto-generate endpoint fixtures via vitest globalSetup hook Replace the committed endpoint-fixtures.json with an auto-generated one via vitest's globalSetup. The fixture is regenerated when test definitions change (detected via directory listing hash), eliminating the manual regeneration step. Co-Authored-By: unknown <> * fix(swift): exclude globalSetup from tsc and use runtime JSON loading for fixtures Co-Authored-By: unknown <> * refactor(swift): replace 139 IR-generated tests with focused unit tests for formatEndpointPathForSwift Co-Authored-By: unknown <> * chore(swift): remove versions.yml bump per reviewer feedback Co-Authored-By: unknown <> * test(swift): add edge case tests for null head/tail, missing params, and consecutive params Co-Authored-By: unknown <> * style(swift): fix biome formatting in test file Co-Authored-By: unknown <> * Delete tests for impossible states * Fix unsafe type casts --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Anar Kafkas --- .../format-endpoint-path-for-swift.test.ts | 182 ++++++++++++++---- .../accept-header.swift | 2 - .../alias-extends.swift | 2 - .../formatted-endpoint-paths/alias.swift | 2 - .../formatted-endpoint-paths/any-auth.swift | 6 - .../api-wide-base-path.swift | 2 - .../formatted-endpoint-paths/audiences.swift | 8 - .../auth-environment-variables.swift | 3 - .../basic-auth-environment-variables.swift | 3 - .../formatted-endpoint-paths/basic-auth.swift | 3 - .../bearer-token-environment-variable.swift | 2 - .../bytes-download.swift | 3 - .../bytes-upload.swift | 2 - .../circular-references-advanced.swift | 0 .../circular-references.swift | 0 .../client-side-params.swift | 13 -- .../content-type.swift | 6 - .../cross-package-type-names.swift | 8 - .../csharp-grpc-proto-exhaustive.swift | 2 - .../csharp-grpc-proto.swift | 0 .../csharp-namespace-collision.swift | 7 - .../csharp-namespace-conflict.swift | 2 - .../csharp-property-access.swift | 2 - .../csharp-readonly-request.swift | 2 - .../csharp-system-collision.swift | 4 - .../csharp-xml-entities.swift | 2 - .../custom-auth.swift | 3 - .../dollar-string-examples.swift | 0 .../empty-clients.swift | 0 .../endpoint-security-auth.swift | 11 -- .../formatted-endpoint-paths/enum.swift | 15 -- .../error-property.swift | 2 - .../formatted-endpoint-paths/errors.swift | 4 - .../formatted-endpoint-paths/examples.swift | 20 -- .../formatted-endpoint-paths/exhaustive.swift | 81 -------- .../formatted-endpoint-paths/extends.swift | 2 - .../extra-properties.swift | 2 - .../file-download.swift | 3 - .../file-upload-openapi.swift | 2 - .../file-upload.swift | 13 -- .../formatted-endpoint-paths/folders.swift | 15 -- .../go-bytes-request.swift | 2 - .../go-content-type.swift | 2 - .../go-optional-literal-alias.swift | 2 - .../go-undiscriminated-union-wire-tests.swift | 2 - .../header-auth-environment-variable.swift | 2 - .../header-auth.swift | 2 - .../formatted-endpoint-paths/http-head.swift | 3 - .../idempotency-headers.swift | 3 - .../formatted-endpoint-paths/imdb.swift | 3 - .../inferred-auth-explicit.swift | 12 -- .../inferred-auth-implicit-api-key.swift | 11 -- .../inferred-auth-implicit-no-expiry.swift | 12 -- .../inferred-auth-implicit-reference.swift | 12 -- .../inferred-auth-implicit.swift | 12 -- .../inline-enum-request.swift | 2 - .../java-builder-extension.swift | 2 - .../java-custom-package-prefix.swift | 3 - .../java-default-timeout.swift | 2 - .../java-inline-types.swift | 4 - .../java-nullable-named-request-types.swift | 3 - .../java-optional-nullable-query-params.swift | 2 - ...java-optional-query-params-overloads.swift | 4 - .../java-output-directory.swift | 2 - .../java-pagination-deep-cursor-path.swift | 4 - .../java-path-param-key-conflict.swift | 2 - .../java-required-body-optional-headers.swift | 8 - .../java-single-property-endpoint.swift | 2 - .../java-staged-builder-ordering.swift | 6 - .../java-streaming-accept-header.swift | 3 - .../java-with-property-conflict.swift | 0 .../formatted-endpoint-paths/license.swift | 2 - .../formatted-endpoint-paths/literal.swift | 14 -- .../literals-unions.swift | 0 .../formatted-endpoint-paths/mixed-case.swift | 3 - .../mixed-file-directory.swift | 11 -- .../multi-api-environment-grouping.swift | 10 - .../multi-api-environment-no-grouping.swift | 10 - .../multi-line-docs.swift | 3 - .../multi-url-environment-no-default.swift | 5 - .../multi-url-environment.swift | 5 - .../multiple-request-bodies.swift | 3 - .../no-environment.swift | 2 - .../formatted-endpoint-paths/no-retries.swift | 2 - .../nullable-allof-extends.swift | 3 - .../nullable-optional.swift | 14 -- .../nullable-request-body.swift | 2 - .../formatted-endpoint-paths/nullable.swift | 4 - .../oauth-client-credentials-custom.swift | 12 -- .../oauth-client-credentials-default.swift | 11 -- ...nt-credentials-environment-variables.swift | 12 -- ...th-client-credentials-mandatory-auth.swift | 9 - ...oauth-client-credentials-nested-root.swift | 11 -- .../oauth-client-credentials-reference.swift | 5 - ...th-client-credentials-with-variables.swift | 15 -- .../oauth-client-credentials.swift | 12 -- .../formatted-endpoint-paths/object.swift | 0 .../objects-with-imports.swift | 0 .../formatted-endpoint-paths/optional.swift | 4 - .../package-yml.swift | 5 - .../pagination-custom.swift | 2 - .../pagination-uri-path.swift | 3 - .../formatted-endpoint-paths/pagination.swift | 33 ---- .../path-parameters.swift | 12 -- .../formatted-endpoint-paths/plain-text.swift | 2 - .../property-access.swift | 2 - .../public-object.swift | 2 - .../python-backslash-escape.swift | 2 - .../python-mypy-exclude.swift | 0 .../python-positional-single-property.swift | 2 - .../python-streaming-parameter-openapi.swift | 3 - .../query-parameters-openapi-as-objects.swift | 2 - .../query-parameters-openapi.swift | 2 - .../query-parameters.swift | 2 - .../request-parameters.swift | 5 - .../required-nullable.swift | 3 - .../reserved-keywords.swift | 2 - .../response-property.swift | 8 - .../ruby-reserved-word-properties.swift | 2 - .../server-sent-event-examples.swift | 3 - .../server-sent-events.swift | 3 - .../server-url-templating.swift | 4 - .../formatted-endpoint-paths/simple-api.swift | 2 - .../simple-fhir.swift | 2 - .../single-url-environment-default.swift | 2 - .../single-url-environment-no-default.swift | 2 - .../streaming-parameter.swift | 2 - .../formatted-endpoint-paths/streaming.swift | 3 - .../formatted-endpoint-paths/trace.swift | 54 ------ .../ts-express-casing.swift | 3 - .../ts-extra-properties.swift | 3 - .../ts-inline-types.swift | 4 - ...minated-union-with-response-property.swift | 3 - .../undiscriminated-unions.swift | 8 - .../unions-with-local-date.swift | 12 -- .../formatted-endpoint-paths/unions.swift | 8 - .../formatted-endpoint-paths/unknown.swift | 3 - .../url-form-encoded.swift | 2 - .../formatted-endpoint-paths/validation.swift | 3 - .../formatted-endpoint-paths/variables.swift | 2 - .../version-no-default.swift | 2 - .../formatted-endpoint-paths/version.swift | 2 - .../webhook-audience.swift | 0 .../websocket-bearer-auth.swift | 0 .../websocket-inferred-auth.swift | 3 - .../formatted-endpoint-paths/websocket.swift | 0 .../util/format-endpoint-path-for-swift.ts | 5 +- .../client/util/parse-endpoint-path.ts | 13 +- 148 files changed, 158 insertions(+), 843 deletions(-) delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/accept-header.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias-extends.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/any-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/api-wide-base-path.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/audiences.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/auth-environment-variables.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth-environment-variables.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bearer-token-environment-variable.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-download.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-upload.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/circular-references-advanced.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/circular-references.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/client-side-params.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/content-type.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/cross-package-type-names.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-grpc-proto-exhaustive.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-grpc-proto.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-collision.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-conflict.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-property-access.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-readonly-request.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-system-collision.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-xml-entities.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/custom-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/dollar-string-examples.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/empty-clients.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/endpoint-security-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/enum.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/error-property.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/errors.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/examples.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/exhaustive.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extends.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extra-properties.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-download.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload-openapi.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/folders.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-bytes-request.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-content-type.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-optional-literal-alias.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-undiscriminated-union-wire-tests.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth-environment-variable.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/http-head.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/idempotency-headers.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/imdb.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-explicit.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-api-key.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-no-expiry.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-reference.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inline-enum-request.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-builder-extension.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-custom-package-prefix.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-default-timeout.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-inline-types.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-nullable-named-request-types.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-nullable-query-params.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-query-params-overloads.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-output-directory.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-pagination-deep-cursor-path.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-path-param-key-conflict.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-required-body-optional-headers.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-single-property-endpoint.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-staged-builder-ordering.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-streaming-accept-header.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-with-property-conflict.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/license.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/literal.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/literals-unions.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-case.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-file-directory.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-grouping.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-no-grouping.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-line-docs.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment-no-default.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multiple-request-bodies.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-environment.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-retries.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-allof-extends.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-optional.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-request-body.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-custom.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-default.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-environment-variables.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-mandatory-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-nested-root.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-reference.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-with-variables.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/object.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/objects-with-imports.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/optional.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/package-yml.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-custom.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-uri-path.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/path-parameters.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/plain-text.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/property-access.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/public-object.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-backslash-escape.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-mypy-exclude.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-positional-single-property.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-streaming-parameter-openapi.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi-as-objects.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/request-parameters.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/required-nullable.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/reserved-keywords.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/response-property.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ruby-reserved-word-properties.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-event-examples.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-events.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-url-templating.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-api.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-fhir.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-default.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-no-default.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming-parameter.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/trace.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-express-casing.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-extra-properties.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-inline-types.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-union-with-response-property.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-unions.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions-with-local-date.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unknown.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/url-form-encoded.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/validation.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/variables.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version-no-default.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/webhook-audience.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket-bearer-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket-inferred-auth.swift delete mode 100644 generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket.swift diff --git a/generators/swift/sdk/src/generators/client/util/__test__/format-endpoint-path-for-swift.test.ts b/generators/swift/sdk/src/generators/client/util/__test__/format-endpoint-path-for-swift.test.ts index 754d205d95c6..6c96d3cdc05c 100644 --- a/generators/swift/sdk/src/generators/client/util/__test__/format-endpoint-path-for-swift.test.ts +++ b/generators/swift/sdk/src/generators/client/util/__test__/format-endpoint-path-for-swift.test.ts @@ -1,43 +1,151 @@ -import { readdirSync } from "node:fs"; -import { resolve } from "node:path"; - -import { AbsoluteFilePath } from "@fern-api/fs-utils"; -import { createSampleIr } from "@fern-api/test-utils"; -import { IntermediateRepresentation } from "@fern-fern/ir-v59-sdk/api"; import { formatEndpointPathForSwift } from "../format-endpoint-path-for-swift.js"; +import { EndpointPathInput } from "../parse-endpoint-path.js"; -const pathToTestDefinitions = resolve(__dirname, "../../../../../../../../test-definitions/fern/apis"); -const testDefinitionNames = readdirSync(pathToTestDefinitions, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name); - -async function getIRForTestDefinition(testDefinitionName: string): Promise { - const absolutePathToWorkspace = AbsoluteFilePath.of(resolve(pathToTestDefinitions, testDefinitionName)); - return (await createSampleIr(absolutePathToWorkspace, { - version: "v59" // make sure to upgrade this when the IR version is upgraded - })) as IntermediateRepresentation; +function makeEndpoint(opts: { + head: string; + parts?: Array<{ + paramOriginalName: string; + paramCamelCase: string; + tail: string; + }>; +}): EndpointPathInput { + const parts = opts.parts ?? []; + return { + fullPath: { + head: opts.head, + parts: parts.map((p) => ({ + pathParameter: p.paramOriginalName, + tail: p.tail + })) + }, + allPathParameters: parts.map((p) => ({ + name: { + originalName: p.paramOriginalName, + camelCase: { unsafeName: p.paramCamelCase } + }, + docs: undefined + })) + }; } -describe.each(testDefinitionNames)("formatEndpointPathForSwift - %s", (testDefinitionName) => { - // This allows us to conveniently review the formatted endpoint paths for every test definition in a single location - - it("correctly formats all endpoint paths for definition", async () => { - const ir = await getIRForTestDefinition(testDefinitionName); - const endpointPathsByService = Object.fromEntries( - Object.entries(ir.services).map(([serviceName, service]) => { - return [ - serviceName, - // biome-ignore lint/suspicious/noExplicitAny: allow explicit any - service.endpoints.map((endpoint) => formatEndpointPathForSwift(endpoint as any)) - ] as const; - }) - ); - const fileContents = Object.entries(endpointPathsByService) - .map(([serviceName, paths]) => ({ serviceName, serviceContent: paths.map((p) => `"${p}"`).join("\n") })) - .map(({ serviceName, serviceContent }) => `// ${serviceName}\n${serviceContent}`) - .join("\n\n"); - await expect(fileContents).toMatchFileSnapshot( - `snapshots/formatted-endpoint-paths/${testDefinitionName}.swift` +describe("formatEndpointPathForSwift", () => { + // --- Basic path formatting --- + + it("formats a static path with no parameters", () => { + const endpoint = makeEndpoint({ head: "/users" }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/users"); + }); + + it("formats a path with a single path parameter", () => { + const endpoint = makeEndpoint({ + head: "/users/", + parts: [{ paramOriginalName: "user_id", paramCamelCase: "userId", tail: "" }] + }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/users/\\(userId)"); + }); + + it("formats a path with multiple path parameters", () => { + const endpoint = makeEndpoint({ + head: "/organizations/", + parts: [ + { paramOriginalName: "org_id", paramCamelCase: "orgId", tail: "/users/" }, + { paramOriginalName: "user_id", paramCamelCase: "userId", tail: "" } + ] + }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/organizations/\\(orgId)/users/\\(userId)"); + }); + + // --- Leading slash handling --- + + it("prepends a leading slash if missing", () => { + const endpoint = makeEndpoint({ head: "users" }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/users"); + }); + + it("prepends a leading slash when head is empty and path parameter starts the path", () => { + const endpoint = makeEndpoint({ + head: "", + parts: [{ paramOriginalName: "id", paramCamelCase: "id", tail: "" }] + }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/\\(id)"); + }); + + // --- Trailing slash handling --- + + it("strips a trailing slash", () => { + const endpoint = makeEndpoint({ head: "/users/" }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/users"); + }); + + it("preserves a single slash as the root path", () => { + const endpoint = makeEndpoint({ head: "/" }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/"); + }); + + it("strips trailing slash after path parameter tail", () => { + const endpoint = makeEndpoint({ + head: "/", + parts: [{ paramOriginalName: "id", paramCamelCase: "id", tail: "/details/" }] + }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/\\(id)/details"); + }); + + // --- Empty head handling --- + + it("handles an empty head with no parts", () => { + const endpoint = makeEndpoint({ head: "" }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/"); + }); + + // --- Missing path parameter declaration --- + + it("skips path parameters not declared in allPathParameters but keeps surrounding literals", () => { + const endpoint: EndpointPathInput = { + fullPath: { + head: "/items/", + parts: [{ pathParameter: "unknown_param", tail: "/details" }] + }, + allPathParameters: [] + }; + expect(formatEndpointPathForSwift(endpoint)).toBe("/items//details"); + }); + + // --- Consecutive path parameters --- + + it("handles consecutive path parameters with no separator", () => { + const endpoint = makeEndpoint({ + head: "/", + parts: [ + { paramOriginalName: "key", paramCamelCase: "key", tail: "" }, + { paramOriginalName: "value", paramCamelCase: "value", tail: "" } + ] + }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/\\(key)\\(value)"); + }); + + // --- Tail after path parameter --- + + it("handles a path parameter followed by a tail segment", () => { + const endpoint = makeEndpoint({ + head: "/", + parts: [{ paramOriginalName: "id", paramCamelCase: "id", tail: "/details" }] + }); + expect(formatEndpointPathForSwift(endpoint)).toBe("/\\(id)/details"); + }); + + // --- Complex / deeply nested --- + + it("handles deeply nested paths with multiple segments and parameters", () => { + const endpoint = makeEndpoint({ + head: "/api/v1/", + parts: [ + { paramOriginalName: "tenant_id", paramCamelCase: "tenantId", tail: "/resources/" }, + { paramOriginalName: "resource_id", paramCamelCase: "resourceId", tail: "/actions/" }, + { paramOriginalName: "action_id", paramCamelCase: "actionId", tail: "" } + ] + }); + expect(formatEndpointPathForSwift(endpoint)).toBe( + "/api/v1/\\(tenantId)/resources/\\(resourceId)/actions/\\(actionId)" ); - }, 10_000); + }); }); diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/accept-header.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/accept-header.swift deleted file mode 100644 index 559eaaec8b6d..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/accept-header.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/container" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias-extends.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias-extends.swift deleted file mode 100644 index fdfc1a433999..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias-extends.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/extends/extended-inline-request-body" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias.swift deleted file mode 100644 index 1c6ea4ba406f..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/alias.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/\(typeID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/any-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/any-auth.swift deleted file mode 100644 index 1177dff2cbd2..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/any-auth.swift +++ /dev/null @@ -1,6 +0,0 @@ -// service_auth -"/token" - -// service_user -"/users" -"/admins" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/api-wide-base-path.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/api-wide-base-path.swift deleted file mode 100644 index aa9e6d484f14..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/api-wide-base-path.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/test/\(pathParam)/\(serviceParam)/\(endpointParam)/\(resourceParam)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/audiences.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/audiences.swift deleted file mode 100644 index ab9b633ae30d..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/audiences.swift +++ /dev/null @@ -1,8 +0,0 @@ -// service_folder-a/service -"/" - -// service_folder-d/service -"/partner-path" - -// service_foo -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/auth-environment-variables.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/auth-environment-variables.swift deleted file mode 100644 index 14c17cc35312..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/auth-environment-variables.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_service -"/apiKey" -"/apiKeyInHeader" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth-environment-variables.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth-environment-variables.swift deleted file mode 100644 index 3b814aedef10..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth-environment-variables.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_basic-auth -"/basic-auth" -"/basic-auth" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth.swift deleted file mode 100644 index 3b814aedef10..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/basic-auth.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_basic-auth -"/basic-auth" -"/basic-auth" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bearer-token-environment-variable.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bearer-token-environment-variable.swift deleted file mode 100644 index 5641b49606df..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bearer-token-environment-variable.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/apiKey" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-download.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-download.swift deleted file mode 100644 index 71b18b7a33a5..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-download.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_service -"/snippet" -"/download-content/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-upload.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-upload.swift deleted file mode 100644 index 866db57aba0c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/bytes-upload.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/upload-content" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/circular-references-advanced.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/circular-references-advanced.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/circular-references.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/circular-references.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/client-side-params.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/client-side-params.swift deleted file mode 100644 index 9cd305d2a6e7..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/client-side-params.swift +++ /dev/null @@ -1,13 +0,0 @@ -// service_service -"/api/resources" -"/api/resources/\(resourceID)" -"/api/resources/search" -"/api/users" -"/api/users/\(userID)" -"/api/users" -"/api/users/\(userID)" -"/api/users/\(userID)" -"/api/connections" -"/api/connections/\(connectionID)" -"/api/clients" -"/api/clients/\(clientID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/content-type.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/content-type.swift deleted file mode 100644 index 9605144e5cee..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/content-type.swift +++ /dev/null @@ -1,6 +0,0 @@ -// service_service -"/" -"/complex/\(id)" -"/named-mixed/\(id)" -"/optional-merge-patch-test" -"/regular/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/cross-package-type-names.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/cross-package-type-names.swift deleted file mode 100644 index dcd4488c9b01..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/cross-package-type-names.swift +++ /dev/null @@ -1,8 +0,0 @@ -// service_folder-a/service -"/" - -// service_folder-d/service -"/" - -// service_foo -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-grpc-proto-exhaustive.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-grpc-proto-exhaustive.swift deleted file mode 100644 index 5293755f35cc..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-grpc-proto-exhaustive.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_dataservice -"/foo" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-grpc-proto.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-grpc-proto.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-collision.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-collision.swift deleted file mode 100644 index b9c1074f9cb7..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-collision.swift +++ /dev/null @@ -1,7 +0,0 @@ -// service_ -"/users" -"/users" - -// service_System -"/users" -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-conflict.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-conflict.swift deleted file mode 100644 index 7d3be5cd96cb..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-namespace-conflict.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_tasktest -"/hello" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-property-access.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-property-access.swift deleted file mode 100644 index 0d3f2f4184c4..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-property-access.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-readonly-request.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-readonly-request.swift deleted file mode 100644 index 5c183b9e003c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-readonly-request.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/vendors/batch" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-system-collision.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-system-collision.swift deleted file mode 100644 index a17e47b64a7e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-system-collision.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_ -"/users" -"/users" -"/users/empty" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-xml-entities.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-xml-entities.swift deleted file mode 100644 index 81ea339c04f0..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/csharp-xml-entities.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/timezone" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/custom-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/custom-auth.swift deleted file mode 100644 index bf5a1c2670fb..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/custom-auth.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_custom-auth -"/custom-auth" -"/custom-auth" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/dollar-string-examples.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/dollar-string-examples.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/empty-clients.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/empty-clients.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/endpoint-security-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/endpoint-security-auth.swift deleted file mode 100644 index 57f0eb71651a..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/endpoint-security-auth.swift +++ /dev/null @@ -1,11 +0,0 @@ -// service_auth -"/token" - -// service_user -"/users" -"/users" -"/users" -"/users" -"/users" -"/users" -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/enum.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/enum.swift deleted file mode 100644 index c2425fda1619..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/enum.swift +++ /dev/null @@ -1,15 +0,0 @@ -// service_headers -"/headers" - -// service_inlined-request -"/inlined" - -// service_multipart-form -"/multipart" - -// service_path-param -"/path/\(operand)/\(operandOrColor)" - -// service_query-param -"/query" -"/query-list" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/error-property.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/error-property.swift deleted file mode 100644 index f7085f71c4a6..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/error-property.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_property-based-error -"/property-based-error" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/errors.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/errors.swift deleted file mode 100644 index 450b59159b6b..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/errors.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_simple -"/foo1" -"/foo2" -"/foo3" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/examples.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/examples.swift deleted file mode 100644 index b948dcb09c28..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/examples.swift +++ /dev/null @@ -1,20 +0,0 @@ -// service_ -"/" -"/" - -// service_file/notification/service -"/file/notification/\(notificationID)" - -// service_file/service -"/file/\(filename)" - -// service_health/service -"/check/\(id)" -"/ping" - -// service_service -"/movie/\(movieID)" -"/movie" -"/metadata" -"/big-entity" -"/refresh-token" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/exhaustive.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/exhaustive.swift deleted file mode 100644 index 9f3e85a04c72..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/exhaustive.swift +++ /dev/null @@ -1,81 +0,0 @@ -// service_endpoints/container -"/container/list-of-primitives" -"/container/list-of-objects" -"/container/set-of-primitives" -"/container/set-of-objects" -"/container/map-prim-to-prim" -"/container/map-prim-to-object" -"/container/map-prim-to-union" -"/container/opt-objects" - -// service_endpoints/content-type -"/foo/bar" -"/foo/baz" - -// service_endpoints/enum -"/enum" - -// service_endpoints/http-methods -"/http-methods/\(id)" -"/http-methods" -"/http-methods/\(id)" -"/http-methods/\(id)" -"/http-methods/\(id)" - -// service_endpoints/object -"/object/get-and-return-with-optional-field" -"/object/get-and-return-with-required-field" -"/object/get-and-return-with-map-of-map" -"/object/get-and-return-nested-with-optional-field" -"/object/get-and-return-nested-with-required-field/\(string)" -"/object/get-and-return-nested-with-required-field-list" -"/object/get-and-return-with-datetime-like-string" - -// service_endpoints/pagination -"/pagination" - -// service_endpoints/params -"/params/path/\(param)" -"/params/path/\(param)" -"/params" -"/params" -"/params/path-query/\(param)" -"/params/path-query/\(param)" -"/params/path/\(param)" -"/params/path/\(param)" - -// service_endpoints/primitive -"/primitive/string" -"/primitive/integer" -"/primitive/long" -"/primitive/double" -"/primitive/boolean" -"/primitive/datetime" -"/primitive/date" -"/primitive/uuid" -"/primitive/base64" - -// service_endpoints/put -"/\(id)" - -// service_endpoints/union -"/union" - -// service_endpoints/urls -"/urls/MixedCase" -"/urls/no-ending-slash" -"/urls/with-ending-slash" -"/urls/with_underscores" - -// service_inlined-requests -"/req-bodies/object" - -// service_no-auth -"/no-auth" - -// service_no-req-body -"/no-req-body" -"/no-req-body" - -// service_req-with-headers -"/test-headers/custom-header" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extends.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extends.swift deleted file mode 100644 index fdfc1a433999..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extends.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/extends/extended-inline-request-body" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extra-properties.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extra-properties.swift deleted file mode 100644 index 236a6d0a182c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/extra-properties.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_user -"/user" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-download.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-download.swift deleted file mode 100644 index 5fd4ff59efa6..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-download.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_service -"/snippet" -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload-openapi.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload-openapi.swift deleted file mode 100644 index 204943cd2466..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload-openapi.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_fileUploadExample -"/upload-file" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload.swift deleted file mode 100644 index eb58e64c10ed..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/file-upload.swift +++ /dev/null @@ -1,13 +0,0 @@ -// service_service -"/" -"/just-file" -"/just-file-with-query-params" -"/just-file-with-optional-query-params" -"/with-content-type" -"/with-form-encoding" -"/" -"/optional-args" -"/inline-type" -"/with-json-property" -"/snippet" -"/with-literal-enum" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/folders.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/folders.swift deleted file mode 100644 index 18d3e8643874..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/folders.swift +++ /dev/null @@ -1,15 +0,0 @@ -// service_ -"/" - -// service_a/b -"/" - -// service_a/c -"/" - -// service_folder -"/" - -// service_folder/service -"/service" -"/service" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-bytes-request.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-bytes-request.swift deleted file mode 100644 index 866db57aba0c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-bytes-request.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/upload-content" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-content-type.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-content-type.swift deleted file mode 100644 index c510a8fc29db..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-content-type.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_imdb -"/movies/create-movie" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-optional-literal-alias.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-optional-literal-alias.swift deleted file mode 100644 index b9d21429e3ee..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-optional-literal-alias.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/search" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-undiscriminated-union-wire-tests.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-undiscriminated-union-wire-tests.swift deleted file mode 100644 index 7045440065b3..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/go-undiscriminated-union-wire-tests.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/rerank" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth-environment-variable.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth-environment-variable.swift deleted file mode 100644 index 5641b49606df..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth-environment-variable.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/apiKey" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth.swift deleted file mode 100644 index 5641b49606df..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/header-auth.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/apiKey" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/http-head.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/http-head.swift deleted file mode 100644 index d7b065662662..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/http-head.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_user -"/users" -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/idempotency-headers.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/idempotency-headers.swift deleted file mode 100644 index 8aaa551ade08..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/idempotency-headers.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_payment -"/payment" -"/payment/\(paymentID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/imdb.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/imdb.swift deleted file mode 100644 index 6ad601b993ae..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/imdb.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_imdb -"/movies/create-movie" -"/movies/\(movieID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-explicit.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-explicit.swift deleted file mode 100644 index a972e0ade89e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-explicit.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_auth -"/token" -"/token/refresh" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-api-key.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-api-key.swift deleted file mode 100644 index ecb7a698bf6a..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-api-key.swift +++ /dev/null @@ -1,11 +0,0 @@ -// service_auth -"/token" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-no-expiry.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-no-expiry.swift deleted file mode 100644 index a972e0ade89e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-no-expiry.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_auth -"/token" -"/token/refresh" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-reference.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-reference.swift deleted file mode 100644 index a972e0ade89e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit-reference.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_auth -"/token" -"/token/refresh" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit.swift deleted file mode 100644 index a972e0ade89e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inferred-auth-implicit.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_auth -"/token" -"/token/refresh" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inline-enum-request.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inline-enum-request.swift deleted file mode 100644 index c324af05d81e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/inline-enum-request.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/convert" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-builder-extension.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-builder-extension.swift deleted file mode 100644 index 2b741f079c36..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-builder-extension.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/api/hello" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-custom-package-prefix.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-custom-package-prefix.swift deleted file mode 100644 index 6ad601b993ae..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-custom-package-prefix.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_imdb -"/movies/create-movie" -"/movies/\(movieID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-default-timeout.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-default-timeout.swift deleted file mode 100644 index a70f2a344f97..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-default-timeout.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/user" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-inline-types.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-inline-types.swift deleted file mode 100644 index 08f4fd5496cc..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-inline-types.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_ -"/root/root" -"/root/discriminated-union" -"/root/undiscriminated-union" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-nullable-named-request-types.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-nullable-named-request-types.swift deleted file mode 100644 index d59f3395ae07..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-nullable-named-request-types.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/postWithNullableNamedRequestBodyType/\(id)" -"/postWithNonNullableNamedRequestBodyType/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-nullable-query-params.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-nullable-query-params.swift deleted file mode 100644 index 7195ccca6a80..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-nullable-query-params.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/api/search" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-query-params-overloads.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-query-params-overloads.swift deleted file mode 100644 index ca0c399c5155..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-optional-query-params-overloads.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_ -"/api/users/\(userID)/insurance" -"/api/policies/search" -"/api/policies" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-output-directory.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-output-directory.swift deleted file mode 100644 index c60e1fbf9d8c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-output-directory.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/users/\(userID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-pagination-deep-cursor-path.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-pagination-deep-cursor-path.swift deleted file mode 100644 index f92257877060..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-pagination-deep-cursor-path.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_deep-cursor-path -"/" -"/" -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-path-param-key-conflict.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-path-param-key-conflict.swift deleted file mode 100644 index 0a94bc5ad7d3..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-path-param-key-conflict.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/\(key)/\(value)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-required-body-optional-headers.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-required-body-optional-headers.swift deleted file mode 100644 index 563e699fa2fc..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-required-body-optional-headers.swift +++ /dev/null @@ -1,8 +0,0 @@ -// service_ -"/api/users" -"/api/users/\(userID)" -"/api/users/with-options" -"/api/users/required-header" -"/api/users/required-query" -"/api/users" -"/api/users/inlined" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-single-property-endpoint.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-single-property-endpoint.swift deleted file mode 100644 index 1323c2721ff6..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-single-property-endpoint.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_single-property -"/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-staged-builder-ordering.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-staged-builder-ordering.swift deleted file mode 100644 index 39f4f5c36b56..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-staged-builder-ordering.swift +++ /dev/null @@ -1,6 +0,0 @@ -// service_service -"/staged-builder/simple" -"/staged-builder/medium" -"/staged-builder/complex" -"/staged-builder/mixed" -"/staged-builder/parent" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-streaming-accept-header.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-streaming-accept-header.swift deleted file mode 100644 index ef38bd97ca8b..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-streaming-accept-header.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_dummy -"/generate-stream" -"/generate" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-with-property-conflict.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-with-property-conflict.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/license.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/license.swift deleted file mode 100644 index 9886cbe70cc1..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/license.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/literal.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/literal.swift deleted file mode 100644 index d54f3b7d7862..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/literal.swift +++ /dev/null @@ -1,14 +0,0 @@ -// service_headers -"/headers" - -// service_inlined -"/inlined" - -// service_path -"/path/\(id)" - -// service_query -"/query" - -// service_reference -"/reference" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/literals-unions.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/literals-unions.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-case.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-case.swift deleted file mode 100644 index 1c335d81aefb..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-case.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_service -"/resource/\(resourceID)" -"/resource" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-file-directory.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-file-directory.swift deleted file mode 100644 index 73d8c580f2dd..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/mixed-file-directory.swift +++ /dev/null @@ -1,11 +0,0 @@ -// service_organization -"/organizations" - -// service_user -"/users" - -// service_user/events -"/users/events" - -// service_user/events/metadata -"/users/events/metadata" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-grouping.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-grouping.swift deleted file mode 100644 index d1df6e8124e8..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-grouping.swift +++ /dev/null @@ -1,10 +0,0 @@ -// service_ -"/wallets" -"/wallets/\(walletID)" -"/wallets/\(walletID)/balance" -"/accounts" -"/transactions" -"/transactions/\(transactionID)" -"/payments" -"/payments/\(paymentID)/refund" -"/refunds" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-no-grouping.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-no-grouping.swift deleted file mode 100644 index d1df6e8124e8..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-api-environment-no-grouping.swift +++ /dev/null @@ -1,10 +0,0 @@ -// service_ -"/wallets" -"/wallets/\(walletID)" -"/wallets/\(walletID)/balance" -"/accounts" -"/transactions" -"/transactions/\(transactionID)" -"/payments" -"/payments/\(paymentID)/refund" -"/refunds" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-line-docs.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-line-docs.swift deleted file mode 100644 index d202e08630e4..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-line-docs.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_user -"/users/\(userID)" -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment-no-default.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment-no-default.swift deleted file mode 100644 index 49892658921f..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment-no-default.swift +++ /dev/null @@ -1,5 +0,0 @@ -// service_ec2 -"/ec2/boot" - -// service_s3 -"/s3/presigned-url" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment.swift deleted file mode 100644 index 49892658921f..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multi-url-environment.swift +++ /dev/null @@ -1,5 +0,0 @@ -// service_ec2 -"/ec2/boot" - -// service_s3 -"/s3/presigned-url" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multiple-request-bodies.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multiple-request-bodies.swift deleted file mode 100644 index f67033910b78..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/multiple-request-bodies.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/documents/upload" -"/documents/upload" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-environment.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-environment.swift deleted file mode 100644 index 4f6bc45b92df..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-environment.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_dummy -"/dummy" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-retries.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-retries.swift deleted file mode 100644 index f9d4b8d30ac1..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/no-retries.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_retries -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-allof-extends.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-allof-extends.swift deleted file mode 100644 index 6fa96e93070d..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-allof-extends.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/test" -"/test" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-optional.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-optional.swift deleted file mode 100644 index 0006a005e278..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-optional.swift +++ /dev/null @@ -1,14 +0,0 @@ -// service_nullable-optional -"/api/users/\(userID)" -"/api/users" -"/api/users/\(userID)" -"/api/users" -"/api/users/search" -"/api/profiles/complex" -"/api/profiles/complex/\(profileID)" -"/api/profiles/complex/\(profileID)" -"/api/test/deserialization" -"/api/users/filter" -"/api/users/\(userID)/notifications" -"/api/users/\(userID)/tags" -"/api/search" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-request-body.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-request-body.swift deleted file mode 100644 index 6c33721fc316..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable-request-body.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_testGroup -"/optional-request-body/\(pathParam)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable.swift deleted file mode 100644 index ac7c13872123..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/nullable.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_nullable -"/users" -"/users" -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-custom.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-custom.swift deleted file mode 100644 index 3fae7782a151..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-custom.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_auth -"/token" -"/token" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-default.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-default.swift deleted file mode 100644 index ecb7a698bf6a..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-default.swift +++ /dev/null @@ -1,11 +0,0 @@ -// service_auth -"/token" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-environment-variables.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-environment-variables.swift deleted file mode 100644 index 3fae7782a151..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-environment-variables.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_auth -"/token" -"/token" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-mandatory-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-mandatory-auth.swift deleted file mode 100644 index 804bea941476..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-mandatory-auth.swift +++ /dev/null @@ -1,9 +0,0 @@ -// service_auth -"/token" -"/token" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-nested-root.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-nested-root.swift deleted file mode 100644 index ecb7a698bf6a..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-nested-root.swift +++ /dev/null @@ -1,11 +0,0 @@ -// service_auth -"/token" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-reference.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-reference.swift deleted file mode 100644 index 31f00656a0e3..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-reference.swift +++ /dev/null @@ -1,5 +0,0 @@ -// service_auth -"/token" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-with-variables.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-with-variables.swift deleted file mode 100644 index 284a1e634e74..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials-with-variables.swift +++ /dev/null @@ -1,15 +0,0 @@ -// service_auth -"/token" -"/token" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_service -"/service/\(endpointParam)" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials.swift deleted file mode 100644 index 3fae7782a151..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/oauth-client-credentials.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_auth -"/token" -"/token" - -// service_nested-no-auth/api -"/nested-no-auth/get-something" - -// service_nested/api -"/nested/get-something" - -// service_simple -"/get-something" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/object.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/object.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/objects-with-imports.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/objects-with-imports.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/optional.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/optional.swift deleted file mode 100644 index cc9a25f1519c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/optional.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_optional -"/send-optional-body" -"/send-optional-typed-body" -"/deploy/\(actionID)/versions/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/package-yml.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/package-yml.swift deleted file mode 100644 index 2c06a04d4ded..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/package-yml.swift +++ /dev/null @@ -1,5 +0,0 @@ -// service_ -"/\(id)" - -// service_service -"/\(id)//\(nestedID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-custom.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-custom.swift deleted file mode 100644 index c4aea3693b95..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-custom.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_users -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-uri-path.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-uri-path.swift deleted file mode 100644 index e0b17fc8b9a6..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination-uri-path.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_users -"/users/uri" -"/users/path" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination.swift deleted file mode 100644 index bd0831e50218..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/pagination.swift +++ /dev/null @@ -1,33 +0,0 @@ -// service_complex -"/\(index)/conversations/search" - -// service_inline-users/inline-users -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" -"/inline-users" - -// service_users -"/users" -"/users" -"/users" -"/users/top-level-cursor" -"/users" -"/users" -"/users" -"/users" -"/users" -"/users" -"/users" -"/users" -"/users" -"/users" -"/users/optional-data" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/path-parameters.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/path-parameters.swift deleted file mode 100644 index 7e9df769e778..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/path-parameters.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_organizations -"/\(tenantID)/organizations/\(organizationID)" -"/\(tenantID)/organizations/\(organizationID)/users/\(userID)" -"/\(tenantID)/organizations/\(organizationID)/search" - -// service_user -"/\(tenantID)/user/\(userID)" -"/\(tenantID)/user" -"/\(tenantID)/user/\(userID)" -"/\(tenantID)/user/\(userID)/search" -"/\(tenantID)/user/\(userID)/metadata/v\(version)" -"/\(tenantID)/user/\(userID)/specifics/\(version)/\(thought)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/plain-text.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/plain-text.swift deleted file mode 100644 index 13748278446a..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/plain-text.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/text" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/property-access.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/property-access.swift deleted file mode 100644 index 0d3f2f4184c4..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/property-access.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/users" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/public-object.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/public-object.swift deleted file mode 100644 index 2220ed0b48ba..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/public-object.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/helloworld.txt" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-backslash-escape.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-backslash-escape.swift deleted file mode 100644 index 00441012f5e7..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-backslash-escape.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_user -"/users/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-mypy-exclude.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-mypy-exclude.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-positional-single-property.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-positional-single-property.swift deleted file mode 100644 index fac19a984c14..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-positional-single-property.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/create" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-streaming-parameter-openapi.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-streaming-parameter-openapi.swift deleted file mode 100644 index 00216c32ae90..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/python-streaming-parameter-openapi.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/chat" -"/chat" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi-as-objects.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi-as-objects.swift deleted file mode 100644 index 39fde7d49de4..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi-as-objects.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/user/getUsername" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi.swift deleted file mode 100644 index 39fde7d49de4..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters-openapi.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/user/getUsername" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters.swift deleted file mode 100644 index 236a6d0a182c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/query-parameters.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_user -"/user" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/request-parameters.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/request-parameters.swift deleted file mode 100644 index 1eec57fa05c6..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/request-parameters.swift +++ /dev/null @@ -1,5 +0,0 @@ -// service_user -"/user/username" -"/user/username-referenced" -"/user/username-optional" -"/user" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/required-nullable.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/required-nullable.swift deleted file mode 100644 index dbc9f5f67ae1..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/required-nullable.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/foo" -"/foo/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/reserved-keywords.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/reserved-keywords.swift deleted file mode 100644 index 39189524ef02..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/reserved-keywords.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_package -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/response-property.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/response-property.swift deleted file mode 100644 index 57d4be339f42..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/response-property.swift +++ /dev/null @@ -1,8 +0,0 @@ -// service_service -"/movie" -"/movie" -"/movie" -"/movie" -"/movie" -"/movie" -"/movie" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ruby-reserved-word-properties.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ruby-reserved-word-properties.swift deleted file mode 100644 index 688f79e3a46e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ruby-reserved-word-properties.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/ruby-reserved-word-properties/getFoo" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-event-examples.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-event-examples.swift deleted file mode 100644 index 85945355e75e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-event-examples.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_completions -"/stream" -"/stream-events" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-events.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-events.swift deleted file mode 100644 index 635cec4d24a6..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-sent-events.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_completions -"/stream" -"/stream-no-terminator" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-url-templating.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-url-templating.swift deleted file mode 100644 index b786dafb1509..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/server-url-templating.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_ -"/users" -"/users/\(userID)" -"/auth/token" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-api.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-api.swift deleted file mode 100644 index 00441012f5e7..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-api.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_user -"/users/\(id)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-fhir.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-fhir.swift deleted file mode 100644 index 966150d43825..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/simple-fhir.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/account/\(accountID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-default.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-default.swift deleted file mode 100644 index 4f6bc45b92df..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-default.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_dummy -"/dummy" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-no-default.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-no-default.swift deleted file mode 100644 index 4f6bc45b92df..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/single-url-environment-no-default.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_dummy -"/dummy" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming-parameter.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming-parameter.swift deleted file mode 100644 index e84824763c9c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming-parameter.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_dummy -"/generate" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming.swift deleted file mode 100644 index ef38bd97ca8b..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/streaming.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_dummy -"/generate-stream" -"/generate" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/trace.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/trace.swift deleted file mode 100644 index 049c51cc334c..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/trace.swift +++ /dev/null @@ -1,54 +0,0 @@ -// service_v2 -"/" - -// service_admin -"/admin/store-test-submission-status/\(submissionID)" -"/admin/store-test-submission-status-v2/\(submissionID)" -"/admin/store-workspace-submission-status/\(submissionID)" -"/admin/store-workspace-submission-status-v2/\(submissionID)" -"/admin/store-test-trace/submission/\(submissionID)/testCase/\(testCaseID)" -"/admin/store-test-trace-v2/submission/\(submissionID)/testCase/\(testCaseID)" -"/admin/store-workspace-trace/submission/\(submissionID)" -"/admin/store-workspace-trace-v2/submission/\(submissionID)" - -// service_homepage -"/homepage-problems" -"/homepage-problems" - -// service_migration -"/migration-info/all" - -// service_playlist -"/v2/playlist/\(serviceParam)/create" -"/v2/playlist/\(serviceParam)/all" -"/v2/playlist/\(serviceParam)/\(playlistID)" -"/v2/playlist/\(serviceParam)/\(playlistID)" -"/v2/playlist/\(serviceParam)/\(playlistID)" - -// service_problem -"/problem-crud/create" -"/problem-crud/update/\(problemID)" -"/problem-crud/delete/\(problemID)" -"/problem-crud/default-starter-files" - -// service_submission -"/sessions/create-session/\(language)" -"/sessions/\(sessionID)" -"/sessions/stop/\(sessionID)" -"/sessions/execution-sessions-state" - -// service_sysprop -"/sysprop/num-warm-instances/\(language)/\(numWarmInstances)" -"/sysprop/num-warm-instances" - -// service_v2/problem -"/problems-v2/lightweight-problem-info" -"/problems-v2/problem-info" -"/problems-v2/problem-info/\(problemID)" -"/problems-v2/problem-info/\(problemID)/version/\(problemVersion)" - -// service_v2/v3/problem -"/problems-v2/lightweight-problem-info" -"/problems-v2/problem-info" -"/problems-v2/problem-info/\(problemID)" -"/problems-v2/problem-info/\(problemID)/version/\(problemVersion)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-express-casing.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-express-casing.swift deleted file mode 100644 index 6ad601b993ae..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-express-casing.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_imdb -"/movies/create-movie" -"/movies/\(movieID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-extra-properties.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-extra-properties.swift deleted file mode 100644 index ec1b793ca624..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-extra-properties.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/user" -"/user" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-inline-types.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-inline-types.swift deleted file mode 100644 index 08f4fd5496cc..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/ts-inline-types.swift +++ /dev/null @@ -1,4 +0,0 @@ -// service_ -"/root/root" -"/root/discriminated-union" -"/root/undiscriminated-union" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-union-with-response-property.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-union-with-response-property.swift deleted file mode 100644 index 0b0c4abd96ce..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-union-with-response-property.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/union" -"/unions" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-unions.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-unions.swift deleted file mode 100644 index 6fc55677c97e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/undiscriminated-unions.swift +++ /dev/null @@ -1,8 +0,0 @@ -// service_union -"/" -"/metadata" -"/metadata" -"/call" -"/duplicate" -"/nested" -"/camel-case" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions-with-local-date.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions-with-local-date.swift deleted file mode 100644 index 7ffd7cd3185d..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions-with-local-date.swift +++ /dev/null @@ -1,12 +0,0 @@ -// service_bigunion -"/\(id)" -"/" -"/many" - -// service_types -"/time/\(id)" -"/time" - -// service_union -"/\(id)" -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions.swift deleted file mode 100644 index cf0b95e1a3f7..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unions.swift +++ /dev/null @@ -1,8 +0,0 @@ -// service_bigunion -"/\(id)" -"/" -"/many" - -// service_union -"/\(id)" -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unknown.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unknown.swift deleted file mode 100644 index c783d01e16a0..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/unknown.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_unknown -"/" -"/with-object" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/url-form-encoded.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/url-form-encoded.swift deleted file mode 100644 index e29f8b794d29..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/url-form-encoded.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_ -"/submit" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/validation.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/validation.swift deleted file mode 100644 index 2ea4f4c6524e..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/validation.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_ -"/create" -"/" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/variables.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/variables.swift deleted file mode 100644 index 2fdb2a0ac3d0..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/variables.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_service -"/\(endpointParam)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version-no-default.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version-no-default.swift deleted file mode 100644 index c5b5ba55bfb4..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version-no-default.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_user -"/users/\(userID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version.swift deleted file mode 100644 index c5b5ba55bfb4..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/version.swift +++ /dev/null @@ -1,2 +0,0 @@ -// service_user -"/users/\(userID)" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/webhook-audience.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/webhook-audience.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket-bearer-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket-bearer-auth.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket-inferred-auth.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket-inferred-auth.swift deleted file mode 100644 index e95dc947c264..000000000000 --- a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket-inferred-auth.swift +++ /dev/null @@ -1,3 +0,0 @@ -// service_auth -"/token" -"/token/refresh" \ No newline at end of file diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/websocket.swift deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/generators/swift/sdk/src/generators/client/util/format-endpoint-path-for-swift.ts b/generators/swift/sdk/src/generators/client/util/format-endpoint-path-for-swift.ts index 28a98b4e1fad..1badf480fa24 100644 --- a/generators/swift/sdk/src/generators/client/util/format-endpoint-path-for-swift.ts +++ b/generators/swift/sdk/src/generators/client/util/format-endpoint-path-for-swift.ts @@ -1,7 +1,6 @@ -import { FernIr } from "@fern-fern/ir-sdk"; -import { parseEndpointPath } from "./parse-endpoint-path.js"; +import { EndpointPathInput, parseEndpointPath } from "./parse-endpoint-path.js"; -export function formatEndpointPathForSwift(endpoint: FernIr.HttpEndpoint): string { +export function formatEndpointPathForSwift(endpoint: EndpointPathInput): string { let path = ""; const { pathParts } = parseEndpointPath(endpoint); pathParts.forEach((pathPart) => { diff --git a/generators/swift/sdk/src/generators/client/util/parse-endpoint-path.ts b/generators/swift/sdk/src/generators/client/util/parse-endpoint-path.ts index f401124f9509..55506cbb19af 100644 --- a/generators/swift/sdk/src/generators/client/util/parse-endpoint-path.ts +++ b/generators/swift/sdk/src/generators/client/util/parse-endpoint-path.ts @@ -1,4 +1,13 @@ -import { FernIr } from "@fern-fern/ir-sdk"; +export interface EndpointPathInput { + fullPath: { + head: string; + parts: Array<{ pathParameter: string; tail: string }>; + }; + allPathParameters: Array<{ + name: { originalName: string; camelCase: { unsafeName: string } }; + docs: string | undefined; + }>; +} type PathPart = | { @@ -15,7 +24,7 @@ export type ParseEndpointPathResult = { pathParts: PathPart[]; }; -export function parseEndpointPath(endpoint: FernIr.HttpEndpoint): ParseEndpointPathResult { +export function parseEndpointPath(endpoint: EndpointPathInput): ParseEndpointPathResult { const pathParts: PathPart[] = []; const pathParameterInfosByOriginalName = Object.fromEntries(