From d5fc47cac58179f7feca21f8dea5c903fe93000c Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:41:58 +0800 Subject: [PATCH 1/2] fix(ai-bedrock): use string-literal specifiers for AWS SDK dynamic imports (#929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The adapter loaded its AWS SDK dependencies through dynamic imports whose specifier was a variable — `const mod = '@aws-sdk/...'; import(/* @vite-ignore */ mod)`. Static bundlers (esbuild, bun build, Rollup) cannot resolve import() when the argument is not a string literal, so the SDK was left external and runtime resolution failed in node_modules-free server bundles. The reported failure: a Bun build of a Bedrock-backed server threw "Cannot find package @aws-sdk/client-bedrock-runtime" on the first request. Switch both anti-pattern sites (auth.ts credential-providers import and converse-text.ts client-bedrock-runtime import) to string-literal specifiers. The dynamic import still defers the Node-only SDK until first use, so the module-load graph is unchanged, but bundlers can now statically resolve and include the SDK in self-contained server artifacts. Adds a source-level regression test that pins the literal-specifier contract for both sites — if a future refactor brings the variable-specifier pattern back, the test fails before the regression ships. Fixes #929. --- .../ai-bedrock-static-bundler-specifiers.md | 16 ++++ .../ai-bedrock/src/adapters/converse-text.ts | 25 ++++--- packages/ai-bedrock/src/utils/auth.ts | 24 +++--- .../tests/bundler-static-analysis.test.ts | 73 +++++++++++++++++++ 4 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 .changeset/ai-bedrock-static-bundler-specifiers.md create mode 100644 packages/ai-bedrock/tests/bundler-static-analysis.test.ts diff --git a/.changeset/ai-bedrock-static-bundler-specifiers.md b/.changeset/ai-bedrock-static-bundler-specifiers.md new file mode 100644 index 000000000..68b7c7f58 --- /dev/null +++ b/.changeset/ai-bedrock-static-bundler-specifiers.md @@ -0,0 +1,16 @@ +--- +"@tanstack/ai-bedrock": patch +--- + +Use string-literal specifiers for the AWS SDK dynamic imports in +`@tanstack/ai-bedrock` so static bundlers — esbuild, bun build, Rollup — can +resolve them. Previously the adapter loaded `@aws-sdk/client-bedrock-runtime` +and `@aws-sdk/credential-providers` through `import(/* @vite-ignore */ mod)` +where `mod` was a variable; static analysers cannot resolve a non-literal +specifier, so the SDK was left external and runtime resolution failed in +`node_modules`-free server bundles. Switching to `import('@aws-sdk/...')` +keeps the dynamic import (so the Node-only SDK still defers until first use) +while letting bundlers include it in self-contained server artifacts. Adds a +source-level regression test pinning the literal-specifier contract. + +Fixes #929. diff --git a/packages/ai-bedrock/src/adapters/converse-text.ts b/packages/ai-bedrock/src/adapters/converse-text.ts index 8a2fa8f5a..c94f76e8d 100644 --- a/packages/ai-bedrock/src/adapters/converse-text.ts +++ b/packages/ai-bedrock/src/adapters/converse-text.ts @@ -92,17 +92,24 @@ export class BedrockConverseTextAdapter< } /** - * Dynamically import `@aws-sdk/client-bedrock-runtime`. The specifier is held - * in a variable (not a string literal) so bundler dep scanners (e.g. Vite/ - * esbuild optimizeDeps) cannot statically discover the AWS SDK and try to - * pre-bundle it for the browser — it would fail on the SDK's Node-only - * `fromTokenFile` export chain. The SDK is Node/server-only and is only - * reached on a real request. `typeof import(...)` is a type-only reference - * (erased at emit) so the imported members keep full typing. + * Dynamically import `@aws-sdk/client-bedrock-runtime`. The string-literal + * specifier lets static bundlers — esbuild, bun build, Rollup — resolve and + * include the AWS SDK in self-contained server bundles (#929). The SDK is + * Node/server-only and is only reached on a real request, so the dynamic + * import also keeps it out of the module-load graph until first use. + * `typeof import(...)` is a type-only reference (erased at emit) so the + * imported members keep full typing — no cast is needed on the return. + * + * Vite's dev-time optimizeDeps pre-bundler may try to scan this import for + * the browser and fail on the SDK's Node-only exports. Browser-side use of + * this server-only adapter is unsupported; if Vite flags the SDK during a + * server build, add `@aws-sdk/client-bedrock-runtime` (and + * `@aws-sdk/credential-providers`) to `optimizeDeps.exclude` — see + * `docs/adapters/bedrock.md` and the matching pattern in + * `examples/ts-react-chat/vite.config.ts`. */ protected importBedrockRuntime(): Promise { - const mod = '@aws-sdk/client-bedrock-runtime' - return import(/* @vite-ignore */ mod) as Promise + return import('@aws-sdk/client-bedrock-runtime') } /** diff --git a/packages/ai-bedrock/src/utils/auth.ts b/packages/ai-bedrock/src/utils/auth.ts index babcd647d..2839c3f2a 100644 --- a/packages/ai-bedrock/src/utils/auth.ts +++ b/packages/ai-bedrock/src/utils/auth.ts @@ -62,17 +62,23 @@ export function resolveBedrockAuth( kind: 'sigv4', region, service: sigv4Service(endpoint), - // Lazy credential provider: the AWS SDK is Node/server-only, so we defer the - // dynamic import until SigV4 actually needs to resolve credentials. The - // specifier is held in a variable (not a string literal) so bundler dep - // scanners (e.g. Vite/esbuild optimizeDeps) cannot statically discover the - // AWS SDK and try to pre-bundle it for the browser — it would fail on the - // SDK's Node-only `fromTokenFile` export chain. `typeof import(...)` is a - // type-only reference (erased at emit) so we keep full typing. + // Lazy credential provider: the AWS SDK is Node/server-only, so we defer + // the dynamic import until SigV4 actually needs to resolve credentials. + // A string-literal specifier (rather than a variable) lets static bundlers + // — esbuild, bun build, Rollup — resolve and include the SDK in + // self-contained server bundles (#929). The SDK still stays out of the + // module-load graph until first use because the import is dynamic. + // + // Vite's dev-time optimizeDeps pre-bundler may try to scan this import + // for the browser and fail on the SDK's Node-only `fromTokenFile` export + // chain. Browser-side use of this server-only adapter is unsupported; if + // Vite flags the SDK during a server build, add `@aws-sdk/credential-providers` + // (and `@aws-sdk/client-bedrock-runtime`) to `optimizeDeps.exclude` — see + // `docs/adapters/bedrock.md` and the matching pattern in + // `examples/ts-react-chat/vite.config.ts`. credentials: async (...args) => { - const mod = '@aws-sdk/credential-providers' const { fromNodeProviderChain } = (await import( - /* @vite-ignore */ mod + '@aws-sdk/credential-providers' )) as typeof CredentialProviders return fromNodeProviderChain()(...args) }, diff --git a/packages/ai-bedrock/tests/bundler-static-analysis.test.ts b/packages/ai-bedrock/tests/bundler-static-analysis.test.ts new file mode 100644 index 000000000..306dd6bac --- /dev/null +++ b/packages/ai-bedrock/tests/bundler-static-analysis.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest' +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' + +/** + * Regression test for issue #929: `@tanstack/ai-bedrock` used to load its AWS + * SDK dependencies through dynamic imports whose specifier was a *variable* + * — `const mod = '@aws-sdk/...'; import(mod)` with a leading @vite-ignore + * comment. Static bundlers — esbuild, bun build, Rollup — cannot resolve + * `import()` when the argument is not a string literal, so the SDK was left + * external and runtime resolution failed in `node_modules`-free server + * bundles. + * + * The fix is to use string-literal specifiers (`import('@aws-sdk/...')`). This + * test pins the contract: every AWS SDK dynamic import in the adapter must use + * a string-literal specifier. If a future refactor brings the variable-specifier + * pattern back, this test fails before the regression ships. + * + * Why a source-level check (not a real bundler run): the contract is purely + * about static analysability — whether the specifier is a literal or not. A + * regex over the source is the smallest test that faithfully pins it, with no + * new dev dependency (esbuild/acorn/etc.) and no slow build step in the unit + * suite. The behaviour is unchanged at runtime; only the static shape matters. + */ +describe('AWS SDK dynamic imports — static-bundler resolvable (#929)', () => { + const cases = [ + { + file: 'src/utils/auth.ts', + sdk: '@aws-sdk/credential-providers', + // The credential-providers import is inside an async lambda inside + // resolveBedrockAuth's sigv4 branch — assert the literal import is + // present at all (the anti-pattern check below guards the regression). + }, + { + file: 'src/adapters/converse-text.ts', + sdk: '@aws-sdk/client-bedrock-runtime', + }, + ] as const + + for (const { file, sdk } of cases) { + describe(`${file} → ${sdk}`, () => { + const path = fileURLToPath(new URL(`../${file}`, import.meta.url)) + const source = readFileSync(path, 'utf8') + + it('imports the SDK via a string-literal specifier', () => { + // Required shape: `import('@aws-sdk/...')` — single or double quotes. + // Matches `import('@aws-sdk/foo')`, `import("@aws-sdk/foo")`, and + // whitespace variations like `import( '@aws-sdk/foo' )`. + const literalPattern = new RegExp( + `import\\s*\\(\\s*['"]${escapeRegex(sdk)}['"]\\s*\\)`, + ) + expect(literalPattern.test(source)).toBe(true) + }) + + it('does not use the variable-specifier anti-pattern', () => { + // Anti-pattern: `const mod = '@aws-sdk/...'; ... import(/* @vite-ignore */ mod)` + // — the variable defeats static analysers. Pin both halves: the + // variable assignment and the `@vite-ignore`-commented `import(mod)`. + const variableAssignment = new RegExp( + `const\\s+mod\\s*=\\s*['"]${escapeRegex(sdk)}['"]`, + ) + const viteIgnoredModImport = /import\s*\(\s*\/\*\s*@vite-ignore\s*\*\/\s*mod\s*\)/ + expect(variableAssignment.test(source)).toBe(false) + expect(viteIgnoredModImport.test(source)).toBe(false) + }) + }) + } +}) + +/** Escape RegExp metacharacters in a package specifier (`@`/`/` are safe). */ +function escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} From 649ab2d5239b2933f2123121a0f0462def8b82d4 Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:09:40 +0800 Subject: [PATCH 2/2] chore(ai-bedrock): address CodeRabbit nitpicks on #958 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorder imports in `bundler-static-analysis.test.ts` so built-in `node:fs` and `node:url` precede the external `vitest` import (built-in-first per @typescript-eslint/consistent-type-imports and the repo's other tests). - Align the `importBedrockRuntime` JSDoc with the actual return type (`typeof BedrockRuntime` from the type-only namespace import, not `typeof import(...)` — the inline form is rejected by the repo's `consistent-type-imports` config, so the namespace import stays). CodeRabbit's first nitpick (inline `typeof import('@aws-sdk/...')` on the return type) was tested and reverted: it triggers `@typescript-eslint/consistent-type-imports`'s `import()` type-annotation ban, so the type-only namespace import is preserved as-is. --- packages/ai-bedrock/src/adapters/converse-text.ts | 5 +++-- packages/ai-bedrock/tests/bundler-static-analysis.test.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ai-bedrock/src/adapters/converse-text.ts b/packages/ai-bedrock/src/adapters/converse-text.ts index c94f76e8d..88b2d1ee8 100644 --- a/packages/ai-bedrock/src/adapters/converse-text.ts +++ b/packages/ai-bedrock/src/adapters/converse-text.ts @@ -97,8 +97,9 @@ export class BedrockConverseTextAdapter< * include the AWS SDK in self-contained server bundles (#929). The SDK is * Node/server-only and is only reached on a real request, so the dynamic * import also keeps it out of the module-load graph until first use. - * `typeof import(...)` is a type-only reference (erased at emit) so the - * imported members keep full typing — no cast is needed on the return. + * `typeof BedrockRuntime` (from the type-only namespace import above) is + * erased at emit, so the imported members keep full typing — no value-level + * `as` cast is needed on the return. * * Vite's dev-time optimizeDeps pre-bundler may try to scan this import for * the browser and fail on the SDK's Node-only exports. Browser-side use of diff --git a/packages/ai-bedrock/tests/bundler-static-analysis.test.ts b/packages/ai-bedrock/tests/bundler-static-analysis.test.ts index 306dd6bac..e71274fca 100644 --- a/packages/ai-bedrock/tests/bundler-static-analysis.test.ts +++ b/packages/ai-bedrock/tests/bundler-static-analysis.test.ts @@ -1,6 +1,6 @@ -import { describe, expect, it } from 'vitest' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' /** * Regression test for issue #929: `@tanstack/ai-bedrock` used to load its AWS