Skip to content

build: migrate effect v3 → v4 (beta.103) - #746

Closed
ryanbas21 wants to merge 4 commits into
mainfrom
effect-v4
Closed

build: migrate effect v3 → v4 (beta.103)#746
ryanbas21 wants to merge 4 commits into
mainfrom
effect-v4

Conversation

@ryanbas21

@ryanbas21 ryanbas21 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Migrates the entire SDK from effect@^3.20 (v3) to effect@4.0.0-beta.103 (v4 beta).

Changes by package

pnpm-workspace.yaml (catalog)

  • effect: '4.0.0-beta.103'
  • @effect/vitest: '4.0.0-beta.103'
  • @effect/platform-node: '4.0.0-beta.103'
  • Removed @effect/platform and @effect/cli (consolidated into effect core)
  • Bumped vitest catalog to ^4.1.0

packages/sdk-utilities

  • micro.utils.ts: MicroExit/exitIsFail/exitIsSuccess/causeIsDieExit/Cause; renamed handleMicroExithandleExit
  • config.types/utils/effects/test: EitherResult (succeed/fail/isSuccess/isFailure)

packages/journey-client

  • journey.utils.ts: Either.right/leftResult.succeed/fail
  • client.store.ts: Either.matchResult.match (onFailure/onSuccess)
  • Tests: _tag: 'Right'/'Left''Success'/'Failure'; .right/.left.success/.failure

packages/oidc-client (12 files)

  • All Micro.*Effect.*
  • Cause.failureOptionCause.findErrorOption (returns Option<E>)
  • exitIsFail(exit)Cause.findErrorOption(exit.cause) + Option.isSome
  • Tests: added vi.clearAllMocks() in afterEach to fix accumulated mock call counts

packages/davinci-client (5 files)

  • All Micro.*Effect.*
  • Effect.repeat({while})Effect.gen + manual loop (v4 while receives schedule output type, not effect output)
  • password-policy.rules.ts: Array.filterMap now expects Result callbacks; Option.some/none()Result.succeed/failVoid
  • Context.TagContext.Service

e2e/mock-api-v2 (full migration)

  • All from '@effect/platform'from 'effect/unstable/httpapi'
  • Context.TagContext.Service
  • Effect.ifEffect.suspend(() => bool ? ... : ...)
  • Option.fromNullableOption.fromNullishOr
  • Schema.Union(a, b)Schema.Union([a, b])
  • Schema.Record({key, value})Schema.Record(key, value)
  • Schema.Schema<T, T>Schema.Schema<T>
  • HttpApiMiddleware.TagHttpApiMiddleware.Service<Self, {provides: T}>()
  • Fixed build script: nxBuildbuild

Verification

Check Result
TypeScript build (all packages) ✅ clean
Unit tests ✅ 887/887 (79 test files)
Lint ✅ 0 errors
Prettier ✅ clean
E2E journey-suites (uses mock-api-v2) ✅ 15/15 passed (2 skipped, pre-existing)

Summary by CodeRabbit

  • New Features
    • Davinci and Journey clients now expose a shareable SDK store.
    • OIDC clients can optionally reuse an existing SDK store while retaining standalone operation.
    • Well-known configuration caching and access are centralized for consistent reuse across clients.
  • Improvements
    • Updated asynchronous processing and result handling across authorization, sessions, tokens, and client flows.
    • Improved shared-store subscriptions, middleware integration, and lazy state setup.
  • Bug Fixes
    • Strengthened mock API validation, authorization, session handling, and error responses.

Single canonical RTK Query instance used by davinci-client, journey-client,
and oidc-client — prerequisite for shared Redux store cache deduplication.
… inject into oidc

davinci() and journey() now expose an opaque SdkStore handle on their return value.
oidc() accepts that handle as an optional second argument and lazily injects its
reducer and middleware into the shared store via combineSlices.inject() and
createDynamicMiddleware.addMiddleware(), so both clients share a single Redux
store with one deduplicated wellknown cache entry.

- sdk-types: add SdkStore opaque type (symbol brand)
- sdk-wellknown: add passWithNoTests to vite.config.ts
- davinci-client: migrate to combineSlices + InjectableStore + toSdkStore/fromSdkStore
- journey-client: same migration pattern, expose store: SdkStore on JourneyClient
- oidc-client: add injectIntoStore() helper; oidc(opts, sharedStore?) API
- oidc-client: shared-store.test.ts — 7 integration tests covering injection contract
  and cache deduplication behaviour

