feat(sdk): shared store for multiple SDK clients + @forgerock/sdk-store - #729
feat(sdk): shared store for multiple SDK clients + @forgerock/sdk-store#729ryanbas21 wants to merge 5 commits into
Conversation
🦋 Changeset detectedLatest commit: 28d2507 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
View your CI Pipeline Execution ↗ for commit 28d2507
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
@forgerock/davinci-client
@forgerock/device-client
@forgerock/journey-client
@forgerock/oidc-client
@forgerock/protect
@forgerock/sdk-types
@forgerock/sdk-utilities
@forgerock/iframe-manager
@forgerock/sdk-logger
@forgerock/sdk-oidc
@forgerock/sdk-request-middleware
@forgerock/storage
@forgerock/sdk-store
commit: |
|
Deployed 82c3792 to https://ForgeRock.github.io/ping-javascript-sdk/pr-729/82c3792bdb4a42617c69295d8a64b100fc663665 branch gh-pages in ForgeRock/ping-javascript-sdk |
📦 Bundle Size Analysis📦 Bundle Size Analysis🚨 Significant Changes🔻 @forgerock/sdk-oidc - 3.5 KB (-2.2 KB, -38.4%) 🆕 New Packages🆕 @forgerock/sdk-store - 10.7 KB (new) 📊 Minor Changes📉 @forgerock/davinci-client - 54.3 KB (-0.9 KB) ➖ No Changes➖ @forgerock/sdk-logger - 1.6 KB 15 packages analyzed • Baseline from latest Legend🆕 New package ℹ️ How bundle sizes are calculated
🔄 Updated automatically on each push to this PR |
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (23.59%) is below the target coverage (40.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #729 +/- ##
==========================================
+ Coverage 18.07% 23.59% +5.52%
==========================================
Files 155 163 +8
Lines 24398 25765 +1367
Branches 1203 1674 +471
==========================================
+ Hits 4410 6080 +1670
+ Misses 19988 19685 -303
🚀 New features to boost your workflow:
|
410cb72 to
9a87a40
Compare
Allow davinci(), journey(), and oidc() to share a single Redux store, so
the OpenID Connect discovery document is fetched once regardless of how many
clients are initialised. Three ownership modes are supported:
// Mode 1 — implicit (unchanged default)
const client = await oidc({ config });
// Mode 2 — one client owns the store (primary sharing story)
const dv = await davinci({ config: davinciConfig });
const oc = await oidc({ config: oidcConfig, store: dv.store });
// Mode 3 — consumer owns the store
const store = createSdkStore();
await davinci({ config: davinciConfig, store });
await oidc({ config: oidcConfig, store });
New @forgerock/sdk-store package (scope:sdk-effects) owns:
- The single canonical wellknownApi instance and its RTK Query selectors.
createWellknownSelector was rebuilt on every call; it is now memoized per
URL via a module-level Map, restoring the memoization that was always cold.
- initWellknownQuery and isValidWellknownResponse (moved from sdk-oidc).
- The shared store contract: SdkStore / SdkStoreHandle / createSdkStore /
injectClient / isSdkStoreHandle.
- clientExtra() — the per-client slot resolver used by every *.api.ts.
Request middleware and logger are scoped per client (security fix):
Before, every *.api.ts resolved its middleware and logger from the store's
store-wide thunk extraArgument. On a shared store that extra belongs to the
owning client, so DaVinci middleware ran against AUTHORIZE, PAR,
TOKEN_EXCHANGE, REVOKE, USER_INFO and END_SESSION, and oidc's own logger was
silently discarded. extraArgument is now a registry keyed by each api's
reducerPath; clientExtra() returns only the calling client's slot. A missing
or malformed slot yields empty middleware and an error-level fallback logger —
never another client's values.
Public API changes:
- oidc() takes store as part of its options object (not a positional second
arg), consistent with every other factory in the SDK.
- davinci() and journey() expose store: SdkStore on the returned client.
Both factories also accept store?: SdkStore as input (mode 2 / 3).
- One OIDC client per store: a second oidc() with a different clientId
returns argument_error instead of silently overwriting token state.
- oidc() validates arguments before injecting into a store (RTK inject is
irreversible); a rejected call leaves a caller-owned store unchanged.
- A value that fails isSdkStoreHandle() returns argument_error, not TypeError.
Store contract is declared once structurally:
InjectableStore was declared three times — twice identically and once as a
weaker mirror — joined only by `as unknown as`. The fictional __sdkStoreBrand
required casts on both sides with no compile-time link between them. There
is now one structural interface in sdk-store; producers satisfy it without a
cast, consumers receive it without a cast. The two unavoidable widenings live
in store.effects.ts with documented rationale. Deleted: toSdkStore,
fromSdkStore x3, InjectableStore x3, __sdkStoreBrand, JourneyStore,
injectIntoStore, the requestMiddleware log.warn (obsolete under per-client
scoping), and the two tests that existed only to cover that warning.
Layering enforcement:
sdk-store previously depended on sdk-oidc to access initWellknownQuery,
which violated the scope:sdk-effects constraint that allows only sdk-types
and sdk-utilities. Moving the file removes the only cross-effects dependency
and promotes enforce-module-boundaries from warn to error. All 23 affected
projects lint clean.
Tests:
- sdk-store: 40 tests (was 0; passWithNoTests removed).
- wellknownApi: cache-per-URL, error mapping, selector memoization (instance
identity asserted).
- clientExtra: never returns another client's slot; degrades gracefully on
any malformed extra.
- Middleware isolation: DaVinci middleware does not run against OIDC requests,
proven for both the foreign-slot shape and the old flat shape.
- State-shape assertions: combineSlices keys are now implicit (via slice.name)
rather than literal; pinned so a rename fails here, not in selectors.
- shared-store.test.ts fully rewritten: fake handle removed; all three D1
modes tested against the real factories and real createSdkStore().
- store-lifecycle.test.ts: validate-before-inject; clientId conflict guard.
- store.effects.test.ts: createSdkStore, isSdkStoreHandle, injectClient.
E2E:
- Playwright suite in davinci-suites asserts exactly one .well-known network
request when davinci() and oidc() share a store (mode 2). Requests are
intercepted via page.route() so no live credential is needed.
BREAKING (sdk-oidc): initWellknownQuery and isValidWellknownResponse are
removed from @forgerock/sdk-oidc and are now exported from @forgerock/sdk-store.
Update imports if you were using them directly.
9a87a40 to
aae6ae5
Compare
| * not require a live PingOne endpoint. | ||
| */ | ||
|
|
||
| const WELLKNOWN_URL = 'https://sdk-test.example.com/as/.well-known/openid-configuration'; |
Summary
Allows
davinci(),journey(), andoidc()to share a single Redux store, so OpenID Connect discovery (.well-known/openid-configuration) is fetched once instead of once per client.What's in this PR
New package:
@forgerock/sdk-storeA new
scope:sdk-effectspackage that owns:wellknownApiinstance and discovery cache (previously duplicated in each client package)SdkStore,SdkStoreHandle,createSdkStore(),injectClient()initWellknownQuery,isValidWellknownResponseStore sharing
All three client factories accept an optional
storeoption:Omitting
storeis unchanged behaviour — each client creates its own store, exactly as before.davinci()andjourney()expose their store asclient.storefor apps that want to pass it tooidc()without creating one explicitly.Middleware and logging are scoped per client
Each client's
requestMiddlewareandloggerare registered against that client alone. Middleware passed todavinci()orjourney()is never applied to OIDC requests (AUTHORIZE,PAR,TOKEN_EXCHANGE,REVOKE,USER_INFO,END_SESSION), and vice versa. Both options are honoured on a shared store.One OIDC client per store
oidc()mounts at a fixed key. Initialising a second OIDC client on the same store with a differentclientIdreturns anargument_errorinstead of silently overwriting the first client's token state. Re-initialising with the sameclientIdis idempotent. Use a separate store perclientId.Validation before attachment
oidc()validates its arguments before attaching to a store, so a rejected call no longer leaves a caller-provided store modified. Passing a non-SDK-store value tostorereturns anargument_errorinstead of throwing.Bug fixes
createWellknownSelectorpreviously rebuilt its selector on every call, so the cache never took effect.enforce-module-boundarieslint rule promoted fromwarntoerroracross the repo. All packages pass.Breaking changes
@forgerock/sdk-oidc:initWellknownQueryandisValidWellknownResponsemove to@forgerock/sdk-store. Update imports if you were using them directly.Testing
@forgerock/sdk-storepackage (store.effects.test.ts,store.utils.test.ts,wellknown.api.test.ts)store-shape.test.ts,store-lifecycle.test.ts,shared-store.test.ts)e2e/davinci-suites/src/shared-store.test.ts)