chore(davinci-client): refactor node reducer updater to remove throws - #753
chore(davinci-client): refactor node reducer updater to remove throws#753ancheetah wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: fc3136f The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 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 |
📝 WalkthroughWalkthroughThe PR updates DaVinci collector update types, centralizes collector validation, changes invalid reducer updates to no-ops, updates related tests, and adds a patch-release changeset. ChangesCollector update refactor
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ClientStore
participant ValidationUtils
participant NodeReducer
Client->>ClientStore: submit collector update
ClientStore->>ValidationUtils: validate category and value
ValidationUtils-->>ClientStore: narrowed value or error
ClientStore->>NodeReducer: dispatch valid update
NodeReducer-->>ClientStore: updated state or unchanged state
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 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 |
0eecf39 to
fc3136f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
packages/davinci-client/src/lib/node.reducer.ts (1)
226-237: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHoist
getUpdateValueto module scope.The reducer re-creates this function on every
node/updateaction. It closes over nothing from the case body. Declare it once at module level next to the reducer.♻️ Proposed refactor
Add above
nodeCollectorReducer:/** * Validates `value` against `collector` and returns the narrowed value, or * `null` if validation failed. Discards the validation error: the * reducer only needs to know whether to no-op, not why. */ function getUpdateValue<T extends UpdatableCollectors>( collector: T, value: CollectorValueTypes, ): CollectorValueType<T> | null { const result = resolveCollectorUpdateValue(collector, value); return Either.isLeft(result) ? null : result.right; }Then remove the inner declaration:
- /** - * Validates `value` against `collector` and returns the narrowed value, or - * `null` if validation failed. Discards the validation error: the - * reducer only needs to know whether to no-op, not why. - */ - function getUpdateValue<T extends UpdatableCollectors>( - collector: T, - value: CollectorValueTypes, - ): CollectorValueType<T> | null { - const result = resolveCollectorUpdateValue(collector, value); - return Either.isLeft(result) ? null : result.right; - } -🤖 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/node.reducer.ts` around lines 226 - 237, Move the standalone getUpdateValue function from the node/update case body to module scope alongside nodeCollectorReducer, preserving its existing generic signature, validation logic, and documentation. Remove the inner declaration so the reducer reuses the module-level helper.packages/davinci-client/src/lib/node.reducer.test.ts (1)
357-386: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd no-op coverage for invalid values on other updatable categories.
These tests cover the category gate and the MetadataCollector value check. The new
resolveCollectorUpdateValuefailure paths for the other categories are untested in this reducer. Add cases such as a boolean value sent to aTextCollectorand an object value sent to aMultiSelectCollector, and assert the state stays unchanged.Also applies to: 388-429, 431-456, 2396-2414
🤖 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/node.reducer.test.ts` around lines 357 - 386, Extend the reducer tests around nodeCollectorReducer with no-op cases for invalid values across the remaining updatable collector categories, including a boolean value for a TextCollector and an object value for a MultiSelectCollector. Reuse the existing collector fixtures and action structure where appropriate, and assert each invalid update returns state unchanged to cover the resolveCollectorUpdateValue failure paths.packages/davinci-client/src/lib/client.store.utils.ts (2)
113-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffMove the logging effect out of this
*.utils.tsfile.
handleUpdateValidateErrorinvokescb(message)at line 118, so calling it writes a log entry. That makes the utility effectful. Keep*.utils.tspure, and place the single logging effect in an*.effects.tsmodule, or let each caller log before it returns the error response.As per coding guidelines: "Keep
*.utils.tsfiles pure and stateless; never put effectful logic in them. Place single isolated effects in*.effects.tsor multi-step workflows in*.micros.ts".🤖 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 113 - 128, Remove the cb(message) side effect from handleUpdateValidateError so the utility only constructs and returns the InternalErrorResponse. Move the single logging call into an appropriate effects module or into each caller immediately before returning the validation error, preserving one log entry per error.Source: Coding guidelines
169-259: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd a fallback for unmatched collectors.
Match.exhaustivethrows when runtime input bypasses TypeScript's exhaustiveness checks. Although current callers useisValidCollectorCategory, the exportedresolveCollectorUpdateValuecan receive an unmatched collector. Returnerr('Collector does not fall into a category that can be updated')withMatch.orElseinstead.🤖 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 169 - 259, Update resolveCollectorUpdateValue’s Match chain by replacing Match.exhaustive with Match.orElse that returns err('Collector does not fall into a category that can be updated') for unmatched runtime collectors, while preserving all existing collector-specific validation branches.packages/davinci-client/src/lib/client.types.test-d.ts (1)
182-182: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReplace
toMatchTypeOfwithtoExtend. The pinned Vitest version resolvesexpect-type1.2.2, which deprecatestoMatchTypeOf.toExtend<CollectorValueTypes>()preserves this assignability check; the assertion remains weaker than the previous exact-equality check.🤖 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.types.test-d.ts` at line 182, In the updater type assertion, replace the deprecated toMatchTypeOf call with toExtend while retaining CollectorValueTypes as the generic type, preserving the existing assignability check.
🤖 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/davinci-client/src/lib/node.reducer.ts`:
- Around line 259-271: Update the MultiValueCollector branch in the reducer to
honor action.payload.index when applying scalar updates, inserting or replacing
the value at the specified position as required by the Updater contract while
preserving full-array replacement behavior. If indexed updates are not
supported, instead remove index consistently from the Updater signature and
action payload.
---
Nitpick comments:
In `@packages/davinci-client/src/lib/client.store.utils.ts`:
- Around line 113-128: Remove the cb(message) side effect from
handleUpdateValidateError so the utility only constructs and returns the
InternalErrorResponse. Move the single logging call into an appropriate effects
module or into each caller immediately before returning the validation error,
preserving one log entry per error.
- Around line 169-259: Update resolveCollectorUpdateValue’s Match chain by
replacing Match.exhaustive with Match.orElse that returns err('Collector does
not fall into a category that can be updated') for unmatched runtime collectors,
while preserving all existing collector-specific validation branches.
In `@packages/davinci-client/src/lib/client.types.test-d.ts`:
- Line 182: In the updater type assertion, replace the deprecated toMatchTypeOf
call with toExtend while retaining CollectorValueTypes as the generic type,
preserving the existing assignability check.
In `@packages/davinci-client/src/lib/node.reducer.test.ts`:
- Around line 357-386: Extend the reducer tests around nodeCollectorReducer with
no-op cases for invalid values across the remaining updatable collector
categories, including a boolean value for a TextCollector and an object value
for a MultiSelectCollector. Reuse the existing collector fixtures and action
structure where appropriate, and assert each invalid update returns state
unchanged to cover the resolveCollectorUpdateValue failure paths.
In `@packages/davinci-client/src/lib/node.reducer.ts`:
- Around line 226-237: Move the standalone getUpdateValue function from the
node/update case body to module scope alongside nodeCollectorReducer, preserving
its existing generic signature, validation logic, and documentation. Remove the
inner declaration so the reducer reuses the module-level helper.
🪄 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: 7d0697d4-1d15-4665-b68e-0db06f1c4295
📒 Files selected for processing (10)
.changeset/five-badgers-rule.mdpackages/davinci-client/api-report/davinci-client.api.mdpackages/davinci-client/api-report/davinci-client.types.api.mdpackages/davinci-client/src/lib/client.store.tspackages/davinci-client/src/lib/client.store.utils.tspackages/davinci-client/src/lib/client.types.test-d.tspackages/davinci-client/src/lib/client.types.tspackages/davinci-client/src/lib/node.reducer.test.tspackages/davinci-client/src/lib/node.reducer.tspackages/davinci-client/src/lib/node.types.ts
| if (collector.category === 'MultiValueCollector') { | ||
| if (typeof action.payload.value !== 'string' && !Array.isArray(action.payload.value)) { | ||
| throw new Error('MultiValueCollector does not accept an object'); | ||
| const value = getUpdateValue(collector, action.payload.value); | ||
| if (value === null) { | ||
| return; | ||
| } | ||
| if (Array.isArray(action.payload.value)) { | ||
| collector.input.value = [...action.payload.value]; | ||
|
|
||
| if (Array.isArray(value)) { | ||
| collector.input.value = [...value]; | ||
| } else { | ||
| collector.input.value.push(action.payload.value); | ||
| collector.input.value.push(value); | ||
| } | ||
| return; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Find every producer and consumer of the update payload `index` field.
set -euo pipefail
fd -e ts -e tsx . packages/davinci-client/src | xargs rg -n -C4 '\bindex\b' -g '!*.test.ts' \
| rg -n -C4 'payload|Updater|update\(' || true
# Show the full node/update reducer case for confirmation.
ast-grep run --pattern 'addCase(updateCollectorValues, ($$$) => { $$$ })' --lang typescript packages/davinci-client/src/lib/node.reducer.tsRepository: ForgeRock/ping-javascript-sdk
Length of output: 4726
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- updater type and implementation ---'
sed -n '110,140p' packages/davinci-client/src/lib/client.types.ts
sed -n '320,375p' packages/davinci-client/src/lib/client.store.ts
printf '%s\n' '--- reducer action and relevant branches ---'
sed -n '40,85p' packages/davinci-client/src/lib/node.reducer.ts
sed -n '225,280p' packages/davinci-client/src/lib/node.reducer.ts
printf '%s\n' '--- update action declarations and all index references ---'
rg -n -C3 '\bindex\b|updateCollectorValues|nodeSlice\.actions\.update' packages/davinci-client/src/lib \
-g '*.ts' -g '!*.test.ts' -g '!*.test-d.ts'
printf '%s\n' '--- multi-value collector shape and update-value helper ---'
rg -n -C5 'MultiValueCollector|getUpdateValue|resolveCollectorUpdateValue' packages/davinci-client/src/lib \
-g '*.ts' -g '!*.test.ts' -g '!*.test-d.ts'Repository: ForgeRock/ping-javascript-sdk
Length of output: 50387
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path("packages/davinci-client/src/lib/node.reducer.ts")
text = p.read_text()
start = text.index("if (collector.category === 'MultiValueCollector')")
end = text.index("\n if (collector.type === 'DeviceAuthenticationCollector')", start)
branch = text[start:end]
print("--- MultiValueCollector reducer branch ---")
print(branch)
print("--- payload.index references in branch ---")
print(branch.count("action.payload.index"))
PY
printf '%s\n' '--- tests and documentation for updater index semantics ---'
rg -n -C4 'index|MultiValueCollector|update\(' packages/davinci-client/src/lib \
-g '*.test.ts' -g '*.test-d.ts' -g '*.md' \
| head -n 240
printf '%s\n' '--- multi-value factory and input initialization ---'
sed -n '700,760p' packages/davinci-client/src/lib/collector.utils.ts
rg -n -C4 'MultiValueCollector' packages/davinci-client/src/lib/collector.types.tsRepository: ForgeRock/ping-javascript-sdk
Length of output: 13675
Handle action.payload.index for MultiValueCollector updates. The public Updater contract exposes index, but the reducer ignores it and always appends scalar values. If index is obsolete, remove it from the Updater signature and action payload.
🤖 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/node.reducer.ts` around lines 259 - 271,
Update the MultiValueCollector branch in the reducer to honor
action.payload.index when applying scalar updates, inserting or replacing the
value at the specified position as required by the Updater contract while
preserving full-array replacement behavior. If indexed updates are not
supported, instead remove index consistently from the Updater signature and
action payload.
| ]) | ||
| ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
If we do this check at the store.ts level, do we need to do it here too?
| collector.input.value = [...value]; | ||
| } else { | ||
| collector.input.value.push(action.payload.value); | ||
| collector.input.value.push(value); |
There was a problem hiding this comment.
Is this considered safe because it uses Immer? I can't recall, i know immer does the mutation but I would have to look up the push.
We could just use a concat if we needed instead.
There was a problem hiding this comment.
Is this considered safe because it uses Immer?
Yes, that is correct. You can see it explained here: https://redux-toolkit.js.org/usage/immer-reducers#redux-toolkit-and-immer. Though, I have no issue with explicitly writing it in with immutable grammar.
| return err('Value argument cannot be undefined'); | ||
| } | ||
|
|
||
| return Match.value<UpdatableCollectors>(collector).pipe( |
There was a problem hiding this comment.
Honestly i really like this.
The only piece that irks me slightly is that my brain tells me we are handling the errors in the wrong place, however it's not a big deal. I think this makes sense.
I also don't mind the ok and err notation here since Result<T, E> = Ok<T> | Err<E> will be the basic type going foward in effect, however in this context it is mixing two different vocabularies for the same underlying structure.
cerebrl
left a comment
There was a problem hiding this comment.
I like this, but I do worry about mulling in more of the Effect library due to Match. If we can get some numbers around this for context, that would be great. I very much do like the use of Match though :)
| // Every branch below is a no-op rather than a throw: `update()` in | ||
| // client.store.ts already validates the id exists and the category is | ||
| // updatable before dispatching, so reaching an unmatched case here means | ||
| // the action was dispatched directly, bypassing that gate. |
There was a problem hiding this comment.
Since these reducers are not accessible via our public API, do we need these checks? I like writing strong, defensive code for our public APIs, but for private methods we call internally, I'm not sure of the value.
| collector.input.value = [...value]; | ||
| } else { | ||
| collector.input.value.push(action.payload.value); | ||
| collector.input.value.push(value); |
There was a problem hiding this comment.
Is this considered safe because it uses Immer?
Yes, that is correct. You can see it explained here: https://redux-toolkit.js.org/usage/immer-reducers#redux-toolkit-and-immer. Though, I have no issue with explicitly writing it in with immutable grammar.
JIRA Ticket
https://pingidentity.atlassian.net/browse/SDKS-5177
Description
What
Refactors collector-value validation in
davinci-clientsonode.reducer.tsno longer throws on invalid updates, moving that validation into a single shared function used by bothclient.store.ts'supdate()method and the reducer.update()also becomes lazy: it now defers collector-state lookup and validation until the returned updater function is invoked, rather than at call time.Changes
resolveCollectorUpdateValue(client.store.utils.ts), anEffect Match-based validator that is the single source of truth for the collector → accepted-value-type mapping. Returns anEitherinstead of throwing.isValidCollectorCategory, a reusable type guard for narrowing aCollectorsunion member by category.node.reducer.ts'snode/updatecase no longer throws; every unmatched/invalid branch is now a documented no-op, sinceupdate()already validates before dispatching. Added explicitreturns after each branch (some previously relied on fallthrough).client.store.ts'supdate()now returns its validation-and-dispatch logic entirely inside the returned updater function (previously it validated eagerly whenupdate(collector)was called, then returned a dispatch-only closure). Failure paths now log vialog.errorbefore returning anInternalErrorResponse, consistent withvalidate().UpdatableCollectorstype (client.types.ts) to replace the repeatedSingleValueCollectors | MultiSelectCollector | ObjectValueCollectors | AutoCollectorsunion used by bothupdate()andUpdater<T>.CollectorValueTypeforMultiSelectCollector/MultiValueCollectorfromstring[]tostring | string[], matching the reducer's existing support for pushing a single string value.CollectorCategorytype alias (node.types.ts).Summary by CodeRabbit
New Features
"continue"status.Bug Fixes