toSdkStore/fromSdkStore internal helpers use object type to avoid api-extractor
isExternal/isInternal conflict on SdkStore; cast to SdkStore at the public boundary.
…dStore

requestMiddleware has no effect when oidc() uses an injected store — the
owning store's thunk.extraArgument is used instead. Emit an explicit warn
so callers aren't left debugging silent behavior.

Test coverage: adds idempotency, behavioral subscribe, cache-reuse,
requestMiddleware-warning, and state-after-injection assertions.
Migrate all packages from effect@^3.20 to effect@4.0.0-beta.103.

Key changes:
- Replace Micro with Effect throughout oidc-client and davinci-client
- Migrate Either to Result in sdk-utilities and journey-client
- Migrate MicroExit/exitIsFail/exitIsSuccess to Exit/Cause API
- Update e2e/mock-api-v2 from @effect/platform to effect/unstable/httpapi
- Bump vitest catalog to ^4.1.0 to match @effect/vitest peer requirement
- Remove consolidated packages: @effect/platform, @effect/cli
- Fix handleMicroExit → handleExit (renamed)
- Add vi.clearAllMocks() to oidc-client afterEach (accumulated mock counts)
@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 504562d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR migrates Effect HTTP APIs and runtime effects, adds shared Redux store support across clients, extracts the well-known API into a package, updates Result and schema APIs, and adjusts workspace configuration.

Changes

Mock API migration

