From 9a19f6713f53eb2ac34322286d1ca9600a5a8d3a Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 10:14:12 +0000 Subject: [PATCH 1/2] [fern-generated] Update SDK Generated by Fern CLI Version: unknown Generators: - fernapi/fern-typescript-node-sdk: 3.88.1 --- .fern/metadata.json | 8 +- package.json | 5 +- reference.md | 5 - src/BaseClient.ts | 10 +- src/Client.ts | 3 + src/api/errors/BadRequestError.ts | 4 +- src/api/errors/ForbiddenError.ts | 4 +- src/api/errors/InternalServerError.ts | 4 +- src/api/errors/NotFoundError.ts | 4 +- src/api/errors/UnauthorizedError.ts | 4 +- .../resources/accesstokens/client/Client.ts | 18 +- src/api/resources/accounts/client/Client.ts | 70 +++++++- src/api/resources/billing/client/Client.ts | 95 +++++++++-- src/api/resources/catalogs/client/Client.ts | 48 +++++- src/api/resources/checkout/client/Client.ts | 71 ++++++-- src/api/resources/companies/client/Client.ts | 159 ++++++++++++++++-- src/api/resources/components/client/Client.ts | 32 +++- .../componentspublic/client/Client.ts | 2 + src/api/resources/credits/client/Client.ts | 128 ++++++++++++-- .../resources/dataexports/client/Client.ts | 14 +- .../resources/entitlements/client/Client.ts | 97 +++++++++-- src/api/resources/events/client/Client.ts | 23 ++- src/api/resources/features/client/Client.ts | 84 +++++++-- src/api/resources/insights/client/Client.ts | 12 ++ .../integrationsapi/client/Client.ts | 39 ++++- src/api/resources/licenses/client/Client.ts | 6 + src/api/resources/planbundle/client/Client.ts | 26 ++- .../CreateCustomPlanBundleRequestBody.ts | 1 - .../requests/CreatePlanBundleRequestBody.ts | 1 - src/api/resources/plangroups/client/Client.ts | 17 +- .../resources/planmigrations/client/Client.ts | 34 +++- src/api/resources/plans/client/Client.ts | 95 +++++++++-- .../CreateBillingLinkedPlanRequestBody.ts | 3 +- .../requests/CreateCustomPlanRequestBody.ts | 3 +- .../scheduledcheckout/client/Client.ts | 19 ++- src/api/resources/webhooks/client/Client.ts | 36 +++- src/api/types/AuditLogResponseData.ts | 1 + .../BillingPlanCreditGrantResponseData.ts | 2 +- src/api/types/ComponentHydrateResponseData.ts | 2 +- src/api/types/ComponentPreviewResponseData.ts | 2 +- ...CreateBillingPlanCreditGrantRequestBody.ts | 1 + .../CreateCustomPlanBundlePlanRequestBody.ts | 2 +- src/api/types/CreatePlanRequestBody.ts | 2 +- src/api/types/OnboardingRequirementView.ts | 1 + src/api/types/PlanChangeResponseData.ts | 6 + .../PreviewSubscriptionFinanceResponseData.ts | 1 + src/api/types/StripeIntegrationConfig.ts | 2 + src/api/types/UpcomingInvoiceResponseData.ts | 26 +++ ...UpdateBillingPlanCreditGrantRequestBody.ts | 1 + src/api/types/index.ts | 1 + src/core/fetcher/Fetcher.ts | 104 +----------- src/core/fetcher/Headers.ts | 8 +- src/core/fetcher/getResponseBody.ts | 18 +- src/core/fetcher/makePassthroughRequest.ts | 30 +++- src/core/fetcher/redactUrl.ts | 102 +++++++++++ src/core/fetcher/signals.ts | 11 +- src/core/requestBody.ts | 26 +++ src/core/runtime/index.ts | 2 +- src/core/runtime/runtime.ts | 97 +++++++++++ .../builders/schema-utils/JsonError.ts | 4 +- .../builders/schema-utils/ParseError.ts | 4 +- src/core/url/qs.ts | 4 +- src/errors/SchematicError.ts | 6 +- src/errors/SchematicTimeoutError.ts | 14 +- .../CreateBillingLinkedPlanRequestBody.ts | 4 +- .../requests/CreateCustomPlanRequestBody.ts | 4 +- .../types/AuditLogResponseData.ts | 2 + .../types/ComponentHydrateResponseData.ts | 6 +- .../types/ComponentPreviewResponseData.ts | 5 +- .../CreateCustomPlanBundlePlanRequestBody.ts | 4 +- .../types/CreatePlanRequestBody.ts | 4 +- .../types/OnboardingRequirementView.ts | 2 + .../types/PlanChangeResponseData.ts | 7 + .../PreviewSubscriptionFinanceResponseData.ts | 2 + .../types/StripeIntegrationConfig.ts | 2 + .../types/UpcomingInvoiceResponseData.ts | 64 +++++++ src/serialization/types/index.ts | 1 + tests/unit/fetcher/createRequestUrl.test.ts | 2 +- tests/unit/fetcher/getResponseBody.test.ts | 28 +++ .../fetcher/makePassthroughRequest.test.ts | 106 ++++++++++++ tests/unit/fetcher/signals.test.ts | 45 +++++ tests/unit/url/qs.test.ts | 4 +- tests/wire/accounts.test.ts | 2 + tests/wire/checkout.test.ts | 2 + tests/wire/companies.test.ts | 12 ++ tests/wire/components.test.ts | 2 - tests/wire/planbundle.test.ts | 39 ++--- tests/wire/plans.test.ts | 48 ++---- yarn.lock | 42 ++--- 89 files changed, 1688 insertions(+), 415 deletions(-) create mode 100644 src/api/types/UpcomingInvoiceResponseData.ts create mode 100644 src/core/fetcher/redactUrl.ts create mode 100644 src/core/requestBody.ts create mode 100644 src/serialization/types/UpcomingInvoiceResponseData.ts diff --git a/.fern/metadata.json b/.fern/metadata.json index eb92b49f..dae8d301 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,7 +1,7 @@ { "cliVersion": "5.50.0", "generatorName": "fernapi/fern-typescript-node-sdk", - "generatorVersion": "3.70.7", + "generatorVersion": "3.88.1", "generatorConfig": { "allowCustomFetcher": true, "includeApiReference": true, @@ -53,10 +53,10 @@ ] } }, - "originGitCommit": "2b057076e5ae2f32f2925743ebe8a584917851cc", + "originGitCommit": "008b7147a27272499bf4b17fa96d4357ca3b62e9", "originGitCommitIsDirty": false, "invokedBy": "ci", - "requestedVersion": "1.5.9", + "requestedVersion": "1.5.10", "ciProvider": "github", - "sdkVersion": "1.5.9" + "sdkVersion": "1.5.10" } diff --git a/package.json b/package.json index 39d7c7de..d5a73acf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@schematichq/schematic-typescript-node", - "version": "1.5.9", + "version": "1.5.10", "private": false, "repository": { "type": "git", @@ -57,9 +57,6 @@ "crypto": false, "timers": false }, - "resolutions": { - "undici": "7.29.0" - }, "packageManager": "yarn@1.22.22", "engines": { "node": ">=18.0.0" diff --git a/reference.md b/reference.md index 9ad5824d..639c3e05 100644 --- a/reference.md +++ b/reference.md @@ -9360,7 +9360,6 @@ await client.plans.retryCustomPlanBilling("custom_plan_billing_id", { ```typescript await client.plans.createCustomPlan({ companyId: "company_id", - description: "description", name: "name" }); @@ -9479,7 +9478,6 @@ await client.plans.listPlans({ ```typescript await client.plans.createPlan({ - description: "description", name: "name", planType: "plan" }); @@ -9760,7 +9758,6 @@ await client.plans.upsertBillingProductPlan("plan_id", { ```typescript await client.plans.upsertPlanForBillingProduct({ billingProvider: "metronome", - description: "description", externalResourceId: "external_resource_id", name: "name", planType: "plan" @@ -10593,7 +10590,6 @@ await client.planbundle.createCustomPlanBundle({ }], plan: { companyId: "company_id", - description: "description", name: "name" } }); @@ -10650,7 +10646,6 @@ await client.planbundle.createPlanBundle({ action: "create" }], plan: { - description: "description", name: "name", planType: "plan" } diff --git a/src/BaseClient.ts b/src/BaseClient.ts index 123b030c..b08c1e0e 100644 --- a/src/BaseClient.ts +++ b/src/BaseClient.ts @@ -26,6 +26,8 @@ export type BaseClientOptions = { fetcher?: core.FetchFunction; /** Configure logging for the client. */ logging?: core.logging.LogConfig | core.logging.Logger; + /** Default options for SSE stream reconnection behavior. Has no effect on non-resumable endpoints. */ + stream?: { reconnectionEnabled?: boolean; maxReconnectionAttempts?: number }; /** Override auth. Pass false to disable, a function returning auth headers, an AuthProvider, or auth options. */ auth?: AuthOption; } & HeaderAuthProvider.AuthOptions; @@ -39,8 +41,12 @@ export interface BaseRequestOptions { abortSignal?: AbortSignal; /** Additional query string parameters to include in the request. */ queryParams?: Record; + /** A dictionary containing additional parameters to spread into the request's body. */ + additionalBodyParameters?: Record; /** Additional headers to include in the request. */ headers?: Record | null | undefined>; + /** Options for SSE stream reconnection behavior. Has no effect on non-resumable endpoints. */ + stream?: { reconnectionEnabled?: boolean; maxReconnectionAttempts?: number }; } export type NormalizedClientOptions = T & { @@ -60,8 +66,8 @@ export function normalizeClientOptions(args: Fetcher.Args) => Promise>; @@ -103,27 +104,6 @@ function redactHeaders(headers: Headers | Record): Record | undefined, ): Record | undefined { @@ -137,88 +117,6 @@ function redactQueryParameters( return redacted; } -function redactUrl(url: string): string { - const protocolIndex = url.indexOf("://"); - if (protocolIndex === -1) return url; - - const afterProtocol = protocolIndex + 3; - - // Find the first delimiter that marks the end of the authority section - const pathStart = url.indexOf("/", afterProtocol); - let queryStart = url.indexOf("?", afterProtocol); - let fragmentStart = url.indexOf("#", afterProtocol); - - const firstDelimiter = Math.min( - pathStart === -1 ? url.length : pathStart, - queryStart === -1 ? url.length : queryStart, - fragmentStart === -1 ? url.length : fragmentStart, - ); - - // Find the LAST @ before the delimiter (handles multiple @ in credentials) - let atIndex = -1; - for (let i = afterProtocol; i < firstDelimiter; i++) { - if (url[i] === "@") { - atIndex = i; - } - } - - if (atIndex !== -1) { - url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`; - } - - // Recalculate queryStart since url might have changed - queryStart = url.indexOf("?"); - if (queryStart === -1) return url; - - fragmentStart = url.indexOf("#", queryStart); - const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length; - const queryString = url.slice(queryStart + 1, queryEnd); - - if (queryString.length === 0) return url; - - // FAST PATH: Quick check if any sensitive keywords present - // Using indexOf is faster than regex for simple substring matching - const lower = queryString.toLowerCase(); - const hasSensitive = - lower.includes("token") || - lower.includes("key") || - lower.includes("password") || - lower.includes("passwd") || - lower.includes("secret") || - lower.includes("session") || - lower.includes("auth"); - - if (!hasSensitive) { - return url; - } - - // SLOW PATH: Parse and redact - const redactedParams: string[] = []; - const params = queryString.split("&"); - - for (const param of params) { - const equalIndex = param.indexOf("="); - if (equalIndex === -1) { - redactedParams.push(param); - continue; - } - - const key = param.slice(0, equalIndex); - let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()); - - if (!shouldRedact && key.includes("%")) { - try { - const decodedKey = decodeURIComponent(key); - shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase()); - } catch {} - } - - redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param); - } - - return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd); -} - async function getHeaders(args: Fetcher.Args): Promise { const newHeaders: Headers = new Headers(); diff --git a/src/core/fetcher/Headers.ts b/src/core/fetcher/Headers.ts index af841aa2..f02246c5 100644 --- a/src/core/fetcher/Headers.ts +++ b/src/core/fetcher/Headers.ts @@ -68,23 +68,23 @@ if (typeof globalThis.Headers !== "undefined") { return this.headers.get("set-cookie") || []; } - *entries(): HeadersIterator<[string, string]> { + *entries(): IterableIterator<[string, string]> { for (const [key, values] of this.headers.entries()) { yield [key, values.join(", ")]; } } - *keys(): HeadersIterator { + *keys(): IterableIterator { yield* this.headers.keys(); } - *values(): HeadersIterator { + *values(): IterableIterator { for (const values of this.headers.values()) { yield values.join(", "); } } - [Symbol.iterator](): HeadersIterator<[string, string]> { + [Symbol.iterator](): IterableIterator<[string, string]> { return this.entries(); } }; diff --git a/src/core/fetcher/getResponseBody.ts b/src/core/fetcher/getResponseBody.ts index 87922835..3226cdd2 100644 --- a/src/core/fetcher/getResponseBody.ts +++ b/src/core/fetcher/getResponseBody.ts @@ -3,6 +3,16 @@ import { getBinaryResponse } from "./BinaryResponse"; import { chooseStreamWrapper } from "./stream-wrappers/chooseStreamWrapper"; +// Pins the upstream Response so undici's FinalizationRegistry can't GC it and cancel the body stream. +function retainResponse(target: object, response: Response): void { + Object.defineProperty(target, "__fern_response_ref", { + value: response, + enumerable: false, + configurable: true, + writable: false, + }); +} + export async function getResponseBody(response: Response, responseType?: string): Promise { switch (responseType) { case "binary-response": @@ -21,8 +31,9 @@ export async function getResponseBody(response: Response, responseType?: string) }, }; } + retainResponse(response.body, response); return response.body; - case "streaming": + case "streaming": { if (response.body == null) { return { ok: false, @@ -33,7 +44,10 @@ export async function getResponseBody(response: Response, responseType?: string) }; } - return chooseStreamWrapper(response.body); + const wrapper = await chooseStreamWrapper(response.body); + retainResponse(wrapper, response); + return wrapper; + } case "text": return await response.text(); diff --git a/src/core/fetcher/makePassthroughRequest.ts b/src/core/fetcher/makePassthroughRequest.ts index 82166dcf..88ae6122 100644 --- a/src/core/fetcher/makePassthroughRequest.ts +++ b/src/core/fetcher/makePassthroughRequest.ts @@ -3,6 +3,7 @@ import { join } from "../url/join"; import { EndpointSupplier } from "./EndpointSupplier"; import { getFetchFn } from "./getFetchFn"; import { makeRequest } from "./makeRequest"; +import { redactUrl } from "./redactUrl"; import { requestWithRetries } from "./requestWithRetries"; import { Supplier } from "./Supplier"; @@ -114,8 +115,10 @@ export async function makePassthroughRequest( } } - // Apply auth headers - if (clientOptions.getAuthHeaders != null) { + // Apply auth headers, but only when the resolved URL targets the configured base URL. + // This prevents the SDK's credentials from leaking to an unrelated host when a caller + // passes an absolute cross-origin URL into the passthrough fetch escape hatch. + if (clientOptions.getAuthHeaders != null && targetsBaseUrl(fullUrl, baseUrl)) { const authHeaders = await clientOptions.getAuthHeaders(); for (const [key, value] of Object.entries(authHeaders)) { mergedHeaders[key.toLowerCase()] = value; @@ -155,7 +158,7 @@ export async function makePassthroughRequest( if (logger.isDebug()) { logger.debug("Making passthrough HTTP request", { method, - url: fullUrl, + url: redactUrl(fullUrl), hasBody: body != null, }); } @@ -180,10 +183,29 @@ export async function makePassthroughRequest( if (logger.isDebug()) { logger.debug("Passthrough HTTP request completed", { method, - url: fullUrl, + url: redactUrl(fullUrl), statusCode: response.status, }); } return response; } + +/** + * Returns true when the resolved request URL points at the same origin as the + * configured base URL. Relative paths are always joined onto the base URL, so + * they resolve to the base origin and return true. Absolute URLs only match when + * their origin equals the base origin. When there is no base URL to compare + * against, or either value is not a parseable absolute URL, this returns false so + * auth headers are not attached. + */ +function targetsBaseUrl(fullUrl: string, baseUrl: string | undefined): boolean { + if (baseUrl == null) { + return false; + } + try { + return new URL(fullUrl).origin === new URL(baseUrl).origin; + } catch { + return false; + } +} diff --git a/src/core/fetcher/redactUrl.ts b/src/core/fetcher/redactUrl.ts new file mode 100644 index 00000000..3c2e897a --- /dev/null +++ b/src/core/fetcher/redactUrl.ts @@ -0,0 +1,102 @@ +export const SENSITIVE_QUERY_PARAMS: Set = new Set([ + "api_key", + "api-key", + "apikey", + "token", + "access_token", + "access-token", + "auth_token", + "auth-token", + "password", + "passwd", + "secret", + "api_secret", + "api-secret", + "apisecret", + "key", + "session", + "session_id", + "session-id", +]); + +export function redactUrl(url: string): string { + const protocolIndex = url.indexOf("://"); + if (protocolIndex === -1) return url; + + const afterProtocol = protocolIndex + 3; + + // Find the first delimiter that marks the end of the authority section + const pathStart = url.indexOf("/", afterProtocol); + let queryStart = url.indexOf("?", afterProtocol); + let fragmentStart = url.indexOf("#", afterProtocol); + + const firstDelimiter = Math.min( + pathStart === -1 ? url.length : pathStart, + queryStart === -1 ? url.length : queryStart, + fragmentStart === -1 ? url.length : fragmentStart, + ); + + // Find the LAST @ before the delimiter (handles multiple @ in credentials) + let atIndex = -1; + for (let i = afterProtocol; i < firstDelimiter; i++) { + if (url[i] === "@") { + atIndex = i; + } + } + + if (atIndex !== -1) { + url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`; + } + + // Recalculate queryStart since url might have changed + queryStart = url.indexOf("?"); + if (queryStart === -1) return url; + + fragmentStart = url.indexOf("#", queryStart); + const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length; + const queryString = url.slice(queryStart + 1, queryEnd); + + if (queryString.length === 0) return url; + + // FAST PATH: Quick check if any sensitive keywords present + // Using indexOf is faster than regex for simple substring matching + const lower = queryString.toLowerCase(); + const hasSensitive = + lower.includes("token") || + lower.includes("key") || + lower.includes("password") || + lower.includes("passwd") || + lower.includes("secret") || + lower.includes("session") || + lower.includes("auth"); + + if (!hasSensitive) { + return url; + } + + // SLOW PATH: Parse and redact + const redactedParams: string[] = []; + const params = queryString.split("&"); + + for (const param of params) { + const equalIndex = param.indexOf("="); + if (equalIndex === -1) { + redactedParams.push(param); + continue; + } + + const key = param.slice(0, equalIndex); + let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase()); + + if (!shouldRedact && key.includes("%")) { + try { + const decodedKey = decodeURIComponent(key); + shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase()); + } catch {} + } + + redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param); + } + + return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd); +} diff --git a/src/core/fetcher/signals.ts b/src/core/fetcher/signals.ts index 7bd3757e..ba74c4d0 100644 --- a/src/core/fetcher/signals.ts +++ b/src/core/fetcher/signals.ts @@ -14,12 +14,21 @@ export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal for (const signal of signals) { if (signal.aborted) { controller.abort((signal as any)?.reason); - break; + return controller.signal; } signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { signal: controller.signal, }); + + // Re-check after adding listener: the signal may have aborted + // between the initial `signal.aborted` check and the `addEventListener` + // call above. If it did, the abort event was already dispatched and + // the listener will never fire — we must manually abort. + if (signal.aborted) { + controller.abort((signal as any)?.reason); + return controller.signal; + } } return controller.signal; diff --git a/src/core/requestBody.ts b/src/core/requestBody.ts new file mode 100644 index 00000000..d58ef590 --- /dev/null +++ b/src/core/requestBody.ts @@ -0,0 +1,26 @@ +/** + * Spreads caller-supplied `additionalBodyParameters` (from `requestOptions.additionalBodyParameters`) + * on top of the request body. Caller-supplied properties win over the endpoint body. When no + * additional body parameters are provided, the original body is returned unchanged so serialization + * is unaffected. + * + * The merge only applies to plain-object (JSON object) bodies. When the body is `null`/`undefined` + * the additional parameters become the body; when the body is an array or a primitive JSON value it + * is returned unchanged, since object properties cannot be spread into it. This mirrors the Python + * SDK, which only merges additional body parameters into mapping bodies. + */ +export function mergeAdditionalBodyParameters( + body: unknown, + additionalBodyParameters: Record | undefined, +): unknown { + if (additionalBodyParameters == null) { + return body; + } + if (body == null) { + return { ...additionalBodyParameters }; + } + if (typeof body === "object" && !Array.isArray(body)) { + return { ...body, ...additionalBodyParameters }; + } + return body; +} diff --git a/src/core/runtime/index.ts b/src/core/runtime/index.ts index 5c76dbb1..2947db83 100644 --- a/src/core/runtime/index.ts +++ b/src/core/runtime/index.ts @@ -1 +1 @@ -export { RUNTIME } from "./runtime"; +export { getUserAgent, RUNTIME } from "./runtime"; diff --git a/src/core/runtime/runtime.ts b/src/core/runtime/runtime.ts index e6e66b2a..d367ce6b 100644 --- a/src/core/runtime/runtime.ts +++ b/src/core/runtime/runtime.ts @@ -2,6 +2,10 @@ interface DenoGlobal { version: { deno: string; }; + build?: { + os?: string; + arch?: string; + }; } interface BunGlobal { @@ -24,6 +28,18 @@ export interface Runtime { type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd" | "edge-runtime"; version?: string; parsedVersion?: number; + /** + * The operating system the SDK is running on, when it can be determined + * (e.g. "linux", "darwin", "win32" on server runtimes). Undefined in + * environments where the OS is not observable (e.g. browsers). + */ + os?: string; + /** + * The CPU architecture the SDK is running on, when it can be determined + * (e.g. "x64", "arm64" on server runtimes). Undefined in environments where + * the architecture is not observable (e.g. browsers). + */ + arch?: string; } function evaluateRuntime(): Runtime { @@ -85,6 +101,8 @@ function evaluateRuntime(): Runtime { return { type: "deno", version: Deno.version.deno, + os: Deno.build?.os, + arch: Deno.build?.arch, }; } @@ -96,6 +114,8 @@ function evaluateRuntime(): Runtime { return { type: "bun", version: Bun.version, + os: typeof process !== "undefined" ? process.platform : undefined, + arch: typeof process !== "undefined" ? process.arch : undefined, }; } @@ -125,6 +145,8 @@ function evaluateRuntime(): Runtime { type: "node", version: _process.versions.node, parsedVersion: Number(_process.versions.node.split(".")[0]), + os: _process.platform, + arch: _process.arch, }; } @@ -132,3 +154,78 @@ function evaluateRuntime(): Runtime { type: "unknown", }; } + +/** + * Display names for the language runtimes whose version is meaningful to encode + * in a User-Agent. Environments where a version string is not useful (e.g. + * browsers, where `version` is the full navigator UA) are intentionally mapped + * to `undefined` so they are omitted from the User-Agent. + */ +const RUNTIME_DISPLAY_NAMES: Record = { + node: "Node", + deno: "Deno", + bun: "Bun", + browser: undefined, + "web-worker": undefined, + "react-native": undefined, + workerd: undefined, + "edge-runtime": undefined, + unknown: undefined, +}; + +/** + * CPU architecture aliases that all refer to 64-bit x86. They are normalized to + * the single canonical token `x86_64` so the User-Agent architecture label is + * consistent regardless of which runtime reports it (Node reports `x64`, others + * report `amd64` or `x86_64`). + */ +const X86_64_ARCH_ALIASES = new Set(["x64", "amd64", "x86_64"]); + +/** + * Normalizes a CPU architecture token, collapsing the 64-bit x86 aliases + * (`x64`, `amd64`, `x86_64`) to `x86_64`. Other architectures are returned + * unchanged. + */ +function normalizeArch(arch: string | undefined): string | undefined { + if (arch == null) { + return arch; + } + return X86_64_ARCH_ALIASES.has(arch.toLowerCase()) ? "x86_64" : arch; +} + +/** + * Percent-encodes the `@` and `/` characters in an npm package name so the + * User-Agent product token stays within the RFC 7230 token grammar. The + * original scoped package name can be recovered by URL-decoding (e.g. + * `@dummy/sdk` becomes `%40dummy%2Fsdk`). + */ +function encodeProductName(sdkName: string): string { + return sdkName.replace(/@/g, "%40").replace(/\//g, "%2F"); +} + +/** + * Builds a structured User-Agent string of the form + * `{sdkName}/{sdkVersion} ({os}; {arch}) {runtime}/{runtimeVersion}` + * where the platform group and runtime segment are omitted gracefully when the + * underlying values cannot be determined (e.g. in a browser). + */ +export function getUserAgent(sdkName: string, sdkVersion: string): string { + let userAgent = `${encodeProductName(sdkName)}/${sdkVersion}`; + + const platform = [RUNTIME.os, normalizeArch(RUNTIME.arch)].filter( + (part): part is string => part != null && part.length > 0, + ); + if (platform.length > 0) { + userAgent += ` (${platform.join("; ")})`; + } + + const runtimeName = RUNTIME_DISPLAY_NAMES[RUNTIME.type]; + if (runtimeName != null) { + userAgent += ` ${runtimeName}`; + if (RUNTIME.version != null && RUNTIME.version.length > 0) { + userAgent += `/${RUNTIME.version}`; + } + } + + return userAgent; +} diff --git a/src/core/schemas/builders/schema-utils/JsonError.ts b/src/core/schemas/builders/schema-utils/JsonError.ts index 9f813059..4f21ff3f 100644 --- a/src/core/schemas/builders/schema-utils/JsonError.ts +++ b/src/core/schemas/builders/schema-utils/JsonError.ts @@ -4,7 +4,7 @@ import { stringifyValidationError } from "./stringifyValidationErrors"; export class JsonError extends Error { constructor(public readonly errors: ValidationError[]) { super(errors.map(stringifyValidationError).join("; ")); - Object.setPrototypeOf(this, new.target.prototype); - this.name = this.constructor.name; + Object.setPrototypeOf(this, JsonError.prototype); + this.name = "JsonError"; } } diff --git a/src/core/schemas/builders/schema-utils/ParseError.ts b/src/core/schemas/builders/schema-utils/ParseError.ts index a643165c..2340120b 100644 --- a/src/core/schemas/builders/schema-utils/ParseError.ts +++ b/src/core/schemas/builders/schema-utils/ParseError.ts @@ -4,7 +4,7 @@ import { stringifyValidationError } from "./stringifyValidationErrors"; export class ParseError extends Error { constructor(public readonly errors: ValidationError[]) { super(errors.map(stringifyValidationError).join("; ")); - Object.setPrototypeOf(this, new.target.prototype); - this.name = this.constructor.name; + Object.setPrototypeOf(this, ParseError.prototype); + this.name = "ParseError"; } } diff --git a/src/core/url/qs.ts b/src/core/url/qs.ts index f8b45565..aebb95a3 100644 --- a/src/core/url/qs.ts +++ b/src/core/url/qs.ts @@ -27,7 +27,7 @@ function stringifyObject(obj: Record, prefix = "", options: Req for (const [key, value] of Object.entries(obj)) { const fullKey = prefix ? `${prefix}[${key}]` : key; - if (value === undefined) { + if (value == null) { continue; } @@ -47,7 +47,7 @@ function stringifyObject(obj: Record, prefix = "", options: Req } else { for (let i = 0; i < value.length; i++) { const item = value[i]; - if (item === undefined) { + if (item == null) { continue; } if (typeof item === "object" && !Array.isArray(item) && item !== null) { diff --git a/src/errors/SchematicError.ts b/src/errors/SchematicError.ts index 8a47e665..7794636c 100644 --- a/src/errors/SchematicError.ts +++ b/src/errors/SchematicError.ts @@ -28,7 +28,7 @@ export class SchematicError extends Error { Error.captureStackTrace(this, this.constructor); } - this.name = this.constructor.name; + this.name = "SchematicError"; this.statusCode = statusCode; this.body = body; this.rawResponse = rawResponse; @@ -36,6 +36,10 @@ export class SchematicError extends Error { this.cause = cause; } } + + public get requestId(): string | undefined { + return this.rawResponse?.headers?.get("x-request-id") ?? undefined; + } } function buildMessage({ diff --git a/src/errors/SchematicTimeoutError.ts b/src/errors/SchematicTimeoutError.ts index 66707a80..60e89732 100644 --- a/src/errors/SchematicTimeoutError.ts +++ b/src/errors/SchematicTimeoutError.ts @@ -1,18 +1,18 @@ // This file was auto-generated by Fern from our API Definition. -export class SchematicTimeoutError extends Error { - public readonly cause?: unknown; +import * as errors from "./index"; +export class SchematicTimeoutError extends errors.SchematicError { constructor(message: string, opts?: { cause?: unknown }) { - super(message); + super({ + message: message, + cause: opts?.cause, + }); Object.setPrototypeOf(this, new.target.prototype); if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } - this.name = this.constructor.name; - if (opts?.cause != null) { - this.cause = opts.cause; - } + this.name = "SchematicTimeoutError"; } } diff --git a/src/serialization/resources/plans/client/requests/CreateBillingLinkedPlanRequestBody.ts b/src/serialization/resources/plans/client/requests/CreateBillingLinkedPlanRequestBody.ts index 8cda2bea..35177a8e 100644 --- a/src/serialization/resources/plans/client/requests/CreateBillingLinkedPlanRequestBody.ts +++ b/src/serialization/resources/plans/client/requests/CreateBillingLinkedPlanRequestBody.ts @@ -12,7 +12,7 @@ export const CreateBillingLinkedPlanRequestBody: core.serialization.Schema< Schematic.CreateBillingLinkedPlanRequestBody > = core.serialization.object({ billingProvider: core.serialization.property("billing_provider", BillingProviderType), - description: core.serialization.string(), + description: core.serialization.string().optional(), externalResourceId: core.serialization.property("external_resource_id", core.serialization.string()), externalResourceVersion: core.serialization.property( "external_resource_version", @@ -26,7 +26,7 @@ export const CreateBillingLinkedPlanRequestBody: core.serialization.Schema< export declare namespace CreateBillingLinkedPlanRequestBody { export interface Raw { billing_provider: BillingProviderType.Raw; - description: string; + description?: string | null; external_resource_id: string; external_resource_version?: string | null; icon?: PlanIcon.Raw | null; diff --git a/src/serialization/resources/plans/client/requests/CreateCustomPlanRequestBody.ts b/src/serialization/resources/plans/client/requests/CreateCustomPlanRequestBody.ts index e4d7011e..9c82778e 100644 --- a/src/serialization/resources/plans/client/requests/CreateCustomPlanRequestBody.ts +++ b/src/serialization/resources/plans/client/requests/CreateCustomPlanRequestBody.ts @@ -12,7 +12,7 @@ export const CreateCustomPlanRequestBody: core.serialization.Schema< companyId: core.serialization.property("company_id", core.serialization.string()), copiedFromPlanId: core.serialization.property("copied_from_plan_id", core.serialization.string().optional()), copiedPriceId: core.serialization.property("copied_price_id", core.serialization.string().optional()), - description: core.serialization.string(), + description: core.serialization.string().optional(), icon: PlanIcon.optional(), name: core.serialization.string(), }); @@ -22,7 +22,7 @@ export declare namespace CreateCustomPlanRequestBody { company_id: string; copied_from_plan_id?: string | null; copied_price_id?: string | null; - description: string; + description?: string | null; icon?: PlanIcon.Raw | null; name: string; } diff --git a/src/serialization/types/AuditLogResponseData.ts b/src/serialization/types/AuditLogResponseData.ts index 0e6eb9ff..8c4370c8 100644 --- a/src/serialization/types/AuditLogResponseData.ts +++ b/src/serialization/types/AuditLogResponseData.ts @@ -11,6 +11,7 @@ export const AuditLogResponseData: core.serialization.ObjectSchema< serializers.AuditLogResponseData.Raw, Schematic.AuditLogResponseData > = core.serialization.object({ + accountMemberId: core.serialization.property("account_member_id", core.serialization.string().optional()), actorType: core.serialization.property("actor_type", ActorType), apiKey: core.serialization.property("api_key", ApiKeyResponseData.optional()), apiKeyId: core.serialization.property("api_key_id", core.serialization.string().optional()), @@ -35,6 +36,7 @@ export const AuditLogResponseData: core.serialization.ObjectSchema< export declare namespace AuditLogResponseData { export interface Raw { + account_member_id?: string | null; actor_type: ActorType.Raw; api_key?: ApiKeyResponseData.Raw | null; api_key_id?: string | null; diff --git a/src/serialization/types/ComponentHydrateResponseData.ts b/src/serialization/types/ComponentHydrateResponseData.ts index dbb99562..7bbd843a 100644 --- a/src/serialization/types/ComponentHydrateResponseData.ts +++ b/src/serialization/types/ComponentHydrateResponseData.ts @@ -15,10 +15,10 @@ import { ComponentDisplaySettings } from "./ComponentDisplaySettings"; import { ComponentResponseData } from "./ComponentResponseData"; import { CreditCompanyGrantView } from "./CreditCompanyGrantView"; import { FeatureUsageDetailResponseData } from "./FeatureUsageDetailResponseData"; -import { InvoiceResponseData } from "./InvoiceResponseData"; import { PlanDetailResponseData } from "./PlanDetailResponseData"; import { ScheduledDowngradeResponseData } from "./ScheduledDowngradeResponseData"; import { StripeEmbedInfo } from "./StripeEmbedInfo"; +import { UpcomingInvoiceResponseData } from "./UpcomingInvoiceResponseData"; import { UsageBasedEntitlementResponseData } from "./UsageBasedEntitlementResponseData"; export const ComponentHydrateResponseData: core.serialization.ObjectSchema< @@ -72,7 +72,7 @@ export const ComponentHydrateResponseData: core.serialization.ObjectSchema< "trial_payment_method_required", core.serialization.boolean().optional(), ), - upcomingInvoice: core.serialization.property("upcoming_invoice", InvoiceResponseData.optional()), + upcomingInvoice: core.serialization.property("upcoming_invoice", UpcomingInvoiceResponseData.optional()), }); export declare namespace ComponentHydrateResponseData { @@ -103,6 +103,6 @@ export declare namespace ComponentHydrateResponseData { stripe_embed?: StripeEmbedInfo.Raw | null; subscription?: CompanySubscriptionResponseData.Raw | null; trial_payment_method_required?: boolean | null; - upcoming_invoice?: InvoiceResponseData.Raw | null; + upcoming_invoice?: UpcomingInvoiceResponseData.Raw | null; } } diff --git a/src/serialization/types/ComponentPreviewResponseData.ts b/src/serialization/types/ComponentPreviewResponseData.ts index d8979beb..f86cffed 100644 --- a/src/serialization/types/ComponentPreviewResponseData.ts +++ b/src/serialization/types/ComponentPreviewResponseData.ts @@ -19,6 +19,7 @@ import { InvoiceResponseData } from "./InvoiceResponseData"; import { PlanDetailResponseData } from "./PlanDetailResponseData"; import { ScheduledDowngradeResponseData } from "./ScheduledDowngradeResponseData"; import { StripeEmbedInfo } from "./StripeEmbedInfo"; +import { UpcomingInvoiceResponseData } from "./UpcomingInvoiceResponseData"; import { UsageBasedEntitlementResponseData } from "./UsageBasedEntitlementResponseData"; export const ComponentPreviewResponseData: core.serialization.ObjectSchema< @@ -73,7 +74,7 @@ export const ComponentPreviewResponseData: core.serialization.ObjectSchema< "trial_payment_method_required", core.serialization.boolean().optional(), ), - upcomingInvoice: core.serialization.property("upcoming_invoice", InvoiceResponseData.optional()), + upcomingInvoice: core.serialization.property("upcoming_invoice", UpcomingInvoiceResponseData.optional()), }); export declare namespace ComponentPreviewResponseData { @@ -105,6 +106,6 @@ export declare namespace ComponentPreviewResponseData { stripe_embed?: StripeEmbedInfo.Raw | null; subscription?: CompanySubscriptionResponseData.Raw | null; trial_payment_method_required?: boolean | null; - upcoming_invoice?: InvoiceResponseData.Raw | null; + upcoming_invoice?: UpcomingInvoiceResponseData.Raw | null; } } diff --git a/src/serialization/types/CreateCustomPlanBundlePlanRequestBody.ts b/src/serialization/types/CreateCustomPlanBundlePlanRequestBody.ts index 9d3b7697..24dbf9f2 100644 --- a/src/serialization/types/CreateCustomPlanBundlePlanRequestBody.ts +++ b/src/serialization/types/CreateCustomPlanBundlePlanRequestBody.ts @@ -10,7 +10,7 @@ export const CreateCustomPlanBundlePlanRequestBody: core.serialization.ObjectSch Schematic.CreateCustomPlanBundlePlanRequestBody > = core.serialization.object({ companyId: core.serialization.property("company_id", core.serialization.string()), - description: core.serialization.string(), + description: core.serialization.string().optional(), icon: PlanIcon.optional(), name: core.serialization.string(), }); @@ -18,7 +18,7 @@ export const CreateCustomPlanBundlePlanRequestBody: core.serialization.ObjectSch export declare namespace CreateCustomPlanBundlePlanRequestBody { export interface Raw { company_id: string; - description: string; + description?: string | null; icon?: PlanIcon.Raw | null; name: string; } diff --git a/src/serialization/types/CreatePlanRequestBody.ts b/src/serialization/types/CreatePlanRequestBody.ts index d9ce1eb0..abebb603 100644 --- a/src/serialization/types/CreatePlanRequestBody.ts +++ b/src/serialization/types/CreatePlanRequestBody.ts @@ -10,7 +10,7 @@ export const CreatePlanRequestBody: core.serialization.ObjectSchema< serializers.CreatePlanRequestBody.Raw, Schematic.CreatePlanRequestBody > = core.serialization.object({ - description: core.serialization.string(), + description: core.serialization.string().optional(), icon: PlanIcon.optional(), name: core.serialization.string(), planType: core.serialization.property("plan_type", PlanType), @@ -18,7 +18,7 @@ export const CreatePlanRequestBody: core.serialization.ObjectSchema< export declare namespace CreatePlanRequestBody { export interface Raw { - description: string; + description?: string | null; icon?: PlanIcon.Raw | null; name: string; plan_type: PlanType.Raw; diff --git a/src/serialization/types/OnboardingRequirementView.ts b/src/serialization/types/OnboardingRequirementView.ts index e7f9be50..ac946575 100644 --- a/src/serialization/types/OnboardingRequirementView.ts +++ b/src/serialization/types/OnboardingRequirementView.ts @@ -12,6 +12,7 @@ export const OnboardingRequirementView: core.serialization.ObjectSchema< > = core.serialization.object({ blockedBy: core.serialization.property("blocked_by", core.serialization.list(OnboardingRequirement).optional()), id: OnboardingRequirement, + reachedAt: core.serialization.property("reached_at", core.serialization.date().optional()), satisfiedBy: core.serialization.property("satisfied_by", core.serialization.string().optional()), status: OnboardingRequirementStatus, }); @@ -20,6 +21,7 @@ export declare namespace OnboardingRequirementView { export interface Raw { blocked_by?: OnboardingRequirement.Raw[] | null; id: OnboardingRequirement.Raw; + reached_at?: string | null; satisfied_by?: string | null; status: OnboardingRequirementStatus.Raw; } diff --git a/src/serialization/types/PlanChangeResponseData.ts b/src/serialization/types/PlanChangeResponseData.ts index 39657130..42a98e96 100644 --- a/src/serialization/types/PlanChangeResponseData.ts +++ b/src/serialization/types/PlanChangeResponseData.ts @@ -14,6 +14,7 @@ import { PlanChangeSubscriptionAction } from "./PlanChangeSubscriptionAction"; import { PlanSnapshotView } from "./PlanSnapshotView"; import { PlanVersionSnapshotView } from "./PlanVersionSnapshotView"; import { SubscriptionTraitUpdate } from "./SubscriptionTraitUpdate"; +import { TrialStatus } from "./TrialStatus"; export const PlanChangeResponseData: core.serialization.ObjectSchema< serializers.PlanChangeResponseData.Raw, @@ -46,6 +47,9 @@ export const PlanChangeResponseData: core.serialization.ObjectSchema< PlanChangeSubscriptionAction.optional(), ), traitsUpdated: core.serialization.property("traits_updated", core.serialization.list(SubscriptionTraitUpdate)), + trialConvertedAt: core.serialization.property("trial_converted_at", core.serialization.date().optional()), + trialExpiresAt: core.serialization.property("trial_expires_at", core.serialization.date().optional()), + trialStatus: core.serialization.property("trial_status", TrialStatus.optional()), updatedAt: core.serialization.property("updated_at", core.serialization.date()), userId: core.serialization.property("user_id", core.serialization.string().optional()), userName: core.serialization.property("user_name", core.serialization.string().optional()), @@ -74,6 +78,9 @@ export declare namespace PlanChangeResponseData { request_id?: string | null; subscription_change_action?: PlanChangeSubscriptionAction.Raw | null; traits_updated: SubscriptionTraitUpdate.Raw[]; + trial_converted_at?: string | null; + trial_expires_at?: string | null; + trial_status?: TrialStatus.Raw | null; updated_at: string; user_id?: string | null; user_name?: string | null; diff --git a/src/serialization/types/PreviewSubscriptionFinanceResponseData.ts b/src/serialization/types/PreviewSubscriptionFinanceResponseData.ts index 02930279..37735bec 100644 --- a/src/serialization/types/PreviewSubscriptionFinanceResponseData.ts +++ b/src/serialization/types/PreviewSubscriptionFinanceResponseData.ts @@ -20,6 +20,7 @@ export const PreviewSubscriptionFinanceResponseData: core.serialization.ObjectSc periodStart: core.serialization.property("period_start", core.serialization.date()), promoCodeApplied: core.serialization.property("promo_code_applied", core.serialization.boolean()), proration: core.serialization.number(), + prorationBilledAt: core.serialization.property("proration_billed_at", core.serialization.date().optional()), taxAmount: core.serialization.property("tax_amount", core.serialization.number().optional()), taxDisplayName: core.serialization.property("tax_display_name", core.serialization.string().optional()), taxRequireBillingDetails: core.serialization.property("tax_require_billing_details", core.serialization.boolean()), @@ -43,6 +44,7 @@ export declare namespace PreviewSubscriptionFinanceResponseData { period_start: string; promo_code_applied: boolean; proration: number; + proration_billed_at?: string | null; tax_amount?: number | null; tax_display_name?: string | null; tax_require_billing_details: boolean; diff --git a/src/serialization/types/StripeIntegrationConfig.ts b/src/serialization/types/StripeIntegrationConfig.ts index 9f539832..52ae8ce7 100644 --- a/src/serialization/types/StripeIntegrationConfig.ts +++ b/src/serialization/types/StripeIntegrationConfig.ts @@ -14,6 +14,7 @@ export const StripeIntegrationConfig: core.serialization.ObjectSchema< isSandbox: core.serialization.property("is_sandbox", core.serialization.boolean()), liveMode: core.serialization.property("live_mode", core.serialization.boolean()), onboardUrl: core.serialization.property("onboard_url", core.serialization.string().optional()), + returnTo: core.serialization.property("return_to", core.serialization.string().optional()), }); export declare namespace StripeIntegrationConfig { @@ -24,5 +25,6 @@ export declare namespace StripeIntegrationConfig { is_sandbox: boolean; live_mode: boolean; onboard_url?: string | null; + return_to?: string | null; } } diff --git a/src/serialization/types/UpcomingInvoiceResponseData.ts b/src/serialization/types/UpcomingInvoiceResponseData.ts new file mode 100644 index 00000000..4f3ea85d --- /dev/null +++ b/src/serialization/types/UpcomingInvoiceResponseData.ts @@ -0,0 +1,64 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as Schematic from "../../api/index"; +import * as core from "../../core"; +import type * as serializers from "../index"; +import { BillingProviderType } from "./BillingProviderType"; +import { InvoiceStatus } from "./InvoiceStatus"; + +export const UpcomingInvoiceResponseData: core.serialization.ObjectSchema< + serializers.UpcomingInvoiceResponseData.Raw, + Schematic.UpcomingInvoiceResponseData +> = core.serialization.object({ + amountDue: core.serialization.property("amount_due", core.serialization.number()), + amountPaid: core.serialization.property("amount_paid", core.serialization.number()), + amountRemaining: core.serialization.property("amount_remaining", core.serialization.number()), + collectionMethod: core.serialization.property("collection_method", core.serialization.string()), + companyId: core.serialization.property("company_id", core.serialization.string().optional()), + createdAt: core.serialization.property("created_at", core.serialization.date()), + currency: core.serialization.string(), + customerExternalId: core.serialization.property("customer_external_id", core.serialization.string()), + dueDate: core.serialization.property("due_date", core.serialization.date().optional()), + endingBalance: core.serialization.property("ending_balance", core.serialization.number()), + environmentId: core.serialization.property("environment_id", core.serialization.string()), + externalId: core.serialization.property("external_id", core.serialization.string().optional()), + paymentMethodExternalId: core.serialization.property( + "payment_method_external_id", + core.serialization.string().optional(), + ), + providerType: core.serialization.property("provider_type", BillingProviderType), + startingBalance: core.serialization.property("starting_balance", core.serialization.number()), + status: InvoiceStatus.optional(), + subscriptionExternalId: core.serialization.property( + "subscription_external_id", + core.serialization.string().optional(), + ), + subtotal: core.serialization.number(), + updatedAt: core.serialization.property("updated_at", core.serialization.date()), + url: core.serialization.string().optional(), +}); + +export declare namespace UpcomingInvoiceResponseData { + export interface Raw { + amount_due: number; + amount_paid: number; + amount_remaining: number; + collection_method: string; + company_id?: string | null; + created_at: string; + currency: string; + customer_external_id: string; + due_date?: string | null; + ending_balance: number; + environment_id: string; + external_id?: string | null; + payment_method_external_id?: string | null; + provider_type: BillingProviderType.Raw; + starting_balance: number; + status?: InvoiceStatus.Raw | null; + subscription_external_id?: string | null; + subtotal: number; + updated_at: string; + url?: string | null; + } +} diff --git a/src/serialization/types/index.ts b/src/serialization/types/index.ts index 948ca836..e3c44f44 100644 --- a/src/serialization/types/index.ts +++ b/src/serialization/types/index.ts @@ -407,6 +407,7 @@ export * from "./TraitDefinition"; export * from "./TraitDefinitionComparableType"; export * from "./TraitType"; export * from "./TrialStatus"; +export * from "./UpcomingInvoiceResponseData"; export * from "./UpdateAddOnRequestBody"; export * from "./UpdateAutoTopupOverrideRequestBody"; export * from "./UpdateBillingPlanCreditGrantRequestBody"; diff --git a/tests/unit/fetcher/createRequestUrl.test.ts b/tests/unit/fetcher/createRequestUrl.test.ts index f8afeaf0..7787e553 100644 --- a/tests/unit/fetcher/createRequestUrl.test.ts +++ b/tests/unit/fetcher/createRequestUrl.test.ts @@ -77,7 +77,7 @@ describe("Test createRequestUrl", () => { undefinedValue: undefined, emptyString: "", }, - expected: "https://api.example.com?valid=value&nullValue=&emptyString=", + expected: "https://api.example.com?valid=value&emptyString=", }, { description: "should handle deeply nested objects", diff --git a/tests/unit/fetcher/getResponseBody.test.ts b/tests/unit/fetcher/getResponseBody.test.ts index 4d8b66e0..482038b7 100644 --- a/tests/unit/fetcher/getResponseBody.test.ts +++ b/tests/unit/fetcher/getResponseBody.test.ts @@ -75,6 +75,21 @@ describe("Test getResponseBody", () => { } }); + it("should retain a reference to the parent Response for sse responses", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = (await getResponseBody(mockResponse, "sse")) as ReadableStream & { + __fern_response_ref?: Response; + }; + // Pins the parent Response so undici's FinalizationRegistry can't GC it and cancel the stream. + expect(result.__fern_response_ref).toBe(mockResponse); + // The pin must be non-enumerable so it does not leak through JSON.stringify or Object.keys. + const descriptor = Object.getOwnPropertyDescriptor(result, "__fern_response_ref"); + expect(descriptor?.enumerable).toBe(false); + } + }); + it("should handle streaming response type", async () => { if (RUNTIME.type === "node") { const mockStream = new ReadableStream(); @@ -84,4 +99,17 @@ describe("Test getResponseBody", () => { expect(JSON.stringify(result)).toBe(JSON.stringify(await chooseStreamWrapper(new ReadableStream()))); } }); + + it("should retain a reference to the parent Response on the stream wrapper", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = (await getResponseBody(mockResponse, "streaming")) as object & { + __fern_response_ref?: Response; + }; + expect(result.__fern_response_ref).toBe(mockResponse); + const descriptor = Object.getOwnPropertyDescriptor(result, "__fern_response_ref"); + expect(descriptor?.enumerable).toBe(false); + } + }); }); diff --git a/tests/unit/fetcher/makePassthroughRequest.test.ts b/tests/unit/fetcher/makePassthroughRequest.test.ts index 31a51012..033544fd 100644 --- a/tests/unit/fetcher/makePassthroughRequest.test.ts +++ b/tests/unit/fetcher/makePassthroughRequest.test.ts @@ -88,6 +88,7 @@ describe("makePassthroughRequest", () => { headers: { "X-Custom": "from-init", Authorization: "from-init" }, }, { + baseUrl: "https://api.example.com", headers: { "X-Custom": "from-sdk", "X-SDK-Only": "sdk-value", @@ -167,6 +168,7 @@ describe("makePassthroughRequest", () => { describe("auth headers", () => { it("should include auth headers when getAuthHeaders is provided", async () => { await makePassthroughRequest("https://api.example.com", undefined, { + baseUrl: "https://api.example.com", getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), fetch: mockFetch, }); @@ -187,6 +189,7 @@ describe("makePassthroughRequest", () => { "https://api.example.com", { headers: { Authorization: "Bearer override" } }, { + baseUrl: "https://api.example.com", getAuthHeaders: async () => ({ Authorization: "Bearer sdk-auth" }), fetch: mockFetch, }, @@ -196,6 +199,82 @@ describe("makePassthroughRequest", () => { }); }); + describe("auth header origin scoping", () => { + it("should attach auth headers to relative paths resolved against baseUrl", async () => { + await makePassthroughRequest("/v1/users", undefined, { + baseUrl: "https://api.example.com", + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBe("Bearer my-token"); + }); + + it("should attach auth headers to same-origin absolute URLs", async () => { + await makePassthroughRequest("https://api.example.com/v1/users", undefined, { + baseUrl: "https://api.example.com", + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBe("Bearer my-token"); + }); + + it("should attach auth headers when the absolute URL matches the environment origin", async () => { + await makePassthroughRequest("https://env.example.com/v1/users", undefined, { + environment: "https://env.example.com", + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBe("Bearer my-token"); + }); + + it("should NOT attach auth headers to a cross-origin absolute URL", async () => { + await makePassthroughRequest("https://evil.example.com/steal", undefined, { + baseUrl: "https://api.example.com", + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBeUndefined(); + }); + + it("should NOT attach auth headers to a cross-origin URL differing only by port", async () => { + await makePassthroughRequest("https://api.example.com:9999/steal", undefined, { + baseUrl: "https://api.example.com", + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBeUndefined(); + }); + + it("should NOT attach auth headers when no baseUrl or environment is configured", async () => { + await makePassthroughRequest("https://api.example.com/v1/users", undefined, { + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBeUndefined(); + }); + + it("should still allow explicit init headers on cross-origin requests", async () => { + await makePassthroughRequest( + "https://evil.example.com/steal", + { headers: { "X-Custom": "keep-me" } }, + { + baseUrl: "https://api.example.com", + getAuthHeaders: async () => ({ Authorization: "Bearer my-token" }), + fetch: mockFetch, + }, + ); + const [, calledOptions] = mockFetch.mock.calls[0]; + expect(calledOptions.headers.authorization).toBeUndefined(); + expect(calledOptions.headers["x-custom"]).toBe("keep-me"); + }); + }); + describe("method and body", () => { it("should default to GET when no method specified", async () => { await makePassthroughRequest("https://api.example.com", undefined, { @@ -394,4 +473,31 @@ describe("makePassthroughRequest", () => { expect(calledOptions.headers["x-dynamic"]).toBe("dynamic-value"); }); }); + + describe("debug logging", () => { + it("should redact credentials in the logged request URL", async () => { + const mockLogger = { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + await makePassthroughRequest("https://user:password@api.example.com/v1/users?token=secret", undefined, { + fetch: mockFetch, + logging: { + level: "debug", + logger: mockLogger, + silent: false, + }, + }); + + const loggedUrls = mockLogger.debug.mock.calls.map(([, meta]) => (meta as { url: string }).url); + expect(loggedUrls.length).toBeGreaterThan(0); + for (const loggedUrl of loggedUrls) { + expect(loggedUrl).toBe("https://[REDACTED]@api.example.com/v1/users?token=[REDACTED]"); + expect(loggedUrl).not.toContain("password"); + expect(loggedUrl).not.toContain("secret"); + } + }); + }); }); diff --git a/tests/unit/fetcher/signals.test.ts b/tests/unit/fetcher/signals.test.ts index 9cabfa07..b834df80 100644 --- a/tests/unit/fetcher/signals.test.ts +++ b/tests/unit/fetcher/signals.test.ts @@ -66,4 +66,49 @@ describe("Test anySignal", () => { const signal = anySignal(controller1.signal, controller2.signal); expect(signal.aborted).toBe(true); }); + + it("should detect a signal that aborts between the initial aborted check and the event listener registration", () => { + const ctrlA = new AbortController(); + const ctrlB = new AbortController(); + + const originalAddEventListener = ctrlA.signal.addEventListener.bind(ctrlA.signal); + const originalRemoveEventListener = ctrlA.signal.removeEventListener.bind(ctrlA.signal); + + let abortedAccessCount = 0; + const proxy = new Proxy(ctrlA.signal, { + get(target, prop, receiver) { + if (prop === "aborted") { + abortedAccessCount++; + if (abortedAccessCount === 1) return false; + return Reflect.get(target, prop, receiver); + } + if (prop === "addEventListener") { + return (...args: Parameters) => { + if (abortedAccessCount >= 1 && args[0] === "abort") { + ctrlA.abort("too-late"); + } + return originalAddEventListener(...args); + }; + } + if (prop === "removeEventListener") { + return originalRemoveEventListener; + } + return Reflect.get(target, prop, receiver); + }, + }); + + const combined = anySignal(proxy, ctrlB.signal); + + expect(combined.aborted).toBe(true); + expect(combined.reason).toBe("too-late"); + }); + + it("should forward the abort reason from a source signal", () => { + const controller = new AbortController(); + const combined = anySignal(controller.signal); + + controller.abort("test-reason"); + expect(combined.aborted).toBe(true); + expect(combined.reason).toBe("test-reason"); + }); }); diff --git a/tests/unit/url/qs.test.ts b/tests/unit/url/qs.test.ts index 1099b954..54d2d9ce 100644 --- a/tests/unit/url/qs.test.ts +++ b/tests/unit/url/qs.test.ts @@ -189,12 +189,12 @@ describe("Test qs toQueryString", () => { { description: "should handle arrays with null/undefined values", input: { items: ["a", null, "c", undefined, "e"] }, - expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e", + expected: "items%5B0%5D=a&items%5B2%5D=c&items%5B4%5D=e", }, { description: "should handle objects with null/undefined values", input: { name: "John", age: null, email: undefined, active: true }, - expected: "name=John&age=&active=true", + expected: "name=John&active=true", }, ]; diff --git a/tests/wire/accounts.test.ts b/tests/wire/accounts.test.ts index 59a8a96e..fe09efa2 100644 --- a/tests/wire/accounts.test.ts +++ b/tests/wire/accounts.test.ts @@ -1436,6 +1436,7 @@ describe("AccountsClient", () => { const rawResponseBody = { data: { + account_member_id: "account_member_id", actor_type: "api_key", api_key: { created_at: "2024-01-15T09:30:00Z", @@ -1496,6 +1497,7 @@ describe("AccountsClient", () => { const response = await client.accounts.getAuditLog("audit_log_id"); expect(response).toEqual({ data: { + accountMemberId: "account_member_id", actorType: "api_key", apiKey: { createdAt: new Date("2024-01-15T09:30:00.000Z"), diff --git a/tests/wire/checkout.test.ts b/tests/wire/checkout.test.ts index 64dfb7b6..0fa2260c 100644 --- a/tests/wire/checkout.test.ts +++ b/tests/wire/checkout.test.ts @@ -2952,6 +2952,7 @@ describe("CheckoutClient", () => { period_start: "2024-01-15T09:30:00Z", promo_code_applied: true, proration: 1000000, + proration_billed_at: "2024-01-15T09:30:00Z", tax_amount: 1000000, tax_display_name: "tax_display_name", tax_require_billing_details: true, @@ -3056,6 +3057,7 @@ describe("CheckoutClient", () => { periodStart: new Date("2024-01-15T09:30:00.000Z"), promoCodeApplied: true, proration: 1000000, + prorationBilledAt: new Date("2024-01-15T09:30:00.000Z"), taxAmount: 1000000, taxDisplayName: "tax_display_name", taxRequireBillingDetails: true, diff --git a/tests/wire/companies.test.ts b/tests/wire/companies.test.ts index 64a2cfdb..cdf16885 100644 --- a/tests/wire/companies.test.ts +++ b/tests/wire/companies.test.ts @@ -11118,6 +11118,9 @@ describe("CompaniesClient", () => { value: "value", }, ], + trial_converted_at: "2024-01-15T09:30:00Z", + trial_expires_at: "2024-01-15T09:30:00Z", + trial_status: "active", updated_at: "2024-01-15T09:30:00Z", user_id: "user_id", user_name: "user_name", @@ -11238,6 +11241,9 @@ describe("CompaniesClient", () => { value: "value", }, ], + trialConvertedAt: new Date("2024-01-15T09:30:00.000Z"), + trialExpiresAt: new Date("2024-01-15T09:30:00.000Z"), + trialStatus: "active", updatedAt: new Date("2024-01-15T09:30:00.000Z"), userId: "user_id", userName: "user_name", @@ -11420,6 +11426,9 @@ describe("CompaniesClient", () => { value: "value", }, ], + trial_converted_at: "2024-01-15T09:30:00Z", + trial_expires_at: "2024-01-15T09:30:00Z", + trial_status: "active", updated_at: "2024-01-15T09:30:00Z", user_id: "user_id", user_name: "user_name", @@ -11573,6 +11582,9 @@ describe("CompaniesClient", () => { value: "value", }, ], + trialConvertedAt: new Date("2024-01-15T09:30:00.000Z"), + trialExpiresAt: new Date("2024-01-15T09:30:00.000Z"), + trialStatus: "active", updatedAt: new Date("2024-01-15T09:30:00.000Z"), userId: "user_id", userName: "user_name", diff --git a/tests/wire/components.test.ts b/tests/wire/components.test.ts index 34156cc5..36eab4d8 100644 --- a/tests/wire/components.test.ts +++ b/tests/wire/components.test.ts @@ -2221,7 +2221,6 @@ describe("ComponentsClient", () => { ending_balance: 1000000, environment_id: "environment_id", external_id: "external_id", - id: "id", payment_method_external_id: "payment_method_external_id", provider_type: "metronome", starting_balance: 1000000, @@ -3707,7 +3706,6 @@ describe("ComponentsClient", () => { endingBalance: 1000000, environmentId: "environment_id", externalId: "external_id", - id: "id", paymentMethodExternalId: "payment_method_external_id", providerType: "metronome", startingBalance: 1000000, diff --git a/tests/wire/planbundle.test.ts b/tests/wire/planbundle.test.ts index a9921539..4ce6be7d 100644 --- a/tests/wire/planbundle.test.ts +++ b/tests/wire/planbundle.test.ts @@ -11,7 +11,7 @@ describe("PlanbundleClient", () => { const rawRequestBody = { billing_product: { charge_type: "free", is_trialable: true }, entitlements: [{ action: "create" }], - plan: { company_id: "company_id", description: "description", name: "name" }, + plan: { company_id: "company_id", name: "name" }, }; const rawResponseBody = { data: { @@ -111,7 +111,6 @@ describe("PlanbundleClient", () => { ], plan: { companyId: "company_id", - description: "description", name: "name", }, }); @@ -211,7 +210,7 @@ describe("PlanbundleClient", () => { const rawRequestBody = { billing_product: { charge_type: "free", is_trialable: true }, entitlements: [{ action: "create" }, { action: "create" }], - plan: { company_id: "company_id", description: "description", name: "x" }, + plan: { company_id: "company_id", name: "x" }, }; const rawResponseBody = { error: "error" }; @@ -240,7 +239,6 @@ describe("PlanbundleClient", () => { ], plan: { companyId: "company_id", - description: "description", name: "x", }, }); @@ -253,7 +251,7 @@ describe("PlanbundleClient", () => { const rawRequestBody = { billing_product: { charge_type: "free", is_trialable: true }, entitlements: [{ action: "create" }, { action: "create" }], - plan: { company_id: "company_id", description: "description", name: "x" }, + plan: { company_id: "company_id", name: "x" }, }; const rawResponseBody = { error: "error" }; @@ -282,7 +280,6 @@ describe("PlanbundleClient", () => { ], plan: { companyId: "company_id", - description: "description", name: "x", }, }); @@ -295,7 +292,7 @@ describe("PlanbundleClient", () => { const rawRequestBody = { billing_product: { charge_type: "free", is_trialable: true }, entitlements: [{ action: "create" }, { action: "create" }], - plan: { company_id: "company_id", description: "description", name: "x" }, + plan: { company_id: "company_id", name: "x" }, }; const rawResponseBody = { error: "error" }; @@ -324,7 +321,6 @@ describe("PlanbundleClient", () => { ], plan: { companyId: "company_id", - description: "description", name: "x", }, }); @@ -337,7 +333,7 @@ describe("PlanbundleClient", () => { const rawRequestBody = { billing_product: { charge_type: "free", is_trialable: true }, entitlements: [{ action: "create" }, { action: "create" }], - plan: { company_id: "company_id", description: "description", name: "x" }, + plan: { company_id: "company_id", name: "x" }, }; const rawResponseBody = { error: "error" }; @@ -366,7 +362,6 @@ describe("PlanbundleClient", () => { ], plan: { companyId: "company_id", - description: "description", name: "x", }, }); @@ -379,7 +374,7 @@ describe("PlanbundleClient", () => { const rawRequestBody = { billing_product: { charge_type: "free", is_trialable: true }, entitlements: [{ action: "create" }, { action: "create" }], - plan: { company_id: "company_id", description: "description", name: "x" }, + plan: { company_id: "company_id", name: "x" }, }; const rawResponseBody = { error: "error" }; @@ -408,7 +403,6 @@ describe("PlanbundleClient", () => { ], plan: { companyId: "company_id", - description: "description", name: "x", }, }); @@ -418,10 +412,7 @@ describe("PlanbundleClient", () => { test("createPlanBundle (1)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { - entitlements: [{ action: "create" }], - plan: { description: "description", name: "name", plan_type: "plan" }, - }; + const rawRequestBody = { entitlements: [{ action: "create" }], plan: { name: "name", plan_type: "plan" } }; const rawResponseBody = { data: { billing_product: { @@ -515,7 +506,6 @@ describe("PlanbundleClient", () => { }, ], plan: { - description: "description", name: "name", planType: "plan", }, @@ -615,7 +605,7 @@ describe("PlanbundleClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { entitlements: [{ action: "create" }, { action: "create" }], - plan: { description: "description", name: "x", plan_type: "plan" }, + plan: { name: "x", plan_type: "plan" }, }; const rawResponseBody = { error: "error" }; @@ -639,7 +629,6 @@ describe("PlanbundleClient", () => { }, ], plan: { - description: "description", name: "x", planType: "plan", }, @@ -652,7 +641,7 @@ describe("PlanbundleClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { entitlements: [{ action: "create" }, { action: "create" }], - plan: { description: "description", name: "x", plan_type: "plan" }, + plan: { name: "x", plan_type: "plan" }, }; const rawResponseBody = { error: "error" }; @@ -676,7 +665,6 @@ describe("PlanbundleClient", () => { }, ], plan: { - description: "description", name: "x", planType: "plan", }, @@ -689,7 +677,7 @@ describe("PlanbundleClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { entitlements: [{ action: "create" }, { action: "create" }], - plan: { description: "description", name: "x", plan_type: "plan" }, + plan: { name: "x", plan_type: "plan" }, }; const rawResponseBody = { error: "error" }; @@ -713,7 +701,6 @@ describe("PlanbundleClient", () => { }, ], plan: { - description: "description", name: "x", planType: "plan", }, @@ -726,7 +713,7 @@ describe("PlanbundleClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { entitlements: [{ action: "create" }, { action: "create" }], - plan: { description: "description", name: "x", plan_type: "plan" }, + plan: { name: "x", plan_type: "plan" }, }; const rawResponseBody = { error: "error" }; @@ -750,7 +737,6 @@ describe("PlanbundleClient", () => { }, ], plan: { - description: "description", name: "x", planType: "plan", }, @@ -763,7 +749,7 @@ describe("PlanbundleClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { entitlements: [{ action: "create" }, { action: "create" }], - plan: { description: "description", name: "x", plan_type: "plan" }, + plan: { name: "x", plan_type: "plan" }, }; const rawResponseBody = { error: "error" }; @@ -787,7 +773,6 @@ describe("PlanbundleClient", () => { }, ], plan: { - description: "description", name: "x", planType: "plan", }, diff --git a/tests/wire/plans.test.ts b/tests/wire/plans.test.ts index 8880b979..ac62e78a 100644 --- a/tests/wire/plans.test.ts +++ b/tests/wire/plans.test.ts @@ -1359,7 +1359,7 @@ describe("PlansClient", () => { test("createCustomPlan (1)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { company_id: "company_id", description: "description", name: "name" }; + const rawRequestBody = { company_id: "company_id", name: "name" }; const rawResponseBody = { data: { active_version: { @@ -1637,7 +1637,6 @@ describe("PlansClient", () => { const response = await client.plans.createCustomPlan({ companyId: "company_id", - description: "description", name: "name", }); expect(response).toEqual({ @@ -1950,7 +1949,7 @@ describe("PlansClient", () => { test("createCustomPlan (2)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { company_id: "company_id", description: "description", name: "x" }; + const rawRequestBody = { company_id: "company_id", name: "x" }; const rawResponseBody = { error: "error" }; server @@ -1965,7 +1964,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createCustomPlan({ companyId: "company_id", - description: "description", name: "x", }); }).rejects.toThrow(Schematic.BadRequestError); @@ -1974,7 +1972,7 @@ describe("PlansClient", () => { test("createCustomPlan (3)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { company_id: "company_id", description: "description", name: "x" }; + const rawRequestBody = { company_id: "company_id", name: "x" }; const rawResponseBody = { error: "error" }; server @@ -1989,7 +1987,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createCustomPlan({ companyId: "company_id", - description: "description", name: "x", }); }).rejects.toThrow(Schematic.UnauthorizedError); @@ -1998,7 +1995,7 @@ describe("PlansClient", () => { test("createCustomPlan (4)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { company_id: "company_id", description: "description", name: "x" }; + const rawRequestBody = { company_id: "company_id", name: "x" }; const rawResponseBody = { error: "error" }; server @@ -2013,7 +2010,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createCustomPlan({ companyId: "company_id", - description: "description", name: "x", }); }).rejects.toThrow(Schematic.ForbiddenError); @@ -2022,7 +2018,7 @@ describe("PlansClient", () => { test("createCustomPlan (5)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { company_id: "company_id", description: "description", name: "x" }; + const rawRequestBody = { company_id: "company_id", name: "x" }; const rawResponseBody = { error: "error" }; server @@ -2037,7 +2033,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createCustomPlan({ companyId: "company_id", - description: "description", name: "x", }); }).rejects.toThrow(Schematic.NotFoundError); @@ -2046,7 +2041,7 @@ describe("PlansClient", () => { test("createCustomPlan (6)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { company_id: "company_id", description: "description", name: "x" }; + const rawRequestBody = { company_id: "company_id", name: "x" }; const rawResponseBody = { error: "error" }; server @@ -2061,7 +2056,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createCustomPlan({ companyId: "company_id", - description: "description", name: "x", }); }).rejects.toThrow(Schematic.InternalServerError); @@ -2749,7 +2743,7 @@ describe("PlansClient", () => { test("createPlan (1)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { description: "description", name: "name", plan_type: "plan" }; + const rawRequestBody = { name: "name", plan_type: "plan" }; const rawResponseBody = { data: { active_version: { @@ -3026,7 +3020,6 @@ describe("PlansClient", () => { .build(); const response = await client.plans.createPlan({ - description: "description", name: "name", planType: "plan", }); @@ -3340,7 +3333,7 @@ describe("PlansClient", () => { test("createPlan (2)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { description: "description", name: "x", plan_type: "plan" }; + const rawRequestBody = { name: "x", plan_type: "plan" }; const rawResponseBody = { error: "error" }; server @@ -3354,7 +3347,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createPlan({ - description: "description", name: "x", planType: "plan", }); @@ -3364,7 +3356,7 @@ describe("PlansClient", () => { test("createPlan (3)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { description: "description", name: "x", plan_type: "plan" }; + const rawRequestBody = { name: "x", plan_type: "plan" }; const rawResponseBody = { error: "error" }; server @@ -3378,7 +3370,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createPlan({ - description: "description", name: "x", planType: "plan", }); @@ -3388,7 +3379,7 @@ describe("PlansClient", () => { test("createPlan (4)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { description: "description", name: "x", plan_type: "plan" }; + const rawRequestBody = { name: "x", plan_type: "plan" }; const rawResponseBody = { error: "error" }; server @@ -3402,7 +3393,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createPlan({ - description: "description", name: "x", planType: "plan", }); @@ -3412,7 +3402,7 @@ describe("PlansClient", () => { test("createPlan (5)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { description: "description", name: "x", plan_type: "plan" }; + const rawRequestBody = { name: "x", plan_type: "plan" }; const rawResponseBody = { error: "error" }; server @@ -3426,7 +3416,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createPlan({ - description: "description", name: "x", planType: "plan", }); @@ -3436,7 +3425,7 @@ describe("PlansClient", () => { test("createPlan (6)", async () => { const server = mockServerPool.createServer(); const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); - const rawRequestBody = { description: "description", name: "x", plan_type: "plan" }; + const rawRequestBody = { name: "x", plan_type: "plan" }; const rawResponseBody = { error: "error" }; server @@ -3450,7 +3439,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.createPlan({ - description: "description", name: "x", planType: "plan", }); @@ -5050,7 +5038,6 @@ describe("PlansClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { billing_provider: "metronome", - description: "description", external_resource_id: "external_resource_id", name: "name", plan_type: "plan", @@ -5332,7 +5319,6 @@ describe("PlansClient", () => { const response = await client.plans.upsertPlanForBillingProduct({ billingProvider: "metronome", - description: "description", externalResourceId: "external_resource_id", name: "name", planType: "plan", @@ -5649,7 +5635,6 @@ describe("PlansClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { billing_provider: "metronome", - description: "description", external_resource_id: "external_resource_id", name: "x", plan_type: "plan", @@ -5668,7 +5653,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.upsertPlanForBillingProduct({ billingProvider: "metronome", - description: "description", externalResourceId: "external_resource_id", name: "x", planType: "plan", @@ -5681,7 +5665,6 @@ describe("PlansClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { billing_provider: "metronome", - description: "description", external_resource_id: "external_resource_id", name: "x", plan_type: "plan", @@ -5700,7 +5683,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.upsertPlanForBillingProduct({ billingProvider: "metronome", - description: "description", externalResourceId: "external_resource_id", name: "x", planType: "plan", @@ -5713,7 +5695,6 @@ describe("PlansClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { billing_provider: "metronome", - description: "description", external_resource_id: "external_resource_id", name: "x", plan_type: "plan", @@ -5732,7 +5713,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.upsertPlanForBillingProduct({ billingProvider: "metronome", - description: "description", externalResourceId: "external_resource_id", name: "x", planType: "plan", @@ -5745,7 +5725,6 @@ describe("PlansClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { billing_provider: "metronome", - description: "description", external_resource_id: "external_resource_id", name: "x", plan_type: "plan", @@ -5764,7 +5743,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.upsertPlanForBillingProduct({ billingProvider: "metronome", - description: "description", externalResourceId: "external_resource_id", name: "x", planType: "plan", @@ -5777,7 +5755,6 @@ describe("PlansClient", () => { const client = new SchematicClient({ maxRetries: 0, apiKey: "test", environment: server.baseUrl }); const rawRequestBody = { billing_provider: "metronome", - description: "description", external_resource_id: "external_resource_id", name: "x", plan_type: "plan", @@ -5796,7 +5773,6 @@ describe("PlansClient", () => { await expect(async () => { return await client.plans.upsertPlanForBillingProduct({ billingProvider: "metronome", - description: "description", externalResourceId: "external_resource_id", name: "x", planType: "plan", diff --git a/yarn.lock b/yarn.lock index 4f1f74b3..907168fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1548,9 +1548,9 @@ base64-js@^1.3.1: integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== baseline-browser-mapping@^2.11.12: - version "2.11.15" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz#9c0cac93d7d304f3d61bb41088a102cd62e68676" - integrity sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA== + version "2.11.18" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.18.tgz#49701f6ab6c58ccafb6d8e1ab2767574d9f0ba73" + integrity sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw== brace-expansion@^1.1.7: version "1.1.18" @@ -1841,9 +1841,9 @@ dunder-proto@^1.0.1: gopd "^1.2.0" electron-to-chromium@^1.5.402: - version "1.5.411" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz#d56c7e43f91e2b3abb53b4eecd646e20d0b18b34" - integrity sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg== + version "1.5.413" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.413.tgz#0653cd80ad7634cd0a96eaecbc0a7df5436d9ddc" + integrity sha512-F1XPKvt7HVfly5WND90ec16nFsdr4g5x/cVUP3EqjeyXynupabGDqpMa84wwvuYGDnldXLBz6DLXyZXWO9TPvw== emittery@^0.13.1: version "0.13.1" @@ -2052,9 +2052,9 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-uri@^3.0.1: - version "3.1.5" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.5.tgz#610f37419a030270430cecd68d74e3d4d96725d0" - integrity sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw== + version "3.1.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.6.tgz#bfdd308ef763f835fed059f3c6c925f708e33e3a" + integrity sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q== fb-watchman@^2.0.0: version "2.0.2" @@ -3539,17 +3539,17 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -tldts-core@^7.4.10: - version "7.4.10" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.4.10.tgz#33b31839bc37e8283f97d8b5741aa665691701f0" - integrity sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw== +tldts-core@^7.4.11: + version "7.4.11" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.4.11.tgz#a02fba29af72cbf9e658cb3a335f857efc9475f7" + integrity sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg== tldts@^7.0.5: - version "7.4.10" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.4.10.tgz#c1f2804bb028062ae5345cb0adb129cd50f94471" - integrity sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog== + version "7.4.11" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.4.11.tgz#51d5a3feeb473e592edf671491a24fb23789eed2" + integrity sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw== dependencies: - tldts-core "^7.4.10" + tldts-core "^7.4.11" tmpl@1.0.5: version "1.0.5" @@ -3656,10 +3656,10 @@ undici-types@~8.3.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.3.0.tgz#44e9fc9f3244648cdea35e4f9bb2d681e9410809" integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ== -undici@7.28.0, undici@7.29.0: - version "7.29.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-7.29.0.tgz#ae0f6f62e06e057a9cbb7b2b5fde2bb74f791b8f" - integrity sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw== +undici@7.28.0: + version "7.28.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.28.0.tgz#97d64564198b285bc281f0e8e29597e3d11fe7ec" + integrity sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA== universalify@^0.2.0: version "0.2.0" From 7249803327970a1807ad9c1a5463f4149854c6d9 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 10:14:13 +0000 Subject: [PATCH 2/2] [fern-replay] Applied customizations Patches applied (1): - patch-7a37bae5: chore(deps): resolve dependabot security alerts (#156) --- .fern/replay.lock | 51 +++++++++++++++++++++++++++-------------------- package.json | 3 +++ yarn.lock | 8 ++++---- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/.fern/replay.lock b/.fern/replay.lock index a30c3c75..4039c5d0 100644 --- a/.fern/replay.lock +++ b/.fern/replay.lock @@ -48,20 +48,26 @@ generations: cli_version: unknown generator_versions: fernapi/fern-typescript-node-sdk: 3.70.7 -current_generation: 4aa8e15e8199e715486ba5340692e84aa1495d7f + - commit_sha: 75a16cd07b4d34747f0273411e214af54a43b0b0 + tree_hash: 6491b2332b2c411d528b6c4806fa734ef50db2e0 + timestamp: 2026-08-24T10:14:09.360Z + cli_version: unknown + generator_versions: + fernapi/fern-typescript-node-sdk: 3.88.1 +current_generation: 75a16cd07b4d34747f0273411e214af54a43b0b0 patches: - id: patch-7a37bae5 content_hash: sha256:4b8a2c162ce5cb93a744e2c6dbe2c8c37feff60c4728cf67d01968de5977499e original_commit: 7a37bae547cd7e3acf4123558f38a67e8de1449b original_message: "chore(deps): resolve dependabot security alerts (#156)" original_author: Orbit - base_generation: 4aa8e15e8199e715486ba5340692e84aa1495d7f + base_generation: 75a16cd07b4d34747f0273411e214af54a43b0b0 files: - package.json - yarn.lock patch_content: | diff --git a/package.json b/package.json - index 9937134..39d7c7d 100644 + index d5a73ac..33e97ca 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,9 @@ @@ -75,7 +81,7 @@ patches: "engines": { "node": ">=18.0.0" diff --git a/yarn.lock b/yarn.lock - index 663366d..4f1f74b 100644 + index 907168f..f07e202 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3656,10 +3656,10 @@ undici-types@~8.3.0: @@ -97,7 +103,7 @@ patches: package.json: | { "name": "@schematichq/schematic-typescript-node", - "version": "1.5.9", + "version": "1.5.10", "private": false, "repository": { "type": "git", @@ -1723,9 +1729,9 @@ patches: integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== baseline-browser-mapping@^2.11.12: - version "2.11.15" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz#9c0cac93d7d304f3d61bb41088a102cd62e68676" - integrity sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA== + version "2.11.18" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.18.tgz#49701f6ab6c58ccafb6d8e1ab2767574d9f0ba73" + integrity sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw== brace-expansion@^1.1.7: version "1.1.18" @@ -2016,9 +2022,9 @@ patches: gopd "^1.2.0" electron-to-chromium@^1.5.402: - version "1.5.411" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz#d56c7e43f91e2b3abb53b4eecd646e20d0b18b34" - integrity sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg== + version "1.5.413" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.413.tgz#0653cd80ad7634cd0a96eaecbc0a7df5436d9ddc" + integrity sha512-F1XPKvt7HVfly5WND90ec16nFsdr4g5x/cVUP3EqjeyXynupabGDqpMa84wwvuYGDnldXLBz6DLXyZXWO9TPvw== emittery@^0.13.1: version "0.13.1" @@ -2227,9 +2233,9 @@ patches: integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-uri@^3.0.1: - version "3.1.5" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.5.tgz#610f37419a030270430cecd68d74e3d4d96725d0" - integrity sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw== + version "3.1.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.6.tgz#bfdd308ef763f835fed059f3c6c925f708e33e3a" + integrity sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q== fb-watchman@^2.0.0: version "2.0.2" @@ -3714,17 +3720,17 @@ patches: glob "^7.1.4" minimatch "^3.0.4" - tldts-core@^7.4.10: - version "7.4.10" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.4.10.tgz#33b31839bc37e8283f97d8b5741aa665691701f0" - integrity sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw== + tldts-core@^7.4.11: + version "7.4.11" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.4.11.tgz#a02fba29af72cbf9e658cb3a335f857efc9475f7" + integrity sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg== tldts@^7.0.5: - version "7.4.10" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.4.10.tgz#c1f2804bb028062ae5345cb0adb129cd50f94471" - integrity sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog== + version "7.4.11" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.4.11.tgz#51d5a3feeb473e592edf671491a24fb23789eed2" + integrity sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw== dependencies: - tldts-core "^7.4.10" + tldts-core "^7.4.11" tmpl@1.0.5: version "1.0.5" @@ -4091,3 +4097,4 @@ patches: "@speed-highlight/core" "^1.2.7" cookie "^1.0.2" youch-core "^0.3.3" + user_owned: true diff --git a/package.json b/package.json index d5a73acf..33e97ca4 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,9 @@ "crypto": false, "timers": false }, + "resolutions": { + "undici": "7.29.0" + }, "packageManager": "yarn@1.22.22", "engines": { "node": ">=18.0.0" diff --git a/yarn.lock b/yarn.lock index 907168fd..f07e2024 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3656,10 +3656,10 @@ undici-types@~8.3.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.3.0.tgz#44e9fc9f3244648cdea35e4f9bb2d681e9410809" integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ== -undici@7.28.0: - version "7.28.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-7.28.0.tgz#97d64564198b285bc281f0e8e29597e3d11fe7ec" - integrity sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA== +undici@7.28.0, undici@7.29.0: + version "7.29.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.29.0.tgz#ae0f6f62e06e057a9cbb7b2b5fde2bb74f791b8f" + integrity sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw== universalify@^0.2.0: version "0.2.0"