Skip to content

feat(sep): add dedicated /sep entrypoint for SEP-1/2/10 helpers - #3

Open
Dione-b wants to merge 1 commit into
dione/developfrom
fix/1474-give-the-sep-helpers
Open

feat(sep): add dedicated /sep entrypoint for SEP-1/2/10 helpers#3
Dione-b wants to merge 1 commit into
dione/developfrom
fix/1474-give-the-sep-helpers

Conversation

@Dione-b

@Dione-b Dione-b commented Aug 10, 2026

Copy link
Copy Markdown
Member

What problem does your feature solve?

SEP helpers (stellar.toml/SEP-1, federation/SEP-2, web auth/SEP-10) are only exported from the package root, mixed in with core primitives. Consumers who want just these helpers have no way to ask for them alone: require() consumers, import * as sdk namespace imports, and bundlers that don't tree-shake export * as well all end up pulling in Horizon and RPC. And these are the modules that do network fetches (see the SSRF fix in stellar#1390), so a SEP-specific concern touches every SDK user.

Modern bundlers doing named ESM imports already tree-shake the root well — this is not a fix for that case, it's a fix for the cases that can't.

What would you like to see?

A @stellar/stellar-sdk/sep subpath, following the /base, /rpc, /contract pattern. Purely additive, 6 files:

  • src/sep/index.ts (new) — barrel re-exporting Federation, StellarToml, WebAuth
  • package.json./sep and ./axios/sep in exports
  • rollup.config.mjs — one line in libEntries
  • test/unit/sep_entrypoint.test.ts (new) — keeps the three in lockstep
  • scripts/measure-bundle.mjs (new) — reproducible bundle measurement
  • CHANGELOG.md
import { Federation, WebAuth, StellarToml } from "@stellar/stellar-sdk/sep";

src/index.ts untouched — nothing moves, nothing breaks.

Measurements

Run node scripts/measure-bundle.mjs to reproduce:

root, namespace import         441.6 KB raw   119.2 KB gzip
root, named import             182.5 KB raw    45.0 KB gzip
/sep,  namespace import        302.7 KB raw    79.8 KB gzip

The relevant comparison is namespace-to-namespace: 79.8 KB gzip via /sep vs 119.2 KB via the root. The named-import row is there to show the honest baseline — if your bundler can do that, you already get the smallest result and don't need this entrypoint.

Issue reference

Closes stellar#1474


pnpm run test:node/test:node:axios (110 files, 2235 tests), pnpm run test:browser (chromium + firefox), and pnpm run _build pass; all 4 build artifacts verified at runtime.

@pedro-pelicioni pedro-pelicioni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packaging itself is fine — I built the branch and both artifacts resolve at runtime (lib/esm/sep/index.js, lib/cjs/sep/index.js, lib/esm/sep/index.d.ts), the exports shape matches the existing subpaths, and node tests pass 7/7. Two blockers and some description issues.

1. Breaks CI. test/unit/sep_entrypoint.test.ts fails in both browsers:

FAIL |chromium| test/unit/sep_entrypoint.test.ts
Caused by: Error: Module "fs" has been externalized for browser compatibility.
           Cannot access "fs.readFileSync" in client code.
FAIL |firefox|  same

config/vitest.config.browser.ts has include: ["test/unit/**/*.test.ts"], and .github/workflows/tests.yml:69,72 runs test:browser / test:browser:axios. The PR only claims test:node, so this went unnoticed. The precedent is already in the repo: test/unit/guide-snippets.test.ts is explicitly excluded for exactly this reason. Either add this file to that exclude list, or drop the fs assertions (see 4).

2. Missing CHANGELOG entry. /base got one under ### Added (stellar#1550) — same convention applies here.

3. The stated motivation doesn't hold for the main case. "They ship in every bundle anyway" isn't true for ESM bundler consumers — I measured import { Keypair, StrKey } from the root at 44 KB gzip, with 0 references to webauth in the unminified bundle. It already tree-shakes (sideEffects is a narrow allowlist). The real beneficiaries are CJS/require, import * as sdk, and bundlers that handle export * as poorly. That's a legitimate argument and it carries the PR — but it's a different one, worth rewording.

4. The numbers don't reproduce. esbuild, minified + gzip: /sep = 79 KB, root named import = 44 KB, full root namespace = 118 KB. /sep costs more than a named root import, which makes sense since WebAuth pulls the whole transaction-building stack. The honest framing is 79 vs 118 for namespace-import consumers — a real ~39 KB saving, just not 43 vs 247. Worth publishing the measurement script.

5. Two of the three test groups are change-detectors. The rollup one (expect(config).toMatch(/"sep\/index":\s*"src\/sep\/index\.ts"/)) breaks if prettier changes quote style, with nothing actually broken. The valuable assertion is expect(sep[name]).toBe(sdk[name]) — it catches a real regression (forked impl / different http-client) and is browser-safe. Keeping only that one also resolves blocker 1. If the packaging invariant deserves a guard, assert on the build output, not on config text.

6. Base branch is dione/develop — make sure the upstream PR targets master.

StellarToml, Federation and WebAuth are the SDK's network-fetching helpers
and the ones most consumers never touch, but they're only reachable from
the package root. Adds a `/sep` subpath (and `/axios/sep`), following the
pattern set by `/base`, `/rpc` and `/contract`, so callers can opt in
explicitly. Purely additive -- the root export is unchanged.

ESM consumers on a modern bundler already get this for free: `sideEffects`
is a narrow allowlist, so a named import from the root tree-shakes the SEP
modules out. The subpath is for the cases where that doesn't happen --
`require()`, `import * as sdk`, and bundlers that handle `export * as`
poorly -- where today the whole root graph is pulled in regardless.

Measured with esbuild (bundle, minify, browser, gzip) via
scripts/measure-bundle.mjs:

  root, namespace import                 120.0 KB
  root, named import (Keypair, StrKey)    45.4 KB
  /sep, namespace import                  80.5 KB

So `/sep` saves a namespace-import consumer ~39 KB. It is not smaller than
a tree-shaken named import from the root -- WebAuth pulls in the whole
transaction-building stack -- and it isn't meant to be.

Co-authored-by: Nearx-Labs <nearxlabs@nearx.com.br>
@Dione-b
Dione-b force-pushed the fix/1474-give-the-sep-helpers branch from d70063c to e32ffe9 Compare August 11, 2026 18:45
@Dione-b

Dione-b commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

What problem does your feature solve?

StellarToml (SEP-1), Federation (SEP-2) and WebAuth (SEP-10) are the SDK's network-fetching helpers, and they're only reachable from the package root. You were right that the original framing ("they ship in every bundle anyway") doesn't hold for ESM consumers on a modern bundler — sideEffects is a narrow allowlist, so a named root import already tree-shakes them out. The problem is narrower than the description claimed: require(), import * as sdk, and bundlers that handle export * as poorly pull in the whole root graph with no way to opt out.

Your numbers reproduce; mine were wrong. esbuild (bundle, minify, browser, gzip), against the built lib/esm:

entrypoint gzip
root, namespace import 120.0 KB
root, named import (Keypair, StrKey) 45.4 KB
/sep, namespace import 80.5 KB

So ~39 KB for namespace-import consumers, and /sep is indeed larger than a tree-shaken named root import — WebAuth pulls the whole transaction-building stack. The 43 vs 247 KB figure is gone. Measurement script published as scripts/measure-bundle.mjs.

What would you like to see?

A /sep subpath (plus /axios/sep), following /base, /rpc and /contract. Purely additive — the root export is unchanged.

Fixed in the amended commit:

  1. CI — dropped the fs-reading test groups instead of extending the exclude list, which also resolves (5). Verified: test/unit passes 2235 in node and 4202 across chromium + firefox, including sep_entrypoint.test.ts in both.
  2. CHANGELOG — added under ### Added, matching the /base (Add /base subpath export and document Jest setup stellar/js-stellar-sdk#1550) entry.
  3. Motivation — commit message reworded to the CJS / namespace-import / export * as argument.
  4. Numbers — replaced with the measured ones above.
  5. Change-detectors — only expect(sep[name]).toBe(sdk[name]) remains; the package.json and rollup.config.mjs text assertions are gone. A missing packaging entry surfaces as an unresolvable subpath at build time instead.
  6. Base branch — the upstream PR will target master; dione/develop is the fork-side base only.

cc @pedro-pelicioni

@pedro-pelicioni

Copy link
Copy Markdown
Member

Both blockers are cleared — verified on e32ffe9:

  • Browser runner: 8 passed (chromium + firefox). Full node unit suite: 110 files, 2235 passed.
  • CHANGELOG entry is there, and it frames the benefit correctly ("Mainly benefits require() consumers, import * as sdk namespace imports, and bundlers that don't tree-shake export * as well").
  • scripts/measure-bundle.mjs is a good addition. I ran it and it reproduces exactly.

One thing left: the PR description wasn't updated and now contradicts the script the PR itself adds.

root, namespace import         441.6 KB raw   119.2 KB gzip
root, named import             182.5 KB raw    45.0 KB gzip
/sep,  namespace import        302.7 KB raw    79.8 KB gzip
  1. Still says "they ship in every bundle anyway" — the CHANGELOG fixed this framing, the body didn't.
  2. Still says "Purely additive, 4 files" — it's 6 now.
  3. Still says "43 KB vs 247 KB", measured with rollup+terser. measure-bundle.mjs says 79.8 vs 119.2 gzip. This is the number a reviewer will actually run, and the two shouldn't disagree.
  4. The footer still only claims test:node/test:node:axiostest:browser passes now, worth saying so since that's what CI runs.

Description sync only; the code looks good to me.

@Dione-b

Dione-b commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

@pedro-pelicioni description updated — no code changes.

  1. Dropped the "they ship in every bundle anyway" framing; the body now matches the CHANGELOG (require() consumers, import * as sdk, bundlers weak on export * as), and states outright that modern bundlers with named ESM imports already tree-shake the root fine.
  2. 4 files → 6, with scripts/measure-bundle.mjs and CHANGELOG.md listed.
  3. Replaced "43 KB vs 247 KB" with the exact measure-bundle.mjs output and how to reproduce it. Called out that the meaningful comparison is namespace-to-namespace (79.8 KB vs 119.2 KB gzip), and kept the named-import row visible as the honest baseline.
  4. Footer now includes test:browser (chromium + firefox) and the correct counts (110 files, 2235 tests).

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.

2 participants