Skip to content

[modularizing] feat: @shopify/hydrogen-api package#3725

Draft
fredericoo wants to merge 19 commits intofb-scaffold-micropackagefrom
fb-hydrogen-api
Draft

[modularizing] feat: @shopify/hydrogen-api package#3725
fredericoo wants to merge 19 commits intofb-scaffold-micropackagefrom
fb-hydrogen-api

Conversation

@fredericoo
Copy link
Copy Markdown
Contributor

@fredericoo fredericoo commented Apr 16, 2026

WHY are these changes introduced?

We want a framework-agnostic entrypoint for the Storefront API — no React, no Remix, no hydrogen peer. This PR lands @shopify/hydrogen-api with createStorefrontClient, the cache strategies, GraphQL helpers, and the generated Storefront + Customer Account API types/schemas. It's the first of the split packages that should let us ship the Storefront client to consumers who don't want (or can't use) the full Hydrogen framework.

Why tsdown instead of tsup

Early in this work we hit a sharp edge in tsup: with clean: true and an array config, the DTS pass runs after onSuccess, re-cleans the output dir, and erases files the onSuccess hook just copied. That meant our generated .d.ts files (Storefront API types, Customer Account API types) and the schema JSONs were silently disappearing from dist/ on every build. TSDown does not have this issue, builds faster and has a copy util that makes it even simpler

WHAT is this pull request doing?

  • Adds packages/hydrogen-api/ with the full server-side Storefront client (1:1 API parity with packages/hydrogen/src/storefront.ts), cache layer, GraphQL utilities, and request helpers
  • Generates both Storefront API and Customer Account API types + schema JSONs via graphql-codegen, co-located under src/generated/ for source↔dist symmetry
  • Ships a tsdown build with a safety net: fails the build if the dev flag wasn't replaced
  • Adds a changeset for the new package
  • Leaves packages/hydrogen, packages/hydrogen-react, packages/hydrogen-core, and templates/skeleton untouched in the merged diff

HOW to test your changes?

The final commit on this branch removes the skeleton wiring we used to verify the package end-to-end. To reproduce the verification locally:

  1. Check out the branch and roll back one commit to restore the POC route and skeleton dependency:
    git checkout fb-hydrogen-api
    git reset --hard HEAD~1
    pnpm install
  2. Build the new package:
    pnpm --filter @shopify/hydrogen-api build
  3. From the skeleton, regenerate GraphQL types (this exercises the declare module '@shopify/hydrogen-api' augmentation we add in the POC state):
    pnpm --filter skeleton codegen
  4. Typecheck the skeleton — this confirms the published dist/index.d.ts exports resolve correctly and the Storefront/Customer Account type augmentation threads through:
    pnpm --filter skeleton typecheck
  5. Run the skeleton dev server and hit the POC route that calls the Storefront API through @shopify/hydrogen-api directly:
    pnpm --filter skeleton dev
    # then open http://localhost:3000/api-poc
    You should see the shop's name and description rendered — that's the full path from createStorefrontClientstorefront.query() → cache → response, all through the new package.

Post-merge steps

None.

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows) — pure Node + tsdown, no platform-specific tooling
  • I've added a changeset
  • I've added tests — 40 tests covering storefront client, cache, graphql helpers, request utils
  • I've added or updated the documentation — follow-up PR, pending package naming / export review

fredericoo and others added 12 commits April 15, 2026 19:13
Extract the framework-agnostic Storefront API client from hydrogen-react
into its own zero-dependency package. This is the first step toward a
micro-package architecture where API concerns are decoupled from React.

Exports: createStorefrontClient, StorefrontClientProps,
StorefrontClientReturn, SFAPI_VERSION, storefrontApiCustomScalars.
Subpath exports for storefront-api-types and storefront.schema.json.

SDK variant headers adapted to hydrogen-api/api for analytics accuracy.
getPublicTokenHeadersRaw kept module-private (not promoted to public API).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Disposable proof-of-concept that queries the Storefront API using
the low-level createStorefrontClient from hydrogen-api — raw fetch
with typed headers, no Hydrogen framework helpers or caching.