Layer / File(s) Summary
HTTP API, middleware, and server migration
e2e/mock-api-v2/src/...
The mock API uses unstable Effect HTTP APIs, service-based middleware, object-based endpoint definitions, and router middleware.
Schema and service updates
e2e/mock-api-v2/src/schemas/*, e2e/mock-api-v2/src/services/*
Schemas use current Schema forms. Session, token, and user-info services use updated service contracts.

Shared store and client integration

Layer / File(s) Summary
Shared store contract and well-known package
packages/sdk-types/*, packages/sdk-effects/wellknown/*
The PR adds opaque SdkStore and extracts the shared wellknownApi.
Client store integration
packages/davinci-client/src/lib/*, packages/journey-client/src/lib/*, packages/oidc-client/src/lib/*
Davinci and Journey expose stores. OIDC accepts an optional shared store and injects its reducer and middleware.
Shared-store validation
packages/oidc-client/src/lib/shared-store.test.ts
Tests cover reducer and middleware injection, cache reuse, subscriptions, standalone behavior, and warnings.

Effect and Result migration

Layer / File(s) Summary
Effect runtime migration
packages/davinci-client/src/lib/*, packages/oidc-client/src/lib/*
Micro effects and execution utilities are replaced with Effect, Exit, and Cause APIs.
Result and parser migration
packages/journey-client/src/lib/*, packages/sdk-utilities/src/lib/config/*
Journey parsing and SDK configuration parsing use Result instead of Either. Tests use the corresponding Result assertions.

Workspace integration

Layer / File(s) Summary
Catalogs, manifests, and project references
pnpm-workspace.yaml, package.json, packages/*/package.json, tsconfig.json, tools/*
Effect and Vitest catalogs are updated. Package dependencies and TypeScript project references include the new well-known package.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Davinci
  participant Journey
  participant Oidc
  participant SharedStore
  participant WellknownApi
  Davinci->>SharedStore: Create and expose opaque SdkStore
  Journey->>SharedStore: Create and expose opaque SdkStore
  Oidc->>SharedStore: Inject OIDC reducer and middleware
  Oidc->>WellknownApi: Query shared well-known cache
  WellknownApi-->>SharedStore: Store response and notify subscribers
Loading

Possibly related PRs

Suggested reviewers: cerebrl

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: migrating the SDK from Effect v3 to Effect v4 beta.
Description check ✅ Passed The description provides a detailed migration summary, package-level changes, and verification results, despite omitting the template headings and Jira ticket field.
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch effect-v4

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.

@nx-cloud

nx-cloud Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 504562d

Command Status Duration Result
nx affected -t build lint test typecheck e2e-ci ❌ Failed 3m 31s View ↗

💡 Dealing with memory or CPU issues? See memory and CPU details with the resource usage add-on ↗.


☁️ Nx Cloud last updated this comment at 2026-08-05 21:41:56 UTC

@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: 6

🧹 Nitpick comments (1)
e2e/mock-api-v2/src/main.ts (1)

39-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the service’s built-in layer where available.

Context.Service provides a layer from its constructor; use SessionStorage.layer instead of reconstructing it with Layer.effect(SessionStorage, SessionStorage.make).

🤖 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 `@e2e/mock-api-v2/src/main.ts` around lines 39 - 42, Update the SessionLayer
definition to use the built-in SessionStorage.layer provided by the
Context.Service, replacing the manually reconstructed
Layer.effect(SessionStorage, SessionStorage.make) while preserving the existing
Layer.provide wiring.
🤖 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 `@e2e/mock-api-v2/src/middleware/Authorization.ts`:
- Around line 35-37: Update the bearer-token check in Authorization middleware
to stop passing tokenValue to Effect.log; keep the log entry limited to the
non-secret message while preserving the surrounding credential validation flow.

In `@e2e/mock-api-v2/src/middleware/Session.ts`:
- Around line 30-45: The session creation flow in the Session middleware must
retain the generated session ID and persist it to the client. Update
SessionStorage.createSession to return both the generated ID and SessionData,
capture that result when creating a session, and set the sessionId cookie on the
downstream response before returning it; preserve the refresh path for existing
sessions.

In `@e2e/mock-api-v2/src/services/mock-env-helpers/index.ts`:
- Around line 66-71: The validateCapabilitiesResponse function passes only
formData.value to validator, which expects a complete CapabilitiesRequestBody
and therefore bypasses unauthorized handling. Pass the complete body expected by
validator, or replace it with a validator matching the extracted value shape
while preserving Unauthorized for invalid credentials.

In `@e2e/mock-api-v2/src/services/session.service.ts`:
- Around line 76-97: Update refreshSession and updateSession in the session
service so every missing or expired session path uses Effect.fail with the
appropriate error, rather than returning an Effect or Error as a successful
value. Keep successful session updates returning the session data, and ensure
their inferred error channels allow the existing Effect.orDie calls in
Session.ts to handle these failures.

In `@packages/davinci-client/src/lib/client.store.utils.ts`:
- Around line 135-141: Validate the sharedStore argument at the oidc() boundary
before calling injectIntoStore, requiring the store, rootReducer.inject, and
dynamicMiddleware.addMiddleware members used by injection. For invalid objects,
return the established controlled argument_error result; do not use the SdkStore
runtime brand as the validation mechanism.

In `@packages/oidc-client/src/lib/authorize.request.micros.test.ts`:
- Around line 76-87: Ensure every equivalent failure assertion in
packages/oidc-client/src/lib/authorize.request.micros.test.ts at lines 76-87 and
packages/oidc-client/src/lib/session.micros.test.ts at lines 196-211 fails
explicitly when Cause.findErrorOption returns None, instead of returning early.
Preserve the existing typed-error assertions for Some results so defect-only
failures cannot pass.

---

Nitpick comments:
In `@e2e/mock-api-v2/src/main.ts`:
- Around line 39-42: Update the SessionLayer definition to use the built-in
SessionStorage.layer provided by the Context.Service, replacing the manually
reconstructed Layer.effect(SessionStorage, SessionStorage.make) while preserving
the existing Layer.provide wiring.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 34fd29c7-fe7a-4d2d-b4b6-45c6076d316d

📥 Commits

Reviewing files that changed from the base of the PR and between d65f42a and 504562d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (93)
  • e2e/mock-api-v2/package.json
  • e2e/mock-api-v2/src/handlers/authorize.handler.ts
  • e2e/mock-api-v2/src/handlers/capabilities.handler.ts
  • e2e/mock-api-v2/src/handlers/end-session.handler.ts
  • e2e/mock-api-v2/src/handlers/healthcheck.handler.ts
  • e2e/mock-api-v2/src/handlers/open-id-configuration.handler.ts
  • e2e/mock-api-v2/src/handlers/revoke.handler.ts
  • e2e/mock-api-v2/src/handlers/token.handler.ts
  • e2e/mock-api-v2/src/handlers/userinfo.handler.ts
  • e2e/mock-api-v2/src/helpers/match.ts
  • e2e/mock-api-v2/src/main.ts
  • e2e/mock-api-v2/src/middleware/Authorization.ts
  • e2e/mock-api-v2/src/middleware/CookieMiddleware.ts
  • e2e/mock-api-v2/src/middleware/Session.ts
  • e2e/mock-api-v2/src/schemas/authorize.schema.ts
  • e2e/mock-api-v2/src/schemas/capabilities/capabilities.request.schema.ts
  • e2e/mock-api-v2/src/schemas/capabilities/capabilities.response.schema.ts
  • e2e/mock-api-v2/src/schemas/open-id-configuration/open-id-configuration-response.schema.ts
  • e2e/mock-api-v2/src/schemas/return-success-response-redirect.schema.ts
  • e2e/mock-api-v2/src/schemas/revoke/revoke.schema.ts
  • e2e/mock-api-v2/src/schemas/token/token.schema.ts
  • e2e/mock-api-v2/src/services/mock-env-helpers/index.ts
  • e2e/mock-api-v2/src/services/session.service.ts
  • e2e/mock-api-v2/src/services/tokens.service.ts
  • e2e/mock-api-v2/src/services/userinfo.service.ts
  • e2e/mock-api-v2/src/spec.ts
  • goals/centralize-redux-store/facts-result.json
  • goals/centralize-redux-store/facts-review.json
  • goals/centralize-redux-store/facts.md
  • goals/centralize-redux-store/facts.meta.json
  • goals/centralize-redux-store/goal.md
  • goals/centralize-redux-store/interview-result.json
  • goals/centralize-redux-store/interview.json
  • goals/centralize-redux-store/plan.md
  • package.json
  • packages/davinci-client/api-report/davinci-client.api.md
  • packages/davinci-client/api-report/davinci-client.types.api.md
  • packages/davinci-client/package.json
  • packages/davinci-client/src/lib/client.store.effects.test.ts
  • packages/davinci-client/src/lib/client.store.effects.ts
  • packages/davinci-client/src/lib/client.store.ts
  • packages/davinci-client/src/lib/client.store.utils.ts
  • packages/davinci-client/src/lib/fido/fido.ts
  • packages/davinci-client/src/lib/password-policy.rules.ts
  • packages/davinci-client/src/lib/wellknown.api.ts
  • packages/davinci-client/tsconfig.json
  • packages/davinci-client/tsconfig.lib.json
  • packages/journey-client/api-report/journey-client.api.md
  • packages/journey-client/api-report/journey-client.types.api.md
  • packages/journey-client/package.json
  • packages/journey-client/src/lib/client.store.ts
  • packages/journey-client/src/lib/client.store.utils.ts
  • packages/journey-client/src/lib/journey.utils.test.ts
  • packages/journey-client/src/lib/journey.utils.ts
  • packages/journey-client/src/lib/wellknown.api.ts
  • packages/journey-client/tsconfig.lib.json
  • packages/oidc-client/api-report/oidc-client.api.md
  • packages/oidc-client/api-report/oidc-client.types.api.md
  • packages/oidc-client/package.json
  • packages/oidc-client/src/lib/authorize.request.micros.test.ts
  • packages/oidc-client/src/lib/authorize.request.micros.ts
  • packages/oidc-client/src/lib/authorize.request.ts
  • packages/oidc-client/src/lib/authorize.request.utils.test.ts
  • packages/oidc-client/src/lib/client.store.ts
  • packages/oidc-client/src/lib/client.store.utils.ts
  • packages/oidc-client/src/lib/exchange.request.ts
  • packages/oidc-client/src/lib/exchange.utils.test.ts
  • packages/oidc-client/src/lib/exchange.utils.ts
  • packages/oidc-client/src/lib/logout.request.test.ts
  • packages/oidc-client/src/lib/logout.request.ts
  • packages/oidc-client/src/lib/session.micros.test.ts
  • packages/oidc-client/src/lib/session.micros.ts
  • packages/oidc-client/src/lib/shared-store.test.ts
  • packages/oidc-client/tsconfig.lib.json
  • packages/sdk-effects/wellknown/eslint.config.mjs
  • packages/sdk-effects/wellknown/package.json
  • packages/sdk-effects/wellknown/src/index.ts
  • packages/sdk-effects/wellknown/src/lib/wellknown.api.ts
  • packages/sdk-effects/wellknown/tsconfig.json
  • packages/sdk-effects/wellknown/tsconfig.lib.json
  • packages/sdk-effects/wellknown/tsconfig.spec.json
  • packages/sdk-effects/wellknown/vite.config.ts
  • packages/sdk-types/src/index.ts
  • packages/sdk-types/src/lib/store.types.ts
  • packages/sdk-utilities/src/lib/config/config.effects.ts
  • packages/sdk-utilities/src/lib/config/config.test.ts
  • packages/sdk-utilities/src/lib/config/config.types.ts
  • packages/sdk-utilities/src/lib/config/config.utils.ts
  • packages/sdk-utilities/src/lib/micro.utils.ts
  • pnpm-workspace.yaml
  • tools/release/package.json
  • tools/user-scripts/package.json
  • tsconfig.json
💤 Files with no reviewable changes (4)
  • tools/user-scripts/package.json
  • tools/release/package.json
  • packages/journey-client/src/lib/wellknown.api.ts
  • packages/davinci-client/src/lib/wellknown.api.ts

Comment on lines 35 to 37
Effect.gen(function* () {
const tokenValue = Redacted.value(bearerToken);
const tokenValue = Redacted.value(credential);
yield* Effect.log('checking bearer token', tokenValue);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file relevant section =="
sed -n '1,80p' e2e/mock-api-v2/src/middleware/Authorization.ts

echo
echo "== package / Effect dependency context =="
for f in package.json e2e/mock-api-v2/package.json e2e/mock-api-v2/package-lock.json e2e/mock-api-v2/pnpm-lock.yaml e2e/mock-api-v2/yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n '"effect"|effect|`@effect/platform`|effect/Effect' "$f" | head -80 || true
  fi
done

echo
echo "== search for Effect.log definition/imports in local code =="
rg -n "Effect\.log|from ['\"]effect/Effect|log:" e2e/mock-api-v2/src package.json e2e/mock-api-v2/package.json 2>/dev/null || true

Repository: ForgeRock/ping-javascript-sdk

Length of output: 4500


🌐 Web query:

Effect Effect.log effect library default logger signature Effect.log documentation

💡 Result:

In the Effect library, Effect.log is a utility function used to log messages at the default INFO level [1][2]. As of recent versions (specifically following changes in v2.0.0 and related updates), the function signature is variadic, allowing it to accept any number of arguments [3][4]. The signature is defined as follows: Effect.log(...message: ReadonlyArray): Effect<void, never, never> [3] Key details regarding Effect.log and logging in Effect: - Behavior: It logs messages using the configured logger, which defaults to the INFO level [1][2]. - Observability: Effect logs include metadata such as timestamps, log levels, fiber identifiers, and message content [1][2]. - Extensibility: You can control log levels (e.g., using Logger.withMinimumLogLevel) or implement custom loggers to handle output formats like JSON or structured data [1][5][2]. - Related Functions: Effect also provides specific variants for different log levels, such as Effect.logDebug, Effect.logInfo, Effect.logWarning, and Effect.logError, all of which share the same variadic signature structure [3][6]. For more advanced logging requirements, such as writing to files, you can utilize PlatformLogger or configure custom logging layers [1][7].

Citations:


Sensitive Data Exposure (CWE-532): Insertion of Sensitive Information into Log File

Reachability: External · Exploitability: Moderate

Reachability path
● Entry
  e2e/mock-api-v2/src/main.ts:51
  UserInfoMockHandler
│
▼
● Hop
  e2e/mock-api-v2/src/handlers/userinfo.handler.ts
│
▼
● Hop
  e2e/mock-api-v2/src/spec.ts:194
  Authorization
│
▼
● Sink
  e2e/mock-api-v2/src/middleware/Authorization.ts

Do not log the bearer credential.

Effect.log(...message: ReadonlyArray<any>) forwards every argument through the logger. Passing tokenValue = Redacted.value(credential) exposes the bearer token in logs. Keep this log entry limited to non-secret metadata, such as the log message only.

Proposed fix
-          yield* Effect.log('checking bearer token', tokenValue);
+          yield* Effect.log('checking bearer token');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Effect.gen(function* () {
const tokenValue = Redacted.value(bearerToken);
const tokenValue = Redacted.value(credential);
yield* Effect.log('checking bearer token', tokenValue);
Effect.gen(function* () {
const tokenValue = Redacted.value(credential);
yield* Effect.log('checking bearer token');
🤖 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 `@e2e/mock-api-v2/src/middleware/Authorization.ts` around lines 35 - 37, Update
the bearer-token check in Authorization middleware to stop passing tokenValue to
Effect.log; keep the log entry limited to the non-secret message while
preserving the surrounding credential validation flow.

Comment on lines +30 to +45
session = yield* sessionStorage
.createSession({
userId: request.cookies.userId,
createdAt: new Date(),
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
data: {},
})
.pipe(Effect.orDie);
} else {
yield* sessionStorage
.refreshSession(request.cookies.sessionId, sessionData.expiresAt)
.pipe(Effect.orDie);
session = sessionData;
}

return sessionData;
});
return yield* httpEffect.pipe(Effect.provideService(Session, session));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Return and persist the generated session ID.

SessionStorage.createSession generates the map key but returns only SessionData. This middleware then returns the downstream response without setting a sessionId cookie. A client cannot reference the new session on its next request, so the middleware creates a new session again.

Change the session-storage contract to return the generated ID. Set that ID on the response before returning it.

🤖 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 `@e2e/mock-api-v2/src/middleware/Session.ts` around lines 30 - 45, The session
creation flow in the Session middleware must retain the generated session ID and
persist it to the client. Update SessionStorage.createSession to return both the
generated ID and SessionData, capture that result when creating a session, and
set the sessionId cookie on the downstream response before returning it;
preserve the refresh path for existing sessions.

Comment on lines 66 to +71
const validateCapabilitiesResponse = (body: any) =>
pipe(
body,
Option.fromNullable,
Option.map((body) => body.parameters),
Option.map((parameters) => parameters.data),
Option.map((data) => data.formData),
Option.map((formData) => formData.value),
Effect.flatMap(validator),
);
Effect.gen(function* () {
if (body == null) return yield* Effect.fail(new HttpApiError.InternalServerError());
const value = body?.parameters?.data?.formData?.value;
if (value == null) return yield* Effect.fail(new HttpApiError.InternalServerError());
return yield* validator(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass the shape that validator expects.

validator matches a complete CapabilitiesRequestBody with parameters.data.formData.username and password. Line 69 passes only formData.value. The match falls through to Match.orElse(() => Effect.succeed(true)), so invalid credentials succeed instead of returning Unauthorized.

Pass the complete request body, or add a validator that explicitly matches the extracted value shape.

🤖 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 `@e2e/mock-api-v2/src/services/mock-env-helpers/index.ts` around lines 66 - 71,
The validateCapabilitiesResponse function passes only formData.value to
validator, which expects a complete CapabilitiesRequestBody and therefore
bypasses unauthorized handling. Pass the complete body expected by validator, or
replace it with a validator matching the extracted value shape while preserving
Unauthorized for invalid credentials.

Comment on lines +76 to +97
refreshSession: Effect.fn('RefreshSessionMiddleware')(function* (
sessionId: string,
expiryDate?: Date,
) {
const session = _store.get(sessionId);

if (!session) {
return Effect.fail(new Error('Session not found'));
}

cleanupExpiredSessions: Effect.fn('CleanupExpiredSessionsMiddleware')(function* () {
for (const [sessionId, session] of _store.entries()) {
if (isSessionExpired(session)) {
_store.delete(sessionId);
return new Error('Session has expired');
}

// Update expiry date
const newExpiryDate = expiryDate || new Date(Date.now() + 24 * 60 * 60 * 1000); // Default: 24 hours from now
session.expiresAt = newExpiryDate;
_store.set(sessionId, session);

return session.data;
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Signal failures with Effect.fail, not with returned values.

Inside a generator passed to Effect.fn, a return produces a success value. Line 83 therefore succeeds with an un-executed Effect as the value, and line 88 succeeds with an Error instance. The declared error channel stays never, so the Effect.orDie calls in e2e/mock-api-v2/src/middleware/Session.ts lines 25-40 never trigger. A missing or expired session is reported as a successful refresh.

updateSession at lines 65-74 has the same pattern, and the mixed union is why refreshSession is typed Effect.Effect<unknown, never, never> at line 19.

🐛 Proposed fix for failure signalling
-  updateSession: (
-    sessionId: string,
-    data: SessionData,
-  ) => Effect.Effect<SessionData | Error, never, never>;
-  refreshSession: (sessionId: string, expiryDate?: Date) => Effect.Effect<unknown, never, never>;
+  updateSession: (sessionId: string, data: SessionData) => Effect.Effect<SessionData, Error, never>;
+  refreshSession: (
+    sessionId: string,
+    expiryDate?: Date,
+  ) => Effect.Effect<Record<string, unknown> | undefined, Error, never>;
         updateSession: Effect.fn('UpdateSessionMiddleware')(function* (
           sessionId: string,
           data: SessionData,
         ) {
           if (!_store.has(sessionId)) {
-            return new Error('Session not found');
+            return yield* Effect.fail(new Error('Session not found'));
           }
           _store.set(sessionId, data);
           return data;
         }),
@@
           const session = _store.get(sessionId);
 
           if (!session) {
-            return Effect.fail(new Error('Session not found'));
+            return yield* Effect.fail(new Error('Session not found'));
           }
 
           if (isSessionExpired(session)) {
             _store.delete(sessionId);
-            return new Error('Session has expired');
+            return yield* Effect.fail(new Error('Session has expired'));
           }

Note that Session.ts already applies Effect.orDie, so the middleware behavior stays defined after this 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 `@e2e/mock-api-v2/src/services/session.service.ts` around lines 76 - 97, Update
refreshSession and updateSession in the session service so every missing or
expired session path uses Effect.fail with the appropriate error, rather than
returning an Effect or Error as a successful value. Keep successful session
updates returning the session data, and ensure their inferred error channels
allow the existing Effect.orDie calls in Session.ts to handle these failures.

Comment on lines +135 to +141
export function toSdkStore(injectable: InjectableStore): object {
return injectable as unknown as object;
}

/** Recover the InjectableStore from an opaque SdkStore handle. */
export function fromSdkStore(sdkStore: object): InjectableStore {
return sdkStore as unknown as InjectableStore;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline packages/oidc-client/src/lib/client.store.utils.ts --items all
ast-grep outline packages/oidc-client/src/lib/client.store.ts --items all

rg -n -C 8 \
  'fromSdkStore|toSdkStore|sharedStore|rootReducer\.inject|addMiddleware' \
  packages/oidc-client/src/lib packages/journey-client/src/lib packages/davinci-client/src/lib

Repository: ForgeRock/ping-javascript-sdk

Length of output: 35023


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n packages/oidc-client/src/lib/client.store.utils.ts
printf '\n--- oidc signature/body ---\n'
sed -n '53,100p' packages/oidc-client/src/lib/client.store.ts | cat -n
printf '\n--- SdkStore type ---\n'
rg -n -C 4 'export interface SdkStore|export type SdkStore|interface SdkStore|type SdkStore' packages -g '*.ts'
printf '\n--- error/create argument patterns ---\n'
rg -n -C 3 "type: 'argument_error'|argument_error|GenericError|isGenericError" packages/oidc-client/src/lib client.store.utils.ts packages/davinci-client/src/lib packages/journey-client/src/lib packages -g '*.ts' | head -n 220

python3 - <<'PY'
# Read-only behavioral probe: show how fake SdkStore values fail when destructure/inject path runs.
handles = [
    {},
    {store: object()},
    {store: object(), rootReducer: {}},
    {store: object(), rootReducer: {}, dynamicMiddleware: {}},
    {store: object(), rootReducer: {}, dynamicMiddleware: {}},
    {store: object(), rootReducer: {}, dynamicMiddleware: {addMiddleware: None}},
]
for i, h in enumerate(handles, 1):
    try:
        {store, rootReducer, dynamicMiddleware} = h
        try:
            rootReducer.inject("oidcApi")
            print(f"handle {i}: inject succeeded")
        except Exception as e:
            print(f"handle {i}: inject threw {type(e).__name__}: {e}")
    except AttributeError as e:
        print(f"handle {i}: destructured invalid handle threw {type(e).__name__}: {e}")
PY

Repository: ForgeRock/ping-javascript-sdk

Length of output: 26906


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n packages/sdk-types/src/lib/store.types.ts
printf '\n--- argument_error helper usages in oidc-client ---\n'
rg -n -C 4 "typeof|create.*argument|argument_error|GenericError" packages/oidc-client/src/lib packages/sdk-utilities/src/lib packages/sdk-types/src/lib packages/sdk-request-middleware/src/lib -g '*.ts'
printf '\n--- exports containing GenericError/argument_error ---\n'
rg -n "GenericError|argument" packages/sdk-types/src packages/sdk-utilities/src packages/sdk-request-middleware/src -g '*.ts'

Repository: ForgeRock/ping-javascript-sdk

Length of output: 50386


Reject invalid shared-SdkStore handles before injecting OIDC.

injectIntoStore(sharedStore) directly dereferences store, rootReducer.inject, and dynamicMiddleware.addMiddleware, so objects missing those properties cause unhandled failures during oidc() initialization. Validate sharedStore at the oidc() boundary and return a controlled argument_error; do not rely on the SdkStore runtime brand to block invalid values.

🤖 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/davinci-client/src/lib/client.store.utils.ts` around lines 135 -
141, Validate the sharedStore argument at the oidc() boundary before calling
injectIntoStore, requiring the store, rootReducer.inject, and
dynamicMiddleware.addMiddleware members used by injection. For invalid objects,
return the established controlled argument_error result; do not use the SdkStore
runtime brand as the validation mechanism.

Comment on lines +76 to 87
Effect.gen(function* () {
vi.spyOn(sdkOidc, 'generateAndStoreAuthUrlValues').mockImplementation(() => {
throw new Error('storage unavailable');
});
const exit = yield* Micro.exit(generateAuthValuesµ(config, wellknown));
expect(Micro.exitIsFailure(exit)).toBe(true);
if (!Micro.exitIsFailure(exit) || !Micro.causeIsFail(exit.cause)) return;
expect(exit.cause.error.type).toBe('auth_error');
expect(exit.cause.error.error).toBe('PAR_PARAM_BUILD_ERROR');
const exit = yield* Effect.exit(generateAuthValuesµ(config, wellknown));
expect(Exit.isFailure(exit)).toBe(true);
if (!Exit.isFailure(exit)) return;
const errorOpt = Cause.findErrorOption(exit.cause);
if (!Option.isSome(errorOpt)) return;
expect(errorOpt.value.type).toBe('auth_error');
expect(errorOpt.value.error).toBe('PAR_PARAM_BUILD_ERROR');
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail when the cause has no typed error.

Exit.isFailure(exit) also succeeds for defects. The return after Option.isSome(errorOpt) lets a defect-only failure pass without checking the expected error mapping.

  • packages/oidc-client/src/lib/authorize.request.micros.test.ts#L76-L87: throw or assert when errorOpt is None. Apply this change to every equivalent failure assertion in this file.
  • packages/oidc-client/src/lib/session.micros.test.ts#L196-L211: throw or assert when errorOpt is None. Apply this change to every equivalent failure assertion in this file.
Proposed fix
 const errorOpt = Cause.findErrorOption(exit.cause);
-if (!Option.isSome(errorOpt)) return;
+if (!Option.isSome(errorOpt)) {
+  throw new Error('Expected a typed failure');
+}
 expect(errorOpt.value.error).toBe('PAR_PARAM_BUILD_ERROR');
📍 Affects 2 files
  • packages/oidc-client/src/lib/authorize.request.micros.test.ts#L76-L87 (this comment)
  • packages/oidc-client/src/lib/session.micros.test.ts#L196-L211
🤖 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/oidc-client/src/lib/authorize.request.micros.test.ts` around lines
76 - 87, Ensure every equivalent failure assertion in
packages/oidc-client/src/lib/authorize.request.micros.test.ts at lines 76-87 and
packages/oidc-client/src/lib/session.micros.test.ts at lines 196-211 fails
explicitly when Cause.findErrorOption returns None, instead of returning early.
Preserve the existing typed-error assertions for Some results so defect-only
failures cannot pass.

@ryanbas21

Copy link
Copy Markdown
Collaborator Author

Superseded by a clean branch off main — see new PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant