typescript: hand-written fetch runtime — M6 (D-160) - #47
Conversation
Add runtimes/typescript/gantryruntime, the real runtime the generated Box TypeScript SDK ships against (TR-TS.5) — the implementation the rendered runtime.ts stubs (D-158) stand in for. A standalone package, like the Go and Rust runtimes. src/runtime.ts implements the V1 contract with matching names/signatures: Request/Response/Stream envelopes, a Client session (baseUrl, newRequest, accessToken, and a retrying fetch — exponential backoff + full jitter, a single 401 token refresh, Retry-After on 429/503), the with* request builders, and the response accessors. src/errors.ts holds BoxApiError (the exceptions model, TR-TS.3). It depends only on the platform fetch/Headers/URLSearchParams — no Node-only APIs — so it runs on any modern JavaScript runtime. src/auth.ts provides three fetch-only auth flows: developerToken (fixed token), clientCredentials (CCG), and the OAuth 2.0 authorization-code flow (authorizeUrl / exchangeCode / oauth resume). CCG and OAuth cache the access token behind a single-flight refresh and refresh a margin before expiry; OAuth rotates the refresh token Box returns. JWT server auth is deferred to a follow-up slice — it is the only flow needing an RSA signing key (node:crypto + @types/node), so keeping it separate preserves this slice's platform-neutral, dependency-free tsc gate. Gated: a backend test generates the SDK, swaps the stub runtime.ts for the real runtime's source, adds a smoke module constructing the client from an auth flow, and tsc --noEmit's the whole package — proving the runtime satisfies the contract signatures the managers call, no drift (FR-5.2), exactly as the Rust backend's cargo check --examples gate. The runtime package also type-checks standalone under strict TS7, wired as a CI step alongside the Go/Rust runtime jobs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kjN3CzbXhJrnUmUHJJqpW
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThe PR adds a standalone handwritten TypeScript runtime with authentication flows, HTTP request and response handling, retries, package metadata, strict type-checking, CI validation, and generated SDK compatibility testing. ChangesTypeScript runtime implementation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant GeneratedSDK
participant Client
participant Auth
participant BoxAPI
GeneratedSDK->>Client: create request
Client->>Auth: accessToken()
Auth-->>Client: access token
Client->>BoxAPI: fetch authenticated request
BoxAPI-->>Client: response
Client-->>GeneratedSDK: runtime response
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@PROGRESS.md`:
- Around line 33-34: Synchronize the progress percentages in PROGRESS.md:
reconcile the TypeScript entries around the v4 summary and M6 milestone, and
align the Total ~90% figure with the ~88% overall figure. Update the affected
figures consistently, or document the distinct measurement basis where the
values intentionally differ.
In `@runtimes/typescript/gantryruntime/package.json`:
- Around line 5-9: Update the `@box/gantryruntime` package configuration to
publish compiled artifacts: configure the build to emit JavaScript and
declaration files into dist/, then change the exports entry from src/runtime.ts
to the generated JavaScript and types from src/runtime.ts to the generated
declaration file. Ensure the published package includes the dist/ artifacts.
In `@runtimes/typescript/gantryruntime/src/auth.ts`:
- Around line 61-78: Update accessToken to start the shared refresh without
binding it to any caller’s AbortSignal, while preserving single-flight behavior
and token state updates. For each caller awaiting this.inflight, race the shared
promise against that caller’s signal so cancellation affects only that waiter;
callers without a signal should await the shared refresh directly. Keep refresh
errors clearing this.inflight.
- Around line 128-155: Update CcgConfig and clientCredentials to require exactly
one subject: reject configurations providing both userId and enterpriseId or
neither, using a discriminated union for the TypeScript API plus a runtime
validation for JavaScript callers. Preserve subject selection only after
validation, and never send an empty enterprise subject.
- Around line 86-112: Update postTokenForm to catch fetch transport failures,
including network and abort errors, and rethrow them as BoxApiError so token
acquisition preserves the runtime error contract. Keep HTTP response handling
and JSON parsing unchanged, and attach the original failure as a cause if
BoxApiError supports it.
- Around line 189-220: The refreshTokenExchange flow updates the rotated refresh
token only in local state, so oauth callers cannot persist it. Expose each new
token through the oauth/auth API or add a persistence callback that receives the
updated refresh token, and wire it through refreshTokenExchange and oauth while
preserving the existing token exchange behavior.
In `@runtimes/typescript/gantryruntime/src/runtime.ts`:
- Around line 139-143: Update the 401 retry branch in the runtime request flow
to invalidate the cached credential or request a forced refresh before calling
auth.accessToken(signal). Extend the Auth contract and its implementations with
the required invalidation or force-refresh semantics, then use them when
refreshed is false so the retry sends a newly obtained token.
- Around line 80-83: Validate options.maxRetries in the constructor before
assigning this.maxRetries: accept only finite, non-negative integers, and reject
Infinity, NaN, negative values, and fractional values with an appropriate error.
Preserve the default of 5 when maxRetries is not provided.
- Around line 168-179: Update retryAfter to support both delay-seconds and
HTTP-date Retry-After values: retain numeric parsing for seconds, otherwise
parse the header as an HTTP date and calculate the delay relative to the current
time. Clamp any resulting negative delay to zero, while continuing to use
backoff(attempt) only when the header is invalid or absent.
- Around line 110-149: Update the retry loop in the request method containing
the attempt counter, transport-error handling, and retriable-status branch so
retries occur only for idempotent HTTP methods. Preserve the initial request and
existing token-refresh behavior, but prevent non-idempotent writes from
repeating after transport failures or 429/5xx responses unless the existing
request contract provides idempotency protection.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 92f12233-2a87-4a98-b24a-22766fb0a608
📒 Files selected for processing (9)
.github/workflows/ci.ymlDECISIONS.mdPROGRESS.mdcrates/gantry-backend-typescript/tests/compile_output.rsruntimes/typescript/gantryruntime/package.jsonruntimes/typescript/gantryruntime/src/auth.tsruntimes/typescript/gantryruntime/src/errors.tsruntimes/typescript/gantryruntime/src/runtime.tsruntimes/typescript/gantryruntime/tsconfig.json
| "type": "module", | ||
| "types": "./src/runtime.ts", | ||
| "exports": { | ||
| ".": "./src/runtime.ts" | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
git ls-files runtimes/typescript/gantryruntime package.json tsconfig*.json runtimes/typescript/gantryruntime/srcRepository: unofficialbox/box-gantry
Length of output: 400
🏁 Script executed:
cat -n runtimes/typescript/gantryruntime/package.json
printf '\n--- tsconfig ---\n'
cat -n runtimes/typescript/gantryruntime/tsconfig.json
printf '\n--- runtime.ts ---\n'
cat -n runtimes/typescript/gantryruntime/src/runtime.tsRepository: unofficialbox/box-gantry
Length of output: 13436
🏁 Script executed:
rg -n "`@box/gantryruntime`|gantryruntime" -S .
printf '\n--- package files ---\n'
git ls-files '*package.json' '*pnpm-workspace.yaml' '*turbo.json' '*nx.json' '*vite.config.*' '*rollup.config.*' '*tsup.config.*'Repository: unofficialbox/box-gantry
Length of output: 22721
🏁 Script executed:
cat -n crates/gantry-backend-typescript/tests/compile_output.rs | sed -n '150,240p'Repository: unofficialbox/box-gantry
Length of output: 3110
🏁 Script executed:
sed -n '2390,2425p' DECISIONS.md
printf '\n---\n'
sed -n '150,210p' ARCHITECTURE.mdRepository: unofficialbox/box-gantry
Length of output: 5725
🌐 Web query:
Node.js TypeScript stripping node_modules imported TypeScript package.json exports source .ts node_modules support
💡 Result:
As of July 2026, Node.js natively supports TypeScript via type stripping [1], but it explicitly restricts this functionality within the node_modules directory to discourage package authors from publishing raw TypeScript source code [1][2]. Key details regarding this behavior include: Restriction: By default, Node.js throws the error ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING when it encounters a.ts,.mts, or.cts file inside a node_modules folder [1][2][3]. This safeguard exists to protect editor and TypeScript compiler (tsc) performance, as consuming raw TypeScript dependencies without type declarations can lead to significantly slower type inference [3]. Recent Developments: As of mid-2026, there is active development in the Node.js project to refine or lift this restriction to better support monorepos and specific deployment scenarios [4][3]. For example, pull requests have been proposed to allow type stripping in node_modules if co-located declaration files (.d.ts) are present [3], or to remove the restriction entirely to enable zero-build workflows for workspace packages [4]. Alternative Solutions: For projects that require TypeScript resolution in node_modules (such as monorepos), developers often use third-party tools or experimental loaders (e.g., node-ts-resolver) that hook into Node.js's internal type-stripping mechanisms to bypass the default node_modules restriction [5]. Package authors are currently expected to publish transpiled JavaScript files along with declaration files (.d.ts) [3], utilizing the package.json exports field to point to these built assets, rather than shipping raw.ts sources [4]. You can track the ongoing status of this feature through official Node.js GitHub discussions, such as PR #63853 and PR #63936, which detail the evolving support for type stripping in dependencies [4][3].
Citations:
- 1: https://nodejs.org/docs/latest/api/typescript.html
- 2: https://nodejs.org/docs/latest-v23.x/api/typescript.html
- 3: module: allow .ts in node_modules when .d.ts is present nodejs/node#63936
- 4: module: allow type stripping in node_modules nodejs/node#63853
- 5: https://github.com/niieani/node-ts-resolver
Publish built JS from @box/gantryruntime
"exports": "./src/runtime.ts" points consumers at raw TypeScript, but installed packages under node_modules can’t rely on Node’s TypeScript stripping. Emit JS and declarations to dist/, then update exports and types to those build artifacts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@runtimes/typescript/gantryruntime/package.json` around lines 5 - 9, Update
the `@box/gantryruntime` package configuration to publish compiled artifacts:
configure the build to emit JavaScript and declaration files into dist/, then
change the exports entry from src/runtime.ts to the generated JavaScript and
types from src/runtime.ts to the generated declaration file. Ensure the
published package includes the dist/ artifacts.
| async accessToken(signal?: AbortSignal): Promise<string> { | ||
| if (this.token && this.expiry - Date.now() > REFRESH_MARGIN_MS) { | ||
| return this.token; | ||
| } | ||
| if (!this.inflight) { | ||
| this.inflight = this.refresh(signal) | ||
| .then((token) => { | ||
| this.token = token.accessToken; | ||
| this.expiry = Date.now() + token.ttlMs; | ||
| this.inflight = undefined; | ||
| return token.accessToken; | ||
| }) | ||
| .catch((err: unknown) => { | ||
| this.inflight = undefined; | ||
| throw err; | ||
| }); | ||
| } | ||
| return this.inflight; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Decouple shared refreshes from each caller’s abort signal.
The first caller’s signal owns the shared refresh, so its cancellation fails every waiter. Conversely, later callers cannot cancel while awaiting this.inflight. Start an independent single-flight refresh and race each caller against its own signal.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@runtimes/typescript/gantryruntime/src/auth.ts` around lines 61 - 78, Update
accessToken to start the shared refresh without binding it to any caller’s
AbortSignal, while preserving single-flight behavior and token state updates.
For each caller awaiting this.inflight, race the shared promise against that
caller’s signal so cancellation affects only that waiter; callers without a
signal should await the shared refresh directly. Keep refresh errors clearing
this.inflight.
| export interface CcgConfig { | ||
| clientId: string; | ||
| clientSecret: string; | ||
| enterpriseId?: string; | ||
| userId?: string; | ||
| /** Optional; defaults to Box's token endpoint. */ | ||
| tokenUrl?: string; | ||
| } | ||
|
|
||
| /** Build a CCG `Auth`. */ | ||
| export function clientCredentials(config: CcgConfig): Auth { | ||
| const tokenUrl = config.tokenUrl ?? DEFAULT_TOKEN_URL; | ||
| const subject = config.userId | ||
| ? { type: 'user', id: config.userId } | ||
| : { type: 'enterprise', id: config.enterpriseId ?? '' }; | ||
| return new CachedToken((signal) => | ||
| postTokenForm( | ||
| tokenUrl, | ||
| { | ||
| grant_type: 'client_credentials', | ||
| client_id: config.clientId, | ||
| client_secret: config.clientSecret, | ||
| box_subject_type: subject.type, | ||
| box_subject_id: subject.id, | ||
| }, | ||
| signal, | ||
| ), | ||
| ); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n### auth.ts outline\n'
ast-grep outline runtimes/typescript/gantryruntime/src/auth.ts --view expanded || true
printf '\n### auth.ts relevant lines\n'
sed -n '1,260p' runtimes/typescript/gantryruntime/src/auth.ts | cat -n
printf '\n### search for related validation/usages\n'
rg -n "clientCredentials|CcgConfig|enterpriseId|userId|box_subject_type|box_subject_id|DEFAULT_TOKEN_URL|CachedToken" runtimes/typescript/gantryruntime/src -SRepository: unofficialbox/box-gantry
Length of output: 12829
Require exactly one CCG subject. runtimes/typescript/gantryruntime/src/auth.ts should reject configs that set both userId and enterpriseId, or neither; clientCredentials() currently prefers userId and otherwise sends an empty enterprise subject. A discriminated union plus a runtime check would keep the TS API and JS callers aligned.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@runtimes/typescript/gantryruntime/src/auth.ts` around lines 128 - 155, Update
CcgConfig and clientCredentials to require exactly one subject: reject
configurations providing both userId and enterpriseId or neither, using a
discriminated union for the TypeScript API plus a runtime validation for
JavaScript callers. Preserve subject selection only after validation, and never
send an empty enterprise subject.
| for (let attempt = 0; attempt <= this.maxRetries; attempt++) { | ||
| const headers = new Headers(request.headers); | ||
| headers.set('Authorization', `Bearer ${token}`); | ||
|
|
||
| // `httpResponse` is the platform `Response` (via inference — the name is | ||
| // shadowed by this module's `Response` class, so it is never annotated). | ||
| let httpResponse; | ||
| try { | ||
| httpResponse = await fetch(url, { | ||
| method: request.method, | ||
| headers, | ||
| // A `Uint8Array` is a valid `BodyInit` at runtime; the cast bridges | ||
| // the `Uint8Array<ArrayBufferLike>` vs `BufferSource` generic gap in | ||
| // the DOM lib without narrowing the contract's plain-`Uint8Array` body. | ||
| body: request.body as BodyInit | undefined, | ||
| signal, | ||
| }); | ||
| } catch (err) { | ||
| lastError = err; | ||
| if (attempt === this.maxRetries) { | ||
| break; | ||
| } | ||
| await sleep(backoff(attempt), signal); | ||
| continue; | ||
| } | ||
|
|
||
| const bodyBytes = new Uint8Array(await httpResponse.arrayBuffer()); | ||
| const response = new Response(httpResponse.status, httpResponse.headers, bodyBytes); | ||
|
|
||
| // A single token refresh on 401. | ||
| if (response.status === 401 && !refreshed) { | ||
| refreshed = true; | ||
| token = await this.auth.accessToken(signal); | ||
| continue; | ||
| } | ||
| // Backoff on rate-limit / server errors, honoring Retry-After. | ||
| if (retriable(response.status) && attempt < this.maxRetries) { | ||
| await sleep(retryAfter(response, attempt), signal); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant file and surrounding context.
FILE="runtimes/typescript/gantryruntime/src/runtime.ts"
wc -l "$FILE"
sed -n '1,220p' "$FILE"Repository: unofficialbox/box-gantry
Length of output: 7929
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="runtimes/typescript/gantryruntime/src/runtime.ts"
wc -l "$FILE"
sed -n '1,220p' "$FILE"Repository: unofficialbox/box-gantry
Length of output: 7929
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where requests are created and executed to see whether the retry policy
# can distinguish safe/idempotent operations.
rg -n "newRequest\(|\.fetch\(request|client\.fetch|await this\.client\.fetch|request\.method" runtimes/typescript/gantryruntime/src -g '!**/dist/**'Repository: unofficialbox/box-gantry
Length of output: 354
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "newRequest\(|\.fetch\(request|client\.fetch|await this\.client\.fetch|request\.method" runtimes/typescript/gantryruntime/src -g '!**/dist/**'Repository: unofficialbox/box-gantry
Length of output: 354
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the TypeScript runtime tree and search for request construction/HTTP verbs
# outside the runtime implementation.
find runtimes/typescript -maxdepth 4 -type f \( -name '*.ts' -o -name '*.mts' -o -name '*.cts' \) | sort | sed -n '1,200p'
echo '---'
rg -n "\b(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b|withJsonBody\(|withQuery\(|withHeader\(|newRequest\(" runtimes/typescript -g '!**/dist/**'Repository: unofficialbox/box-gantry
Length of output: 1015
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "idempot|retry|retriabl|safe method|X-Request-Id|Idempotency-Key|operation metadata|mutat" runtimes/typescript -g '!**/dist/**'Repository: unofficialbox/box-gantry
Length of output: 1093
Do not retry non-idempotent writes. This loop retries every method on transport failures and 429/5xx responses, so a mutating request can be replayed after a partial commit and duplicate the operation. Restrict retries to idempotent methods or add idempotency protection for writes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@runtimes/typescript/gantryruntime/src/runtime.ts` around lines 110 - 149,
Update the retry loop in the request method containing the attempt counter,
transport-error handling, and retriable-status branch so retries occur only for
idempotent HTTP methods. Preserve the initial request and existing token-refresh
behavior, but prevent non-idempotent writes from repeating after transport
failures or 429/5xx responses unless the existing request contract provides
idempotency protection.
| /** The delay before a retry: the response's Retry-After seconds when present, | ||
| * else the backoff schedule. */ | ||
| function retryAfter(response: Response, attempt: number): number { | ||
| const header = response.headers.get('Retry-After'); | ||
| if (header) { | ||
| const seconds = Number.parseInt(header, 10); | ||
| if (Number.isFinite(seconds)) { | ||
| return seconds * 1000; | ||
| } | ||
| } | ||
| return backoff(attempt); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Handle HTTP-date Retry-After values. Retry-After can be either delay-seconds or an HTTP date; the current parseInt path ignores dates, so the backoff fallback can retry too early. Parse both forms and clamp negative delays to zero.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@runtimes/typescript/gantryruntime/src/runtime.ts` around lines 168 - 179,
Update retryAfter to support both delay-seconds and HTTP-date Retry-After
values: retain numeric parsing for seconds, otherwise parse the header as an
HTTP date and calculate the delay relative to the current time. Clamp any
resulting negative delay to zero, while continuing to use backoff(attempt) only
when the header is invalid or absent.
Bring the TS runtime to parity with the Rust runtime's retry/auth semantics and tighten the auth error contract, per CodeRabbit's review of #47. runtime.ts: - Idempotency-gated retries (shouldRetry): a 429 (never processed) retries for any method; a transport error or 5xx (which may have committed a write) retries only for idempotent methods — matching Rust's should_retry. - Force-refresh on 401 (auth.forceRefresh) re-acquires past the token cache, so the retry no longer resends the rejected token — matching Rust's force_refresh. Previously the 401 refresh was a no-op when the token was still cached. - Retry-After parsed as a floor over backoff (delay-seconds only, like Rust) and clamped to a 30s ceiling so a hostile header can't stall. - maxRetries validated as a non-negative integer at construction. auth.ts: - Auth gains forceRefresh(stale); CachedToken returns a concurrently refreshed token without a network round-trip, else runs a single-flight refresh. Token acquisition is no longer bound to a caller's AbortSignal, so one caller's cancellation can't fail every waiter. - postTokenForm wraps transport failures as BoxApiError (with cause), so auth acquisition never escapes the runtime's error contract. - clientCredentials requires a subject (enterpriseId or userId) rather than silently sending an empty enterprise subject. - OAuth reports each rotated refresh token through an optional onRefresh hook, so a resume doesn't start from an already-invalidated token. errors.ts: BoxApiError carries an optional cause. Docs: sync the stale M6/overall progress figures (~90% / M6 ~42%). Deferred with rationale: publishing @box/gantryruntime as built dist/ JS + d.ts is a ship concern (like the Rust NF-8 crate) — the runtime is consumed as source by the swap test today, not installed from node_modules. HTTP-date Retry-After is unhandled in both the Rust and TS runtimes (a shared gap). Runtime tsc-clean; the generated SDK still type-checks against the real runtime (swap gate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kjN3CzbXhJrnUmUHJJqpW
|
Thanks — worked through the review in Fixed
Deferred, with rationale
Runtime is Generated by Claude Code |
What
Adds
runtimes/typescript/gantryruntime— the real runtime the generated Box TypeScript SDK ships against (TR-TS.5), the implementation the renderedruntime.tsstubs (D-158) stand in for. A standalone package, likeruntimes/go/gantryruntimeandruntimes/rust/gantryruntime.The fourth M6 slice, after the model layer (#44), the runtime-contract stubs (#45), and the managers/client (#46).
The runtime
src/runtime.tsimplements the V1 contract with matching names/signatures:Request/Response/Streamenvelopes (bodies buffered so retries can replay them).Clientsession —baseUrl,newRequest,accessToken, and a retryingfetch: exponential backoff + full jitter, a single 401 token refresh, and Retry-After on 429/503, cancellable viaAbortSignal.with*request builders (withQuery/withHeader/withJsonBody/withFormBody/withStreamBody/withMultipartBody) and the response accessors (responseBytes/responseStream/responseHeader/statusCode).src/errors.tsholdsBoxApiError extends Error(the exceptions model, TR-TS.3).It depends only on the platform
fetch/Headers/URLSearchParams— no Node-only APIs — so it runs on any modern JavaScript runtime.Auth
src/auth.tsprovides threefetch-only flows:developerToken— a fixed console token.clientCredentials(CCG) — server-to-server,enterpriseIdoruserIdsubject.authorizeUrlto build the redirect,exchangeCodeto turn a code into anAuth,oauthto resume from a stored refresh token.CCG and OAuth cache the access token behind a single-flight refresh (concurrent callers share one in-flight exchange) and refresh a margin before expiry; OAuth rotates the refresh token Box returns.
JWT server auth is deferred to a follow-up slice — it is the only flow needing an RSA signing key (
node:crypto+@types/node), so keeping it separate preserves this slice's platform-neutral, dependency-freetscgate.Verification (TR-TS.5)
the_generated_sdk_compiles_against_the_real_runtime): generates the SDK, replaces the stubruntime.tswith the real runtime's source, adds a smoke module that constructs the client from an auth flow, andtsc --noEmits the whole package — proving the runtime satisfies the exact contract signatures the managers call, with no drift (FR-5.2). This is the TypeScript analogue of the Rust backend'scargo check --examplesgate.Not in this slice
JWT server auth, then reference docs + generated tests + the
conform --target typescriptshape — later M6 slices. A live smoke test against a real Box account (as Go/Rust have) is also a follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_016kjN3CzbXhJrnUmUHJJqpW
Generated by Claude Code
Summary by CodeRabbit
Retry-After-aware delays, idempotency-aware retries, and forced token refresh on401.