This validates that hydrogen-api works as a standalone dependency
in a real Hydrogen storefront context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The initial scaffold only had the low-level URL/header builder from
hydrogen-react. This upgrades the package to export the complete
createStorefrontClient with 1:1 API parity to packages/hydrogen/src/storefront.ts:
server caching, i18n, GraphQL validation, error handling, @defer streaming,
and MCP request forwarding.

Key decisions:
- Inlined codegen types (codegen-types.ts) to avoid broken DTS output —
  tsup's DTS bundler externalizes devDependencies, so importing from
  @shopify/hydrogen-codegen or graphql would leave unresolvable references
  in the published .d.ts
- Single runtime dep: @shopify/graphql-client (for @defer streaming)
- Customer Account API types generated independently (not from hydrogen-react)
  to keep the package framework-agnostic
- Stripped browser-only functions from server-timing.ts
- Removed dead logCacheApiStatus code and unused HYDROGEN_SFAPI_PROXY_KEY
- Unified duplicate warnOnce into shared utils/warning.ts
- Added package to lintedTSPackages and fixed all TSDoc violations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The codegen's default `interfaceExtension` now emits a second
`declare module '@shopify/hydrogen-api'` block alongside the existing
`@Shopify/hydrogen` one. This enables type-safe `storefront.query()`
responses when using `@shopify/hydrogen-api` directly.

The `replacePlaceholders` function was changed from `.replace()` to
`.replaceAll()` because there are now two occurrences of each placeholder
in the template string.

Also updated `getSchema()` to fall back to `@shopify/hydrogen-api` when
`@Shopify/hydrogen` is not installed, supporting projects that use
hydrogen-api standalone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The schema and type files were static copies from hydrogen-react with no
way to regenerate them. This adds an independent codegen pipeline that
hits the same live Shopify API endpoints to produce:

- src/storefront-api-types.d.ts
- storefront.schema.json
- src/customer-account-api-types.d.ts
- customer-account.schema.json

Run `pnpm run graphql-types` to regenerate after a Storefront API version
bump. The config mirrors hydrogen-react's codegen.ts so both packages
stay in sync by hitting the same endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous build relied on tsup's onSuccess hook, which is unreliable
when `clean: true` is set in array configs — schemas copied but .d.ts
files did not.

Replaced with a dedicated `copy-assets` npm script that explicitly
copies all 4 non-bundled assets (2 API type .d.ts files + 2 schema
JSONs) into dist/. Schema export paths now point to dist/ so the
`files` array can be a single entry.

Also includes regenerated API type files from the new codegen pipeline
(whitespace-only diffs from the previous manual copies).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tsup's multi-phase build (ESM → onSuccess → DTS) with `clean: true`
wipes files copied during onSuccess when the DTS phase re-cleans the
output folder. tsdown (powered by rolldown) runs JS + DTS as a single
pass, then fires post-build hooks — eliminating the race condition.

Key changes:
- Replace tsup with tsdown (v0.12.9, rolldown v1.0.0-rc.16)
- Use tsdown's built-in `copy` option for asset files instead of a
  separate copy-assets npm script or onSuccess hack
- Use `inputOptions.transform.define` for __HYDROGEN_DEV__ replacement
  (rolldown's Oxc transformer handles this; tsdown's top-level `define`
  leaks into rolldown input validation with a harmless warning)
- Set `hash: false` for predictable output filenames

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.12 had two issues:
- `define` option produced a spurious "Invalid input options" warning
  from rolldown and required an `inputOptions.transform.define`
  workaround to actually perform replacements
- `copy` entries with `{from, to}` treated `to` as a file path

