Skip to content

feat(client): Pick each request's base URL from its path with resolveBaseUrl - #130

Merged
BYK merged 1 commit into
mainfrom
feat/resolve-base-url
Nov 5, 2026
Merged

BYK merged 1 commit into
mainfrom
feat/resolve-base-url

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

This adds a per-request base URL hook to the generated client, so a host can send each org-scoped request to the org's region or server before the URL is built. Follow-up to #129, which gave CredentialProvider.current the path's scope. That only helps pick a credential for a URL that's already built.

const sentry = createSentryClient({
  baseUrl: "https://sentry.io",
  resolveBaseUrl: async ({ baseUrl, scope }) =>
    scope ? await regionUrlFor(scope.organization) : baseUrl,
});
await sentry.listOrganizationIssues({ path: { organization_id_or_slug: "acme" } });
// → https://us.sentry.io/api/0/organizations/acme/issues/

What changes

  • Config.resolveBaseUrl (types BaseUrlRequest and BaseUrlResolver, exported from the root). It receives { baseUrl, url, scope? }:

    • baseUrl is the configured base URL, or '' when none is set.
    • url is the built path and query.
    • scope is parseApiPathScope(url), and is left out for unscoped paths.

    Its result replaces baseUrl for that one request, with trailing slashes removed. It runs once per request, including prepareRequest, after requestValidator and before request interceptors. A per-call resolveBaseUrl replaces the configured one. If it throws, the request rejects with that error unchanged and fetch is never called.

  • The new step lives in scripts/generate-base-url-resolver.mjs. It patches the pinned Fetch client in the same way generate-request-preparation.mjs does.

  • parseApiPathScope moves to lib/api-client/api-path-scope.ts, a module with no imports, so the generated client can use it without bundling errors.ts. api-path.ts re-exports it, so the public API doesn't change.

The host still owns region discovery, caching, and trust. The client only asks the host which base URL to use.

Consumer

The toolkit CLI will use this to replace resolveOrgRegion calls at each call site and the getDefaultSdkConfig({ path }) workaround from getsentry/toolkit#1978. I built that change against a local build of this branch. CLI tsc, lint, and the affected tests pass.

Tests

test/base-url-resolution.test.ts covers:

  • An org-scoped operation goes to the resolved base URL. The resolver gets the base URL, path, query, and scope.
  • A project scope is passed through. Unscoped paths get no scope key.
  • Raw prepareRequest URLs are resolved, including when no baseUrl is configured.
  • Ordering: the resolver runs after validation, before interceptors, and once per request.
  • A per-call resolver overrides the configured one.
  • A resolver that throws rejects the request before any fetch.

pnpm build, pnpm typecheck, and pnpm test (632 tests) pass.

via @BYK.

--

View Session in Sentry

…BaseUrl

Add a resolveBaseUrl option to the generated client. It runs once per
request, before the URL is built, and gets the configured base URL, the
path and query, and the org and project the path targets
(parseApiPathScope). Hosts use it to send org-scoped requests to the
org's region or server.

parseApiPathScope moves to a dependency-free module so the generated
client can use it; api-path.ts re-exports it.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Comment thread build.mjs Resolved
@BYK
BYK merged commit a80b704 into main Nov 5, 2026
11 checks passed
@BYK
BYK deleted the feat/resolve-base-url branch November 5, 2026 12:41
sentry-junior Bot added a commit to getsentry/toolkit that referenced this pull request Nov 5, 2026
1.29.0 ships Config.resolveBaseUrl (getsentry/sentry-api-schema#130), which getOrgSdkConfig() now passes.
BYK added a commit that referenced this pull request Nov 5, 2026
Follow-up to [this review
comment](#130 (comment))
on #130. It's the same refactor as getsentry/toolkit#1939, which made
the CLI's generators run in one process.

Before this PR, `build.mjs` ran each of the 12 scripts in `scripts/` as
its own `node` process with `execSync`. Six of them loaded TypeScript
and five parsed the 6 MB spec, each one starting over. Several also had
their own copies of the same patch helpers.

## What changes

- **One process:** each script exports `emit()` and does no work at
import time. `build.mjs` lists the steps in their old order, imports
each one and runs its `emit`.
- **Specs parsed once:** `normalizeSpec` now also returns the spec it
writes. `build.mjs` passes `{ normalizedSpec, sourceSpec }` to every
step, so no step reads a spec from disk.
- Steps that used to fall back to `openapi-derefed.json` when
`openapi-normalized.json` was missing don't need to anymore; that
fallback was only for running a script on its own.
  - No step changes either spec.
- **Shared helpers:** the new `lib/generated-source.mjs` has
`replaceOnce`, `update`, `parse`, `edit` and `srcPath`. The scripts
import them instead of keeping their own copies.
- `replaceOnce` is the stricter version from
`generate-response-contract.mjs`: it takes a string or a RegExp and
reports how many matches it found.
  - `make-tree-shakeable.mjs` now uses it instead of its own loop.

Most of each script's diff is the new indentation from wrapping its body
in `emit`; [hide
whitespace](https://github.com/getsentry/sentry-api-schema/pull/131/files?w=1)
to see the actual changes. I didn't indent lines inside template
literals, so the generated text didn't change.

## Verification

- With a clean `pnpm build`, every file in `src/` and `dist/` is
byte-identical to `main`'s (`diff -r`).
- `pnpm typecheck` passes, and so do all 632 tests in `pnpm test`.
- The step phase (from the first patch through `make-tree-shakeable`)
took 19.1 s and 19.5 s on `main`, and 16.1 s and 13.5 s on this branch,
in one sandbox. The whole build is about 54 s, and Hey API code
generation and `tsc --emitDeclarationOnly` still take most of it.

<!-- junior-request-attribution:start -->
via @BYK.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:local%3Aweb%3Aad43917b182be622bccdd8f7 -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Aad43917b182be622bccdd8f7/?project=4510944073809921)

<!-- junior-session-footer:end -->

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant