Skip to content

fix(shell): add request id fallback when randomUUID is unavailable#28

Open
skulidropek wants to merge 1 commit into
mainfrom
fix/randomuuid-fallback
Open

fix(shell): add request id fallback when randomUUID is unavailable#28
skulidropek wants to merge 1 commit into
mainfrom
fix/randomuuid-fallback

Conversation

@skulidropek

Copy link
Copy Markdown
Member

Summary

  • add a compatibility fallback chain for request correlation ids
  • keep the middleware id: string contract unchanged
  • cover getRandomValues and no-Web-Crypto runtimes with regressions

Proof of fix

  • Cause: the runtime assumed globalThis.crypto.randomUUID() existed in every browser-like environment, so older WebView/browser runtimes crashed before the first request completed.
  • Solution: randomID() now tries crypto.randomUUID(), then crypto.getRandomValues(), then a deterministic clock/counter fallback.
  • Proof: the new regression tests fail on the old implementation and pass with this change; package typecheck and build also pass.

Verification

  • corepack pnpm --filter @prover-coder-ai/openapi-effect test -- --runInBand packages/app/tests/api-client/create-client-effect.test.ts
  • corepack pnpm --filter @prover-coder-ai/openapi-effect typecheck
  • corepack pnpm --filter @prover-coder-ai/openapi-effect build

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Request IDs now generate reliably even when browser crypto features are unavailable or limited.
    • Improved fallback behavior helps keep app requests working in more environments.
  • Tests
    • Added coverage for multiple request-ID generation paths, including crypto-based and non-crypto fallbacks.

Walkthrough

The randomID() function in the client runtime helpers was updated to add fallback mechanisms for environments where crypto.randomUUID() is unavailable, attempting crypto.getRandomValues() and then a timestamp+counter-based approach. Corresponding tests were added to verify these fallback paths.

Changes

Random ID Fallback Implementation

Layer / File(s) Summary
Multi-step randomID fallback
packages/app/src/shell/api-client/create-client-runtime-helpers.ts
Adds fallback constants and helpers so randomID() tries crypto.randomUUID(), then crypto.getRandomValues(), then a clock+counter based hex string when neither is available.
Fallback path test coverage
packages/app/tests/api-client/create-client-effect.test.ts
Adds afterEach mock/global restoration and two new tests verifying request IDs generated via the getRandomValues fallback and the clock-based fallback.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client as createClientEffect
  participant RandomID as randomID()
  participant Crypto as globalThis.crypto

  Client->>RandomID: request new ID
  RandomID->>Crypto: check randomUUID()
  alt randomUUID available
    Crypto-->>RandomID: UUID string
  else getRandomValues available
    RandomID->>Crypto: getRandomValues(buffer)
    Crypto-->>RandomID: random bytes
  else no crypto available
    RandomID->>RandomID: use timestamp + counter
  end
  RandomID-->>Client: generated ID
Loading

Poem

A hop, a skip, a byte or two,
When crypto's gone, we still pull through!
Timestamps tick, counters climb,
IDs born just in time. 🐰
No UUID? No worries, friend—
This bunny's fallback never ends! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a fallback for request IDs when randomUUID is unavailable.
Description check ✅ Passed The description is directly related to the changeset and accurately describes the fallback chain, tests, and verification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/randomuuid-fallback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/app/src/shell/api-client/create-client-runtime-helpers.ts (2)

30-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant "crypto" in globalThis check.

Since globalThis.crypto simply evaluates to undefined when the property is absent, the in check adds no behavior beyond globalThis.crypto itself (optional chaining is already used by callers). Can be simplified.

♻️ Simplification
-const webCrypto = (): Crypto | undefined => (
-  "crypto" in globalThis ? globalThis.crypto : undefined
-)
+const webCrypto = (): Crypto | undefined => globalThis.crypto
🤖 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 `@packages/app/src/shell/api-client/create-client-runtime-helpers.ts` around
lines 30 - 32, The webCrypto helper in create-client-runtime-helpers.ts is doing
a redundant `"crypto" in globalThis` existence check, which can be simplified.
Update the webCrypto function to rely directly on globalThis.crypto (or
equivalent direct access) and keep the return type unchanged so callers of
webCrypto continue to work with the same optional behavior.

24-25: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Truncated IDs + mod-256 counter increase collision risk; also creates hidden cross-test coupling.

fallbackRandomIdLength = 9 truncates the crypto-derived randomness down to ~36 bits, and randomIdFromClock disambiguates same-millisecond calls with only a 256-value counter — beyond 256 fallback calls within the same millisecond, IDs collide.

Separately, fallbackRequestCounter is let-mutable module state (violates the "без мутаций" purity rule) shared across every randomID() call in the process. The new test at create-client-effect.test.ts (lines 184-206) asserts an exact "00" counter value, implicitly depending on no prior test in the file having hit the clock-fallback path first. Any reordering, parallelization, or a new test inserted earlier that stubs crypto away will silently break this expectation.

Consider deriving the counter via an injected Ref/Effect service (per the Layer/DI guideline) rather than module-level mutable state, and/or widening the ID or documenting the accepted collision risk.

Also applies to: 52-57

🤖 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 `@packages/app/src/shell/api-client/create-client-runtime-helpers.ts` around
lines 24 - 25, The fallback ID generator in randomID/randomIdFromClock is too
collision-prone and relies on shared module-level mutation, which also creates
hidden test coupling. Update the helpers around fallbackRandomIdLength,
fallbackRandomHexBytes, and fallbackRequestCounter so the counter/state is
provided through an injected Effect/Ref or similar per-call service instead of a
mutable let, and either widen the generated ID space or otherwise remove the
256-call-per-millisecond collision limit. Adjust any tests that assert a fixed
counter value so they no longer depend on prior calls or global state.

Source: Coding guidelines

packages/app/tests/api-client/create-client-effect.test.ts (1)

156-206: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

New tests don't follow the .test.ts guideline conventions.

Per repo guidelines for **/*.test.{ts,tsx}, tests should use it.effect()/property-based (fast-check) approaches instead of manual Effect.runPromise wrapping, and should carry explicit documentation of invariants/preconditions/postconditions. These two new tests use it(...).pipe(Effect.runPromise) (consistent with the rest of the file's pre-existing style) and have no invariant/precondition/postcondition documentation. Since this is an example-based verification of exact fallback output rather than a general invariant, full property-based conversion may not fit well here, but adding the required doc fields and considering it.effect() (from @effect/vitest) would align with the stated conventions.

As per coding guidelines, **/*.test.{ts,tsx}: "Использовать Property-Based Testing (fast-check) для проверки инвариантов. Unit-тесты должны использовать мок-зависимости и .effect() вместо async/await." and "Тесты должны содержать явную документацию инвариантов, предусловий, постусловий и вариантных функций".

🤖 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 `@packages/app/tests/api-client/create-client-effect.test.ts` around lines 156
- 206, The two new request-id fallback tests in create-client-effect.test.ts do
not follow the test conventions for *.test.ts files. Update the test cases
around createClientEffect and the GET("/api/auth/me") assertions to use
it.effect() from `@effect/vitest` instead of wrapping with Effect.runPromise, and
add the required invariant/precondition/postcondition documentation for each
test. Keep the existing example-based checks for the
randomUUID/getRandomValues/Date.now fallback behavior, but make the test
structure conform to the repo’s .test.ts guidelines.

Source: Coding guidelines

🤖 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 `@packages/app/src/shell/api-client/create-client-runtime-helpers.ts`:
- Around line 24-63: The helper functions in create-client-runtime-helpers.ts
are plain side-effecting utilities, but the review requires CORE/SHELL style
Effect-TS purity and documentation. Update the random ID helpers around
truncateRandomId, webCrypto, randomIdFromUuid, randomIdFromGetRandomValues,
randomIdFromClock, and randomID to follow the mandated functional contract: add
the required TSDoc and functional comment tags, and make the side-effecting
parts explicit in the API. If converting to Effect is too large, at minimum
isolate the impure operations behind a single shell boundary and keep the rest
pure, and note that randomID’s synchronous use in create-client-runtime.ts will
need a matching call-site change if the signature changes.

---

Nitpick comments:
In `@packages/app/src/shell/api-client/create-client-runtime-helpers.ts`:
- Around line 30-32: The webCrypto helper in create-client-runtime-helpers.ts is
doing a redundant `"crypto" in globalThis` existence check, which can be
simplified. Update the webCrypto function to rely directly on globalThis.crypto
(or equivalent direct access) and keep the return type unchanged so callers of
webCrypto continue to work with the same optional behavior.
- Around line 24-25: The fallback ID generator in randomID/randomIdFromClock is
too collision-prone and relies on shared module-level mutation, which also
creates hidden test coupling. Update the helpers around fallbackRandomIdLength,
fallbackRandomHexBytes, and fallbackRequestCounter so the counter/state is
provided through an injected Effect/Ref or similar per-call service instead of a
mutable let, and either widen the generated ID space or otherwise remove the
256-call-per-millisecond collision limit. Adjust any tests that assert a fixed
counter value so they no longer depend on prior calls or global state.

In `@packages/app/tests/api-client/create-client-effect.test.ts`:
- Around line 156-206: The two new request-id fallback tests in
create-client-effect.test.ts do not follow the test conventions for *.test.ts
files. Update the test cases around createClientEffect and the
GET("/api/auth/me") assertions to use it.effect() from `@effect/vitest` instead of
wrapping with Effect.runPromise, and add the required
invariant/precondition/postcondition documentation for each test. Keep the
existing example-based checks for the randomUUID/getRandomValues/Date.now
fallback behavior, but make the test structure conform to the repo’s .test.ts
guidelines.
🪄 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: 3f9d016c-5aa3-4ca6-89f3-78ae8e1d3cfe

📥 Commits

Reviewing files that changed from the base of the PR and between 13f74f9 and 70902d3.

📒 Files selected for processing (2)
  • packages/app/src/shell/api-client/create-client-runtime-helpers.ts
  • packages/app/tests/api-client/create-client-effect.test.ts

Comment on lines +24 to 63
const fallbackRandomIdLength = 9
const fallbackRandomHexBytes = 5
let fallbackRequestCounter = 0

const truncateRandomId = (value: string): string => value.slice(0, fallbackRandomIdLength)

const webCrypto = (): Crypto | undefined => (
"crypto" in globalThis ? globalThis.crypto : undefined
)

const randomIdFromUuid = (): string | undefined => {
const cryptoApi = webCrypto()
return typeof cryptoApi?.randomUUID === "function"
? truncateRandomId(cryptoApi.randomUUID().replaceAll("-", ""))
: undefined
}

const randomIdFromGetRandomValues = (): string | undefined => {
const cryptoApi = webCrypto()
if (typeof cryptoApi?.getRandomValues !== "function") {
return undefined
}

const bytes = new Uint8Array(fallbackRandomHexBytes)
cryptoApi.getRandomValues(bytes)
return truncateRandomId(Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join(""))
}

const randomIdFromClock = (): string => {
const timestamp = Date.now().toString(16)
const counter = fallbackRequestCounter.toString(16).padStart(2, "0")
fallbackRequestCounter = (fallbackRequestCounter + 1) % 256
return `${timestamp}${counter}`.slice(-fallbackRandomIdLength).padStart(fallbackRandomIdLength, "0")
}

export const randomID = (): string => (
globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, 9)
randomIdFromUuid()
?? randomIdFromGetRandomValues()
?? randomIdFromClock()
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

New helpers don't follow the mandated Effect-TS/CORE-purity house style.

All four functions (truncateRandomId, webCrypto, randomIdFromUuid, randomIdFromGetRandomValues, randomIdFromClock, randomID) perform side effects (reading globalThis.crypto, calling Date.now(), mutating fallbackRequestCounter) directly as plain functions, with no TSDoc and no functional comment tags.

As per coding guidelines, "Функции с побочными эффектами (IO, сеть, БД) должны возвращать Effect<Success, Error, Requirements>... Запрещено выбрасывать исключения; все ошибки должны быть в типе" and "Все функции должны иметь строгую TSDoc документацию с @param, @returns, @pure, @effect, @invariant, @precondition, @postcondition, @complexity, @throws (Never для effect функций)" and "Использовать функциональные комментарии с обязательными полями: CHANGE, WHY, QUOTE(ТЗ), REF, SOURCE, FORMAT THEOREM, PURITY (CORE|SHELL), EFFECT, INVARIANT, COMPLEXITY."

None of this is present here. Given the surrounding contract (randomID() is called synchronously as an object-literal value in create-client-runtime.ts), fully converting to Effect would require touching the call site too — flagging for awareness even though it's a larger, cross-cutting change.

🤖 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 `@packages/app/src/shell/api-client/create-client-runtime-helpers.ts` around
lines 24 - 63, The helper functions in create-client-runtime-helpers.ts are
plain side-effecting utilities, but the review requires CORE/SHELL style
Effect-TS purity and documentation. Update the random ID helpers around
truncateRandomId, webCrypto, randomIdFromUuid, randomIdFromGetRandomValues,
randomIdFromClock, and randomID to follow the mandated functional contract: add
the required TSDoc and functional comment tags, and make the side-effecting
parts explicit in the API. If converting to Effect is too large, at minimum
isolate the impure operations behind a single shell boundary and keep the rest
pure, and note that randomID’s synchronous use in create-client-runtime.ts will
need a matching call-site change if the signature changes.

Source: Coding guidelines

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