Both are fixed in v0.21.9:
- Top-level `define` works correctly (no warning, replacements apply)
- `copy` accepts `from` as an array with `to` as a directory target
- `fixedExtension: false` restores `.js`/`.d.ts` output extensions
  (v0.21 defaults to `.mjs`/`.d.mts`)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If the \`define\` option ever breaks (config typo, tsdown regression,
accidental removal), dev-only warnings would ship to consumers and
their bundlers would hit a ReferenceError on the undeclared global at
runtime — since \`__HYDROGEN_DEV__\` has no declaration, it's a free
variable that only works because the bundler replaces it at build time.

Added a build:done hook that scans all output chunks for the literal
string and throws with a clear error pointing at the config option.
Verified both paths: normal build passes, commented-out define fails
the build with exit code 1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moves storefront/customer-account types and schema JSONs into a single
src/generated/ directory so source layout mirrors dist/generated/ 1:1.

Why: previously the 4 generated artifacts were scattered — .d.ts files
in src/ alongside hand-authored code, schema JSONs at package root. The
tsdown copy rule had to enumerate them individually. Co-locating them
makes the intent self-evident (anything under src/generated/ is
regenerated by `pnpm graphql-types`) and lets the copy rule become a
simple directory mirror.

How: updated codegen.ts output paths, tsdown.config.ts copy (now
`from: 'src/generated/*'` flattens into `dist/generated/`), and the two
type-only imports in src/storefront.ts. No change to public exports —
consumers still import from `@shopify/hydrogen-api/storefront-api-types`
etc., all backed by ./dist/generated/... paths in the exports map.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…API versions

Adds a unit test that fails if SF_API_VERSION or CA_API_VERSION in
api-versions.ts doesn't match the version the checked-in .d.ts files
were generated against — i.e. someone bumped a version constant
without running `pnpm graphql-types` to regenerate.

Why: the generated .d.ts files are committed to the repo so consumers
and CI get deterministic installs. That's only safe if what ships
matches what's declared. Without this check, a version bump in
isolation silently ships stale types, and `storefront.query()` calls
typecheck against the wrong schema shape.

How: extracted SF_API_VERSION and CA_API_VERSION from codegen.ts into
src/api-versions.ts as a single source of truth imported by both
codegen.ts (the writer) and src/generated-files.test.ts (the verifier).
The test reads the "Based on <API> API <version>" header codegen.ts
embeds on line 3 of each .d.ts and asserts the parsed version equals
the declared one. Uses parametrized `it.each` so each API gets its own
named test; the failure message names the file, both versions, and
the remediation command.

Unit test rather than a tsdown hook: this is an invariant on repo
state (are checked-in files fresh?), not on build output (did
bundling produce correct JS?) — it belongs with the other
`pnpm test` assertions where failures are discoverable and
compose with other invariants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@shopify
Copy link
Copy Markdown
Contributor

shopify Bot commented Apr 16, 2026

Oxygen deployed a preview of your fb-hydrogen-api branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment April 17, 2026 5:28 PM

Learn more about Hydrogen's GitHub integration.

Adds packages/hydrogen-api/src/generated/ to .prettierignore so the
minified introspection schema JSONs don't trip prettier's check.

Why: graphql-codegen emits those schema JSONs minified (see
`{introspection: {minify: true}}` in codegen.ts). Running prettier
over a minified schema would re-inflate it, doubling the file size
and producing massive diff noise on every bump — for zero benefit,
since the files are consumed programmatically and never hand-edited.
Ignoring the whole generated directory also future-proofs: any new
output graphql-codegen adds is covered automatically.

Follows the existing precedent in .prettierignore for
packages/hydrogen-react's equivalent schema files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fredericoo fredericoo changed the title Add @shopify/hydrogen-api package feat: @shopify/hydrogen-api package Apr 16, 2026
fredericoo and others added 4 commits April 17, 2026 16:57
Reverts skeleton template to match main — the POC route, workspace dep,
the generated type augmentation, and the corresponding pnpm-lock entry
were local verification aids, not part of the shippable package.
Reviewers can restore them by checking out the previous commit (see PR
description for test instructions).

Why: keeping skeleton untouched in the merged PR avoids coupling this
hydrogen-api scaffold to a skeleton release and keeps the diff focused
on the new package itself. Folding the pnpm-lock.yaml revert into this
same commit means `git reset --hard HEAD~1` restores the entire POC
verification state atomically — package.json, lockfile, and all.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fredericoo fredericoo changed the title feat: @shopify/hydrogen-api package [modularizing] feat: @shopify/hydrogen-api package Apr 17, 2026
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