Conversation
- Rewrite edit/view/table-display/filter binary widgets as hex-only UI with
clean signal-based state. Edit emits {type:'Buffer', data:number[]} and
converts incoming hex strings to the byte-array shape on init so the
save payload is always binary even if the user does not touch the field.
- Parent db-table-row-edit.updateField now replaces a primitive field value
when the incoming update is an object, so Buffer-JSON emissions actually
land in tableRowValues.
- Stop converting Buffer to hex strings on reads: delete
convertBinaryDataInRowUtil and drop the binary->hex loop from
processRowsUtil. Buffer values flow from the DAO into the HTTP response
and Nest serializes them as {type:'Buffer', data:[...]} natively.
- Accept Buffer-JSON (and Buffer/Uint8Array/hex) on writes via shared
toBinaryBuffer helper used by convertHexDataInRowUtil and
convertHexDataInPrimaryKeyUtil. Wire binary conversion into
bulk-update-rows-in-table so bulk updates go through the same path as
single-row updates.
- Update Postgres binary e2e tests to derive hex for the search query
from the returned Buffer-JSON and compare rows with deepEqual.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Introduce frontend/src/app/lib/binary.ts with parseBinaryValue, stringToBytes, hexStringToBytes, bytesToHex, and toBufferJson. The edit, view, and table-display widgets now parse any incoming value (string, Buffer-JSON, Uint8Array) to a byte array first and derive the hex display from bytes. Strings received from the server are treated as latin1 binary strings (one char = one byte) rather than hex, so pg's default bytea-as-string output decodes correctly. Hex input typed into the edit or filter widgets still goes through hexStringToBytes for user-facing validation. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
📝 WalkthroughWalkthroughThis PR introduces comprehensive binary data handling improvements across the application. The backend refactors row-processing pipelines to modify when and how binary-to-hex and hex-to-binary conversions occur, removes binary conversion from certain post-fetch operations, and updates related utility functions. The frontend adds new dedicated binary UI components (filter, edit, view, display) to replace generic file components and provides binary-specific formatting and validation logic. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 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 |
There was a problem hiding this comment.
Pull request overview
This PR introduces first-class “Binary” handling across the UI (display/view/edit/filter) and adjusts backend handling so binary values can round-trip as Buffer-like payloads instead of being coerced to hex strings early.
Changes:
- Add a new
Binarytable widget type and map common binary DB column types to new Binary UI components. - Add shared binary utilities (parse/hex encode/decode) plus comprehensive unit tests for helpers and components.
- Extend filtering UX to support
in/betweenfor default (number/datetime) filters, and update backend row update paths to accept Buffer-like inputs.
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| shared-code/src/shared/enums/table-widget-type.enum.ts | Adds Binary widget type enum value. |
| frontend/src/app/lib/binary.ts | New binary parsing/hex encode-decode helpers and Buffer-JSON adapter. |
| frontend/src/app/lib/binary.spec.ts | Unit tests for binary helper functions. |
| frontend/src/app/consts/table-display-types.ts | Map binary column types to BinaryDisplayComponent instead of FileDisplayComponent. |
| frontend/src/app/consts/record-view-types.ts | Map binary column types to BinaryRecordViewComponent instead of FileRecordViewComponent. |
| frontend/src/app/consts/record-edit-types.ts | Map binary column types to BinaryEditComponent instead of FileEditComponent. |
| frontend/src/app/consts/filter-types.ts | Map binary column types to BinaryFilterComponent. |
| frontend/src/app/components/ui-components/table-display-fields/binary/binary.component.ts | New table cell binary display (hex, truncation, copy-to-clipboard). |
| frontend/src/app/components/ui-components/table-display-fields/binary/binary.component.spec.ts | Tests for binary table display behavior. |
| frontend/src/app/components/ui-components/table-display-fields/binary/binary.component.html | Template for binary table display + copy button. |
| frontend/src/app/components/ui-components/table-display-fields/binary/binary.component.css | Styling for monospace hex display. |
| frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.ts | New record view binary display (longer truncation + tooltip). |
| frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.spec.ts | Tests for record view binary behavior (tooltip/truncation). |
| frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.html | Template for record view binary display + copy button. |
| frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.css | Styling/layout for record view hex rendering. |
| frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.ts | New binary editor using hex textarea with validation and Buffer-JSON output. |
| frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.spec.ts | Tests for binary edit parsing + emitted values. |
| frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.html | Template for binary edit textarea + validation error. |
| frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.css | Styling for binary edit form field. |
| frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.ts | Add in/between comparator handling and associated state. |
| frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.spec.ts | Tests for in/between behavior and comparator options. |
| frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.html | Render IN text input or BETWEEN dual inputs depending on comparator. |
| frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.css | Layout rules for BETWEEN dual inputs. |
| frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.ts | Adds in/between modes to the dedicated datetime filter component. |
| frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.spec.ts | Tests for datetime in/between emissions and restore logic. |
| frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.html | UI additions for datetime in/between mode inputs. |
| frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.css | Layout rules for datetime BETWEEN inputs. |
| frontend/src/app/components/ui-components/filter-fields/binary/binary.component.ts | New binary filter component (hex + comparator modes). |
| frontend/src/app/components/ui-components/filter-fields/binary/binary.component.spec.ts | Tests for binary filter modes and emissions. |
| frontend/src/app/components/ui-components/filter-fields/binary/binary.component.html | Template for binary filter comparator + hex input. |
| frontend/src/app/components/ui-components/filter-fields/binary/binary.component.css | Layout for binary filter row. |
| frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.ts | Safer object-merge update logic for edited row values. |
| backend/test/ava-tests/saas-tests/postgres-with-binary-e2e.test.ts | Update search to use hex derived from returned binary payload; deepEqual binary value. |
| backend/test/ava-tests/non-saas-tests/non-saas-postgres-with-binary-e2e.test.ts | Same as above for non-saas suite. |
| backend/src/helpers/binary-to-hex.ts | Add toBinaryBuffer to normalize inputs to Buffer. |
| backend/src/entities/table/utils/process-found-rows-util.ts | Stop converting binary columns to hex during row post-processing. |
| backend/src/entities/table/utils/convert-hex-data-in-row.util.ts | Use toBinaryBuffer and handle null/empty values. |
| backend/src/entities/table/utils/convert-hex-data-in-primary-key.util.ts | Use toBinaryBuffer and handle null/empty values. |
| backend/src/entities/table/utils/convert-binary-data-in-row.util.ts | Removed (no longer converting outgoing binary to hex). |
| backend/src/entities/table/use-cases/update-row-in-table.use.case.ts | Convert incoming hex/Buffer-like values to Buffer before update; stop outgoing binary-to-hex conversion. |
| backend/src/entities/table/use-cases/get-table-rows.use.case.ts | Adjust processRowsUtil call signature and related imports. |
| backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts | Remove outgoing binary-to-hex conversion and reorder imports. |
| backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts | Convert incoming binary values to Buffer before bulk update. |
| backend/src/entities/table/use-cases/add-row-in-table.use.case.ts | Convert incoming binary values to Buffer before insert; stop outgoing binary-to-hex conversion. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -33,7 +50,9 @@ export class DateTimeFilterComponent extends BaseFilterFieldComponent implements | |||
| } | |||
|
|
|||
| ngAfterViewInit(): void { | |||
| if (this.value) { | |||
| if (this.filterMode === 'between' || this.filterMode === 'in') { | |||
| this.onComparatorChange.emit(this.filterMode); | |||
| } else if (this.value) { | |||
| this.onComparatorChange.emit(this.filterMode); | |||
There was a problem hiding this comment.
DateTimeFilterComponent can't restore previously-selected 'between'/'in' modes from saved filters: filterMode always starts as 'last_day', so the ngOnInit branches that restore array values never run, and ngAfterViewInit will emit 'last_day' as the comparator even when value is an array. Consider accepting an @Input() comparator (passed from the filters dialog) and setting filterMode from it before emitting, or explicitly inferring the mode from the incoming value and not emitting a preset comparator for array values.
| export function toBinaryBuffer(value: unknown): Buffer { | ||
| if (Buffer.isBuffer(value)) return value; | ||
| if (value instanceof Uint8Array) return Buffer.from(value); | ||
| if (typeof value === 'string') return hexToBinary(value); | ||
| if (value && typeof value === 'object') { | ||
| const v = value as { type?: string; data?: unknown }; | ||
| if (v.type === 'Buffer' && Array.isArray(v.data)) return Buffer.from(v.data as number[]); | ||
| if (Array.isArray(v.data)) return Buffer.from(v.data as number[]); | ||
| return Buffer.from(Object.values(value as Record<string, number>)); | ||
| } | ||
| return hexToBinary(String(value)); | ||
| } |
There was a problem hiding this comment.
toBinaryBuffer converts any string via Buffer.from(value, 'hex') without validating that the string is valid hex. This can silently turn malformed user input (or unexpected strings) into an empty/partial Buffer, leading to silent data corruption instead of a validation error. Recommend validating the string first (hex-only, even length) and throwing a 400/validation error when invalid; similarly, avoid Buffer.from(Object.values(...)) unless you can guarantee numeric byte values.
| .set('Cookie', firstUserToken) | ||
| .set('Content-Type', 'application/json') | ||
| .set('Accept', 'application/json'); | ||
| t.is(getTableRowsResponse.status, 200); |
There was a problem hiding this comment.
This assertion is checking getTableRowsResponse.status after the second request. It should assert getTableRowsWithSearchResponse.status so the test fails if the search request returns a non-200 status.
| t.is(getTableRowsResponse.status, 200); | |
| t.is(getTableRowsWithSearchResponse.status, 200); |
| .set('Accept', 'application/json'); | ||
|
|
||
| const getTableRowsWithSearchRO = JSON.parse(getTableRowsWithSearchResponse.text); | ||
| t.is(getTableRowsResponse.status, 200); |
There was a problem hiding this comment.
This assertion is checking getTableRowsResponse.status after the search request. It should assert getTableRowsWithSearchResponse.status so the test actually verifies the second request succeeded.
| t.is(getTableRowsResponse.status, 200); | |
| t.is(getTableRowsWithSearchResponse.status, 200); |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.ts (1)
613-632:⚠️ Potential issue | 🟡 MinorObject reference mutation pattern works for known widgets but lacks test coverage for object payloads.
The in-place merge (delete + assign) assumes callers emit complete object payloads. Evidence shows this holds for the two widgets that emit objects:
- Binary widget always emits
{ type: 'Buffer', data: number[] }(complete shape)- Money widget always emits either
{ amount, currency }or a scalar (complete shape)However, there are no tests validating updateField with object payloads—all spec tests use scalars (strings, null, numbers). This is a gap: if any future widget emits a partial object, keys not in the new payload would be lost.
Two safer alternatives without behavior changes:
- Add explicit tests for object-based updates to prevent regressions when new widgets are added
- Use shallow merge without deletion:
Object.assign(existing, updatedValue)alone, which is safer if callers ever send partial updatesThe OnPush change detection concern is less likely in practice here since ndc-dynamic bindings emit events directly on each input change.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.ts` around lines 613 - 632, The in-place object replacement in updateField (in db-table-row-edit.component, operating on this.tableRowValues[field]) currently deletes existing props then Object.assigns the new object; add safety and tests: change the mutation to a shallow merge using only Object.assign(existing, updatedValue) (remove the for-delete loop) so partial payloads don't drop keys, and add unit tests calling updateField with object payloads (e.g., the binary shape {type:'Buffer',data:[] } and money shape {amount, currency}) to assert existing objects are merged correctly and that this.isPrimaryKeyUpdated still toggles when field is in keyAttributesFromURL.
🧹 Nitpick comments (11)
frontend/src/app/components/ui-components/filter-fields/binary/binary.component.css (1)
1-15: Consider extracting shared filter-row styles.
.filter-row,.comparator-field, and.value-fieldare duplicated verbatim acrossfilter-fields/binary/binary.component.css,filter-fields/date-time/date-time.component.css, andfilter-fields/default-filter/default-filter.component.css. Consider hoisting them into a shared stylesheet (or a base filter component style) to reduce drift as the filter UI evolves.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/filter-fields/binary/binary.component.css` around lines 1 - 15, The three duplicated CSS rules (.filter-row, .comparator-field, .value-field) should be extracted into a shared stylesheet or base component style to avoid drift; create a new shared CSS/SCSS (or a base component stylesheet) containing those class rules and replace the duplicated blocks in binary.component.css, date-time.component.css, and default-filter.component.css with an import or by extending the base styles (ensure the shared file is imported in the components or referenced by the base filter component), keeping selectors identical so existing markup (classes .filter-row, .comparator-field, .value-field) continues to work.frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.css (1)
24-31: Mixing::ng-deepwith descendant selector — double-check the intended scope.Two concerns on these selectors:
.between-fields ::ng-deep > ndc-dynamicplaces::ng-deepbetween the parent and child, which shadow-pierces everything after it. Sincendc-dynamicis actually a direct child in the template (frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.html:21-37), you can style it without::ng-deepat all. The::ng-deepis only needed for the nestedmat-form-fieldrule below.::ng-deepis deprecated (and is what triggers the stylelintselector-pseudo-element-no-unknownerrors on lines 24 and 29). If you want to keep stylelint clean without disabling the rule, either add::ng-deepto the stylelintpseudoElementsallowlist project-wide, or narrow usage and add inline/* stylelint-disable-next-line selector-pseudo-element-no-unknown */on lines 24 and 29.♻️ Suggested adjustment
-.between-fields ::ng-deep > ndc-dynamic { - flex: 1; - min-width: 0; -} - -.between-fields ::ng-deep mat-form-field { - width: 100%; -} +.between-fields > ndc-dynamic { + flex: 1; + min-width: 0; +} + +/* stylelint-disable-next-line selector-pseudo-element-no-unknown */ +.between-fields ::ng-deep mat-form-field { + width: 100%; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.css` around lines 24 - 31, The selector `.between-fields ::ng-deep > ndc-dynamic` unnecessarily uses `::ng-deep` and should be changed to target the direct child (`.between-fields > ndc-dynamic`) so it no longer shadow-pierces; for the nested rule that needs shadow-piercing (`.between-fields ::ng-deep mat-form-field`) either keep `::ng-deep` but suppress the stylelint warning with `/* stylelint-disable-next-line selector-pseudo-element-no-unknown */` immediately above that selector, or instead add `::ng-deep` to the stylelint `pseudoElements` allowlist project-wide—pick one approach and update the selectors `.between-fields > ndc-dynamic` and `.between-fields ::ng-deep mat-form-field` accordingly.frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.html (1)
3-4: Consider always showing hex tooltip when truncated.The current logic is correct: empty tooltip when not truncated avoids noise. A minor nit — you could use
nullinstead of''to fully disable the tooltip (matTooltipwith empty string still registers a no-op tooltip instance). Non-blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.html` around lines 3 - 4, The matTooltip binding should provide null when not truncated to fully disable the tooltip; update the binding that uses isTruncated() and hexValue() so it returns hexValue() when isTruncated() is true and null otherwise (replace the current '' fallback), referencing the isTruncated() and hexValue() expressions used in that matTooltip attribute to locate where to change.frontend/src/app/components/ui-components/filter-fields/binary/binary.component.html (1)
11-25: LGTM — hex filter UI is clean.Conditional rendering via
@iffollows Angular 19 control flow guidelines, validator wiring and error message are correctly tied to the template ref.Minor nit: consider adding
autocomplete="off"andspellcheck="false"on the hex input to avoid browser interference.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/filter-fields/binary/binary.component.html` around lines 11 - 25, Add autocomplete="off" and spellcheck="false" to the hex input element to prevent browser autofill and spellchecking from interfering with binary/hex entry; update the <input matInput ...> tag (the element using hexValidator, name="{{label()}}-{{key()}}", `#inputElement`, `#hexInput`="ngModel", [(ngModel)]="hexValue" and (ngModelChange)="onHexValueChange($event)") to include these attributes while keeping existing bindings and validators intact.frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.html (1)
3-8: Remove form wrapper concern; form context is properly inherited via parent.The binary component is dynamically loaded within the parent form context (via
ndc-dynamicinside<form#editRowForm="ngForm">), so[(ngModel)]binding and validators work correctly. No form wrapper is needed.However, consider adding
autocomplete="off"andspellcheck="false"on the hex textarea to prevent browser autofill and spellcheck interference with hex data.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.html` around lines 3 - 8, The parent form context is inherited so no extra form wrapper is needed; update the hex textarea element used by the BinaryComponent template to disable browser autofill and spellcheck by adding autocomplete="off" and spellcheck="false" to the <textarea> that binds [(ngModel)]="hexData" (template ref `#hexContent`="ngModel", and uses label(), key(), required(), disabled(), readonly(), and onHexChange()) so the hex input won't be interfered with by browser autofill or spellchecking.frontend/src/app/components/ui-components/table-display-fields/binary/binary.component.ts (1)
18-25: Signals should beprotectedorprivate readonly, notpublic.Per coding guidelines, component state signals must not be exposed as
public. Since these are consumed from the template,protected readonlyis the appropriate visibility.♻️ Proposed change
- public readonly bytes = computed(() => parseBinaryValue(this.value())); - public readonly hexValue = computed(() => bytesToHex(this.bytes())); - - public readonly displayText = computed(() => { + protected readonly bytes = computed(() => parseBinaryValue(this.value())); + protected readonly hexValue = computed(() => bytesToHex(this.bytes())); + + protected readonly displayText = computed(() => {Note: the sibling spec (
binary.component.spec.ts) asserts against these signals; if they must remain accessible from tests, consider a testable type alias as per the spec guidelines rather than widening visibility topublic.As per coding guidelines: "Signals for component state must be declared as
protectedorprivate readonlyand initialized withsignal()orcomputed()".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/table-display-fields/binary/binary.component.ts` around lines 18 - 25, The three component signals bytes, hexValue, and displayText are declared as public but should be protected readonly per component state guidelines; change their declarations from public readonly to protected readonly for bytes, hexValue, and displayText (in binary.component.ts) so they remain accessible to the template but not publicly exposed; if tests require access (binary.component.spec.ts), avoid widening visibility and instead introduce a testable type alias or testing accessor as described in the spec guidelines rather than making these signals public.frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.ts (1)
30-33: Minor:hexValidation()({ value: this.hexData } as never)is a typing escape hatch.Casting the control payload to
neversilences TS rather than modeling the validator input. Passing a small{ value: string } as unknown as AbstractControl(or calling a simpleisValidHex(hex)helper) would be cleaner and keep type info honest.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.ts` around lines 30 - 33, onHexChange currently uses a typing escape hatch by calling hexValidation()({ value: this.hexData } as never); instead, change it to pass a correctly-typed control payload or use a simple helper: either construct a minimal AbstractControl-like object (e.g. { value: this.hexData } cast only to unknown then to AbstractControl) when calling hexValidation(), or replace the validator call with a dedicated isValidHex(this.hexData) helper and set this.isInvalidInput based on that; update onHexChange and references to hexValidation/validation logic accordingly so TypeScript types are preserved and no 'as never' cast is used.frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.ts (1)
21-27: Consider extracting the hex-display logic shared withBinaryDisplayComponent.The identical
MAX_DISPLAY_LENGTH/displayText/isTruncatedblock also lives infrontend/src/app/components/ui-components/table-display-fields/binary/binary.component.ts. A small helper infrontend/src/app/lib/binary.ts(e.g.formatHexForDisplay(hex, max)) would avoid drift if the truncation rule changes later.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.ts` around lines 21 - 27, Extract the shared hex truncation logic into a helper (e.g., formatHexForDisplay(hex: string, max?: number)) in frontend/src/app/lib/binary.ts and replace the duplicated code in both BinaryDisplayComponent and the table-display binary component: remove the inline logic that uses MAX_DISPLAY_LENGTH in displayText and isTruncated, call formatHexForDisplay(this.hexValue(), MAX_DISPLAY_LENGTH) to compute displayText (returning the em-dash for empty values and ellipses when truncated) and compute isTruncated by comparing the raw hex length to max (or expose an API like { text, truncated } from the helper); ensure the helper is exported and both components import and use it so truncation behaviour is centralized.frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.ts (1)
115-127:onBetweenLowerChangeandonBetweenUpperChangeare byte-for-byte identical — consolidate.Both recompose
[lower, upper]and emit the same events. A single private helper (e.g._emitBetween()) invoked from the two template handlers would eliminate the duplication.Proposed refactor
- onBetweenLowerChange(): void { - const lower = this._composeDateTime(this.lowerDate, this.lowerTime); - const upper = this._composeDateTime(this.upperDate, this.upperTime); - this.onFieldChange.emit([lower, upper]); - this.onComparatorChange.emit('between'); - } - - onBetweenUpperChange(): void { - const lower = this._composeDateTime(this.lowerDate, this.lowerTime); - const upper = this._composeDateTime(this.upperDate, this.upperTime); - this.onFieldChange.emit([lower, upper]); - this.onComparatorChange.emit('between'); - } + onBetweenLowerChange(): void { + this._emitBetween(); + } + + onBetweenUpperChange(): void { + this._emitBetween(); + }and add a private helper (placed with other private methods):
private _emitBetween(): void { const lower = this._composeDateTime(this.lowerDate, this.lowerTime); const upper = this._composeDateTime(this.upperDate, this.upperTime); this.onFieldChange.emit([lower, upper]); this.onComparatorChange.emit('between'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.ts` around lines 115 - 127, Both onBetweenLowerChange and onBetweenUpperChange contain identical logic; create a private helper named _emitBetween that calls _composeDateTime for this.lowerDate/this.lowerTime and this.upperDate/this.upperTime and then emits this.onFieldChange and this.onComparatorChange('between'), replace both onBetweenLowerChange and onBetweenUpperChange bodies with a single call to this._emitBetween(), and place _emitBetween alongside the other private methods.frontend/src/app/lib/binary.ts (1)
22-32:hexStringToBytessilently swallows malformed input.Returning
[]on a bad parse is convenient for rendering, but callers likeBinaryEditComponent.emitCurrentValue()guard against this only via a separate validator run. If that validator ever disagrees withparseInt(..., 16)(e.g. whitespace, embedded0xprefix), you can end up emittingtoBufferJson([])and silently wiping the field. Consider either throwing/returningnulland making callers handle it, or documenting the invariant that the caller has already validated the hex.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/app/lib/binary.ts` around lines 22 - 32, The hexStringToBytes function currently swallows malformed input by returning [] — change it to return null for any invalid input (use hex = hex.trim(); strip a leading "0x"/"0X" before normalizing) and make parse checks explicit (if parseInt yields NaN -> return null) so callers can distinguish "empty result" from "invalid input"; update BinaryEditComponent.emitCurrentValue() (and any other callers) to handle a null return (skip emitting toBufferJson([]) and surface validation errors) rather than relying on [] to signal failure.backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts (1)
147-153: Inconsistent mutation semantics between related filtering functions — consider aligning patterns.
filterReferencedTablesByPermissionreturnsPromise<void>and mutates the input array in place (reassigning each item'sreferenced_byproperty), making the current code at lines 147-153 safe. However, this differs fromfilterForeignKeysByReadPermission(used at line 106), which returnsPromise<Array<ForeignKeyDS>>with a new filtered array without mutating the input. For consistency and clarity, these two closely-related permission filters should use the same mutation pattern.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts` around lines 147 - 153, The two related permission filters are inconsistent: filterForeignKeysByReadPermission returns a new filtered array while filterReferencedTablesByPermission currently mutates its input (referencedTableNamesAndColumns) and returns void; align them by changing filterReferencedTablesByPermission to return Promise<Array<...>> (a new filtered array) and update the call site in get-row-by-primary-key.use.case to assign its result back to referencedTableNamesAndColumns (i.e., referencedTableNamesAndColumns = await filterReferencedTablesByPermission(...)); update the filterReferencedTablesByPermission signature and internal logic to build and return a new array rather than mutating referenced_by in place so both functions share the same non-mutating pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/src/helpers/binary-to-hex.ts`:
- Around line 23-34: The toBinaryBuffer function silently corrupts data: the
string branch calls hexToBinary (which uses Buffer.from(...,'hex')) without
validating hex content/length, and the generic-object branch uses
Buffer.from(Object.values(...)) which coerces non-numeric values to garbage;
update toBinaryBuffer to validate strings with a hex regex and even length and
throw on invalid input (referencing toBinaryBuffer and hexToBinary), and for the
object branch (the path that currently does Buffer.from(Object.values(value as
Record<string, number>))) validate that Object.values are all finite
numbers/bytes before constructing the Buffer and throw or reject otherwise; keep
the existing valid branches for Buffer/Uint8Array and the explicit
{type:'Buffer',data:[]} path.
In
`@frontend/src/app/components/ui-components/filter-fields/binary/binary.component.ts`:
- Around line 25-27: ngOnInit currently converts this.value directly with
hexStringToBytes -> bytesToHex which silently drops non-hex inputs; instead
normalize the incoming value with parseBinaryValue first (e.g. call
parseBinaryValue(this.value) to get a normalized binary form) and then convert
that normalized result to bytes/hex for hexValue. Update ngOnInit to use
parseBinaryValue before hexStringToBytes/bytesToHex (and import parseBinaryValue
if missing) so persisted non-hex shapes are handled consistently with the
view/edit/display components.
In
`@frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.html`:
- Around line 53-61: The comma-separated ISO datetimes input in the 'in' branch
is missing the readonly binding; update the <input> used for the 'in' branch
(the one bound to [(ngModel)]="inValueText" and
(ngModelChange)="onInTextChange($event)") to include the same readonly binding
as the other branches (i.e. add [readonly]="readonly()") alongside the existing
[required]="required()" and [disabled]="disabled()" so it respects the
readonly() state like the other inputs (use normalizedLabel(), label(), key()
references as-is).
- Around line 19-52: The between-mode date/time inputs do not respect the
component's readonly state; add the readonly binding to each input in the
between branch so they mirror the non-preset branch. Specifically, for the
inputs named "{{label()}}-{{key()}}-lower-date", "-lower-time", "-upper-date",
and "-upper-time" (the inputs using
[(ngModel)]="lowerDate"/"lowerTime"/"upperDate"/"upperTime" and change handlers
onBetweenLowerChange/onBetweenUpperChange), add [readonly]="readonly()"
alongside the existing [required]="required()" and [disabled]="disabled()". This
ensures the component methods normalizedLabel(), label(), key(), required(),
disabled(), and readonly() are used consistently.
In
`@frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.ts`:
- Around line 129-143: The onInTextChange handler currently emits user-typed
tokens raw; instead validate and normalize each comma-split token to an ISO
datetime before emitting: in onInTextChange use new Date(token) to check
validity (new Date(token).toString() !== 'Invalid Date'), map valid dates to new
Date(token).toISOString(), filter out invalid tokens, and then emit either the
array of ISO strings via onFieldChange or emit undefined if no valid tokens
remain; also keep emitting the comparator with onComparatorChange('in') and
consider setting a component error state/property when invalid tokens are
dropped so the UI can surface feedback (referencing onInTextChange, inValueText,
onFieldChange, onComparatorChange).
In
`@frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.html`:
- Around line 10-20: The input for the 'in' comparator is missing the readonly
binding; update the <input> inside the comparator === 'in' branch (the element
bound to [(ngModel)]="inValueText" and (ngModelChange)="onInTextChange($event)")
to include [readonly]="readonly()" so it behaves consistently with other filter
fields (same area using normalizedLabel(), label(), key(), required(), and
disabled()).
- Around line 21-37: The between-mode dynamic inputs are missing the same
readonly/autofocus flags used by innerInputs; update the component class where
lowerInputs and upperInputs are constructed so both include readonly:
this.comparator === 'empty', and ensure upperInputs also includes autofocus:
this.autofocus() (matching how innerInputs sets these properties) while keeping
other input properties intact.
In
`@frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.ts`:
- Around line 104-122: onComparatorSelect currently only clears array/range
state when entering 'in'/'between', but not when leaving them; update
onComparatorSelect to detect when previous was 'in' or 'between' and the new
comparator is not, then reset inValueText, betweenLower, betweenUpper and set
value = undefined and call this.onFieldChange.emit(undefined) before emitting
this.onComparatorChange.emit(comparator) so the inner dynamic component is not
re-bound with stale array/range state; locate the onComparatorSelect method and
add that conditional branch handling the reverse transition.
In
`@frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.ts`:
- Line 19: The static property on BinaryComponent is incorrectly set to 'file'
which can misroute type-dispatch; update BinaryComponent's static type to
'binary' (or remove the static type if unnecessary) so callers like
DefaultFilterComponent.comparatorOptions no longer treat it as
FileEditComponent; locate the static declaration in BinaryComponent (static type
= 'file') and change it to static type = 'binary' (or delete it) and run tests
to verify no callers rely on the 'file' value.
- Around line 24-28: The ngOnInit currently calls emitCurrentValue() which
causes untouched binary fields to be reported as changed; remove that init-time
emission or guard it by comparing the normalized value before emitting: in
ngOnInit (method ngOnInit of this component) compute the normalized/parsed value
via parseBinaryValue(this.value()) and only call emitCurrentValue() if the
freshly constructed BinaryBufferJson differs from the incoming value() (or skip
calling emitCurrentValue() here entirely and instead call emitCurrentValue()
from onHexChange when the user edits the hexData); update references to
bytesToHex/hexData as needed so onHexChange performs the emission on real user
interaction.
In `@frontend/src/app/consts/record-edit-types.ts`:
- Around line 219-225: The mapping incorrectly treats Oracle LONG as binary;
update the record-edit-types mapping so that LONG is routed to a text editor
(e.g., LongTextEditComponent) instead of BinaryEditComponent and ensure only
RAW, 'LONG RAW', BLOB, and BFILE remain mapped to BinaryEditComponent; change
the comment above the block to reflect that LONG is character data and verify
any imports/usages of LongTextEditComponent and BinaryEditComponent (symbols:
LONG, 'LONG RAW', RAW, BLOB, BFILE, BinaryEditComponent, LongTextEditComponent).
- Around line 257-260: The edit mapping for the MSSQL type "image" is
inconsistent: record-edit-types.ts maps "image" to BinaryEditComponent while
record-view-types.ts maps it to ImageRecordViewComponent; pick a single semantic
(render-as-image or edit-as-raw-binary) and make mappings consistent. To fix,
change the "image" entry in the edit registry (where BinaryEditComponent is
referenced) to the image-specific editor (e.g., ImageEditComponent) if you want
view/edit to render as an image, or change the view registry (where
ImageRecordViewComponent is referenced) to BinaryRecordViewComponent if you want
raw hex; then propagate the chosen mapping to other registries
(filter/table-display) so the same "image" symbol resolves the same across
view/edit/filter/table-display.
In `@frontend/src/app/consts/record-view-types.ts`:
- Around line 195-200: The mapping currently assigns LONG to
BinaryRecordViewComponent but in Oracle LONG is character data; update the
record view mappings so LONG maps to CharacterRecordViewComponent while keeping
BLOB, BFILE, RAW, and 'LONG RAW' mapped to BinaryRecordViewComponent (i.e.,
change the LONG entry to use CharacterRecordViewComponent and ensure 'LONG RAW'
remains BinaryRecordViewComponent to match the earlier fix in
record-edit-types.ts).
In `@frontend/src/app/lib/binary.ts`:
- Around line 3-12: parseBinaryValue currently treats any string as raw
characters which corrupts DynamoDB hex-encoded binaries (produced by binaryToHex
in transformAndFilterRow); update parseBinaryValue in
frontend/src/app/lib/binary.ts to detect hex-encoded strings (e.g., a
/^([0-9a-fA-F]{2})+$/ pattern) and, when matched, decode hex pairs into bytes
(byte per two hex chars) instead of calling stringToBytes; keep existing
branches for Uint8Array and {data: number[]} unchanged and fall back to
stringToBytes only for non-hex strings so all drivers (DynamoDB hex strings or
Buffer-style objects) are handled consistently.
---
Outside diff comments:
In
`@frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.ts`:
- Around line 613-632: The in-place object replacement in updateField (in
db-table-row-edit.component, operating on this.tableRowValues[field]) currently
deletes existing props then Object.assigns the new object; add safety and tests:
change the mutation to a shallow merge using only Object.assign(existing,
updatedValue) (remove the for-delete loop) so partial payloads don't drop keys,
and add unit tests calling updateField with object payloads (e.g., the binary
shape {type:'Buffer',data:[] } and money shape {amount, currency}) to assert
existing objects are merged correctly and that this.isPrimaryKeyUpdated still
toggles when field is in keyAttributesFromURL.
---
Nitpick comments:
In `@backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts`:
- Around line 147-153: The two related permission filters are inconsistent:
filterForeignKeysByReadPermission returns a new filtered array while
filterReferencedTablesByPermission currently mutates its input
(referencedTableNamesAndColumns) and returns void; align them by changing
filterReferencedTablesByPermission to return Promise<Array<...>> (a new filtered
array) and update the call site in get-row-by-primary-key.use.case to assign its
result back to referencedTableNamesAndColumns (i.e.,
referencedTableNamesAndColumns = await filterReferencedTablesByPermission(...));
update the filterReferencedTablesByPermission signature and internal logic to
build and return a new array rather than mutating referenced_by in place so both
functions share the same non-mutating pattern.
In
`@frontend/src/app/components/ui-components/filter-fields/binary/binary.component.css`:
- Around line 1-15: The three duplicated CSS rules (.filter-row,
.comparator-field, .value-field) should be extracted into a shared stylesheet or
base component style to avoid drift; create a new shared CSS/SCSS (or a base
component stylesheet) containing those class rules and replace the duplicated
blocks in binary.component.css, date-time.component.css, and
default-filter.component.css with an import or by extending the base styles
(ensure the shared file is imported in the components or referenced by the base
filter component), keeping selectors identical so existing markup (classes
.filter-row, .comparator-field, .value-field) continues to work.
In
`@frontend/src/app/components/ui-components/filter-fields/binary/binary.component.html`:
- Around line 11-25: Add autocomplete="off" and spellcheck="false" to the hex
input element to prevent browser autofill and spellchecking from interfering
with binary/hex entry; update the <input matInput ...> tag (the element using
hexValidator, name="{{label()}}-{{key()}}", `#inputElement`, `#hexInput`="ngModel",
[(ngModel)]="hexValue" and (ngModelChange)="onHexValueChange($event)") to
include these attributes while keeping existing bindings and validators intact.
In
`@frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.ts`:
- Around line 115-127: Both onBetweenLowerChange and onBetweenUpperChange
contain identical logic; create a private helper named _emitBetween that calls
_composeDateTime for this.lowerDate/this.lowerTime and
this.upperDate/this.upperTime and then emits this.onFieldChange and
this.onComparatorChange('between'), replace both onBetweenLowerChange and
onBetweenUpperChange bodies with a single call to this._emitBetween(), and place
_emitBetween alongside the other private methods.
In
`@frontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.css`:
- Around line 24-31: The selector `.between-fields ::ng-deep > ndc-dynamic`
unnecessarily uses `::ng-deep` and should be changed to target the direct child
(`.between-fields > ndc-dynamic`) so it no longer shadow-pierces; for the nested
rule that needs shadow-piercing (`.between-fields ::ng-deep mat-form-field`)
either keep `::ng-deep` but suppress the stylelint warning with `/*
stylelint-disable-next-line selector-pseudo-element-no-unknown */` immediately
above that selector, or instead add `::ng-deep` to the stylelint
`pseudoElements` allowlist project-wide—pick one approach and update the
selectors `.between-fields > ndc-dynamic` and `.between-fields ::ng-deep
mat-form-field` accordingly.
In
`@frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.html`:
- Around line 3-8: The parent form context is inherited so no extra form wrapper
is needed; update the hex textarea element used by the BinaryComponent template
to disable browser autofill and spellcheck by adding autocomplete="off" and
spellcheck="false" to the <textarea> that binds [(ngModel)]="hexData" (template
ref `#hexContent`="ngModel", and uses label(), key(), required(), disabled(),
readonly(), and onHexChange()) so the hex input won't be interfered with by
browser autofill or spellchecking.
In
`@frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.ts`:
- Around line 30-33: onHexChange currently uses a typing escape hatch by calling
hexValidation()({ value: this.hexData } as never); instead, change it to pass a
correctly-typed control payload or use a simple helper: either construct a
minimal AbstractControl-like object (e.g. { value: this.hexData } cast only to
unknown then to AbstractControl) when calling hexValidation(), or replace the
validator call with a dedicated isValidHex(this.hexData) helper and set
this.isInvalidInput based on that; update onHexChange and references to
hexValidation/validation logic accordingly so TypeScript types are preserved and
no 'as never' cast is used.
In
`@frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.html`:
- Around line 3-4: The matTooltip binding should provide null when not truncated
to fully disable the tooltip; update the binding that uses isTruncated() and
hexValue() so it returns hexValue() when isTruncated() is true and null
otherwise (replace the current '' fallback), referencing the isTruncated() and
hexValue() expressions used in that matTooltip attribute to locate where to
change.
In
`@frontend/src/app/components/ui-components/record-view-fields/binary/binary.component.ts`:
- Around line 21-27: Extract the shared hex truncation logic into a helper
(e.g., formatHexForDisplay(hex: string, max?: number)) in
frontend/src/app/lib/binary.ts and replace the duplicated code in both
BinaryDisplayComponent and the table-display binary component: remove the inline
logic that uses MAX_DISPLAY_LENGTH in displayText and isTruncated, call
formatHexForDisplay(this.hexValue(), MAX_DISPLAY_LENGTH) to compute displayText
(returning the em-dash for empty values and ellipses when truncated) and compute
isTruncated by comparing the raw hex length to max (or expose an API like {
text, truncated } from the helper); ensure the helper is exported and both
components import and use it so truncation behaviour is centralized.
In
`@frontend/src/app/components/ui-components/table-display-fields/binary/binary.component.ts`:
- Around line 18-25: The three component signals bytes, hexValue, and
displayText are declared as public but should be protected readonly per
component state guidelines; change their declarations from public readonly to
protected readonly for bytes, hexValue, and displayText (in binary.component.ts)
so they remain accessible to the template but not publicly exposed; if tests
require access (binary.component.spec.ts), avoid widening visibility and instead
introduce a testable type alias or testing accessor as described in the spec
guidelines rather than making these signals public.
In `@frontend/src/app/lib/binary.ts`:
- Around line 22-32: The hexStringToBytes function currently swallows malformed
input by returning [] — change it to return null for any invalid input (use hex
= hex.trim(); strip a leading "0x"/"0X" before normalizing) and make parse
checks explicit (if parseInt yields NaN -> return null) so callers can
distinguish "empty result" from "invalid input"; update
BinaryEditComponent.emitCurrentValue() (and any other callers) to handle a null
return (skip emitting toBufferJson([]) and surface validation errors) rather
than relying on [] to signal failure.
🪄 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
Run ID: 35727b39-a74e-4ee0-ac81-6ed0b5a20f07
📒 Files selected for processing (44)
backend/src/entities/table/use-cases/add-row-in-table.use.case.tsbackend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.tsbackend/src/entities/table/use-cases/get-row-by-primary-key.use.case.tsbackend/src/entities/table/use-cases/get-table-rows.use.case.tsbackend/src/entities/table/use-cases/update-row-in-table.use.case.tsbackend/src/entities/table/utils/convert-binary-data-in-row.util.tsbackend/src/entities/table/utils/convert-hex-data-in-primary-key.util.tsbackend/src/entities/table/utils/convert-hex-data-in-row.util.tsbackend/src/entities/table/utils/process-found-rows-util.tsbackend/src/helpers/binary-to-hex.tsbackend/test/ava-tests/non-saas-tests/non-saas-postgres-with-binary-e2e.test.tsbackend/test/ava-tests/saas-tests/postgres-with-binary-e2e.test.tsfrontend/src/app/components/db-table-row-edit/db-table-row-edit.component.tsfrontend/src/app/components/ui-components/filter-fields/binary/binary.component.cssfrontend/src/app/components/ui-components/filter-fields/binary/binary.component.htmlfrontend/src/app/components/ui-components/filter-fields/binary/binary.component.spec.tsfrontend/src/app/components/ui-components/filter-fields/binary/binary.component.tsfrontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.cssfrontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.htmlfrontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.spec.tsfrontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.tsfrontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.cssfrontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.htmlfrontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.spec.tsfrontend/src/app/components/ui-components/filter-fields/default-filter/default-filter.component.tsfrontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.cssfrontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.htmlfrontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.spec.tsfrontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.tsfrontend/src/app/components/ui-components/record-view-fields/binary/binary.component.cssfrontend/src/app/components/ui-components/record-view-fields/binary/binary.component.htmlfrontend/src/app/components/ui-components/record-view-fields/binary/binary.component.spec.tsfrontend/src/app/components/ui-components/record-view-fields/binary/binary.component.tsfrontend/src/app/components/ui-components/table-display-fields/binary/binary.component.cssfrontend/src/app/components/ui-components/table-display-fields/binary/binary.component.htmlfrontend/src/app/components/ui-components/table-display-fields/binary/binary.component.spec.tsfrontend/src/app/components/ui-components/table-display-fields/binary/binary.component.tsfrontend/src/app/consts/filter-types.tsfrontend/src/app/consts/record-edit-types.tsfrontend/src/app/consts/record-view-types.tsfrontend/src/app/consts/table-display-types.tsfrontend/src/app/lib/binary.spec.tsfrontend/src/app/lib/binary.tsshared-code/src/shared/enums/table-widget-type.enum.ts
💤 Files with no reviewable changes (2)
- backend/src/entities/table/utils/process-found-rows-util.ts
- backend/src/entities/table/utils/convert-binary-data-in-row.util.ts
| export function toBinaryBuffer(value: unknown): Buffer { | ||
| if (Buffer.isBuffer(value)) return value; | ||
| if (value instanceof Uint8Array) return Buffer.from(value); | ||
| if (typeof value === 'string') return hexToBinary(value); | ||
| if (value && typeof value === 'object') { | ||
| const v = value as { type?: string; data?: unknown }; | ||
| if (v.type === 'Buffer' && Array.isArray(v.data)) return Buffer.from(v.data as number[]); | ||
| if (Array.isArray(v.data)) return Buffer.from(v.data as number[]); | ||
| return Buffer.from(Object.values(value as Record<string, number>)); | ||
| } | ||
| return hexToBinary(String(value)); | ||
| } |
There was a problem hiding this comment.
Silent data corruption risk for non-hex strings and heterogeneous objects.
Two branches in toBinaryBuffer silently produce corrupted buffers instead of signaling the problem:
- Line 26:
hexToBinary(value)delegates toBuffer.from(value, 'hex'), which silently skips any non-hex character and truncates on odd length. A stray non-hex character from the frontend (e.g. a user-entered ASCII value, base64, or legacy representation) becomes a partial/empty buffer that is persisted to the DB without error. - Line 31:
Buffer.from(Object.values(value as Record<string, number>))is reached for any unrecognized object shape. If any value is non-numeric,Buffer.fromcoerces it to0/garbage, again without error.
Given this helper is now in the write path for both rows and primary keys (convertHexDataInRowUtil / convertHexDataInPrimaryKeyUtil), a malformed payload can silently write wrong bytes. Consider validating hex input (e.g. /^[0-9a-fA-F]*$/ with even length) and either throwing or rejecting the unknown-object fallback.
🛡️ Proposed hardening
export function toBinaryBuffer(value: unknown): Buffer {
if (Buffer.isBuffer(value)) return value;
if (value instanceof Uint8Array) return Buffer.from(value);
- if (typeof value === 'string') return hexToBinary(value);
+ if (typeof value === 'string') {
+ if (value.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(value)) {
+ throw new Error('Invalid hex string for binary column');
+ }
+ return hexToBinary(value);
+ }
if (value && typeof value === 'object') {
const v = value as { type?: string; data?: unknown };
if (v.type === 'Buffer' && Array.isArray(v.data)) return Buffer.from(v.data as number[]);
if (Array.isArray(v.data)) return Buffer.from(v.data as number[]);
- return Buffer.from(Object.values(value as Record<string, number>));
+ const values = Object.values(value as Record<string, unknown>);
+ if (!values.every((n) => typeof n === 'number' && Number.isInteger(n) && n >= 0 && n <= 255)) {
+ throw new Error('Unsupported object shape for binary column');
+ }
+ return Buffer.from(values as number[]);
}
- return hexToBinary(String(value));
+ throw new Error(`Unsupported value type for binary column: ${typeof value}`);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/src/helpers/binary-to-hex.ts` around lines 23 - 34, The
toBinaryBuffer function silently corrupts data: the string branch calls
hexToBinary (which uses Buffer.from(...,'hex')) without validating hex
content/length, and the generic-object branch uses
Buffer.from(Object.values(...)) which coerces non-numeric values to garbage;
update toBinaryBuffer to validate strings with a hex regex and even length and
throw on invalid input (referencing toBinaryBuffer and hexToBinary), and for the
object branch (the path that currently does Buffer.from(Object.values(value as
Record<string, number>))) validate that Object.values are all finite
numbers/bytes before constructing the Buffer and throw or reject otherwise; keep
the existing valid branches for Buffer/Uint8Array and the explicit
{type:'Buffer',data:[]} path.
| override ngOnInit(): void { | ||
| this.hexValue = bytesToHex(hexStringToBytes(this.value ?? '')); | ||
| } |
There was a problem hiding this comment.
Silent loss of initial value when it isn't valid hex.
hexStringToBytes returns [] for malformed hex, so any pre-existing filter value that isn't pure hex (e.g. a char-code string, a Buffer-JSON object, or odd whitespace) is silently dropped here and hexValue becomes ''. If the filter is restored from a persisted state that was saved in a non-hex shape, the user loses their filter without feedback. Consider routing through parseBinaryValue first (to mirror the view/edit/display components) so the initial value is normalized consistently.
Proposed change
-import { bytesToHex, hexStringToBytes } from 'src/app/lib/binary';
+import { bytesToHex, parseBinaryValue } from 'src/app/lib/binary';
@@
- override ngOnInit(): void {
- this.hexValue = bytesToHex(hexStringToBytes(this.value ?? ''));
- }
+ override ngOnInit(): void {
+ this.hexValue = bytesToHex(parseBinaryValue(this.value ?? ''));
+ }📝 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.
| override ngOnInit(): void { | |
| this.hexValue = bytesToHex(hexStringToBytes(this.value ?? '')); | |
| } | |
| override ngOnInit(): void { | |
| this.hexValue = bytesToHex(parseBinaryValue(this.value ?? '')); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@frontend/src/app/components/ui-components/filter-fields/binary/binary.component.ts`
around lines 25 - 27, ngOnInit currently converts this.value directly with
hexStringToBytes -> bytesToHex which silently drops non-hex inputs; instead
normalize the incoming value with parseBinaryValue first (e.g. call
parseBinaryValue(this.value) to get a normalized binary form) and then convert
that normalized result to bytes/hex for hexValue. Update ngOnInit to use
parseBinaryValue before hexStringToBytes/bytesToHex (and import parseBinaryValue
if missing) so persisted non-hex shapes are handled consistently with the
view/edit/display components.
| @if (filterMode === 'between') { | ||
| <div class="value-field between-fields"> | ||
| <div class="field-couple"> | ||
| <mat-form-field appearance="outline"> | ||
| <mat-label>{{normalizedLabel()}} (from date)</mat-label> | ||
| <input type="date" matInput name="{{label()}}-{{key()}}-lower-date" | ||
| [required]="required()" [disabled]="disabled()" | ||
| [(ngModel)]="lowerDate" (change)="onBetweenLowerChange()"> | ||
| </mat-form-field> | ||
|
|
||
| <mat-form-field appearance="outline"> | ||
| <mat-label>(from time)</mat-label> | ||
| <input type="time" matInput name="{{label()}}-{{key()}}-lower-time" | ||
| [required]="required()" [disabled]="disabled()" | ||
| [(ngModel)]="lowerTime" (change)="onBetweenLowerChange()"> | ||
| </mat-form-field> | ||
| </div> | ||
|
|
||
| <div class="field-couple"> | ||
| <mat-form-field appearance="outline"> | ||
| <mat-label>{{normalizedLabel()}} (to date)</mat-label> | ||
| <input type="date" matInput name="{{label()}}-{{key()}}-upper-date" | ||
| [required]="required()" [disabled]="disabled()" | ||
| [(ngModel)]="upperDate" (change)="onBetweenUpperChange()"> | ||
| </mat-form-field> | ||
|
|
||
| <mat-form-field appearance="outline"> | ||
| <mat-label>(to time)</mat-label> | ||
| <input type="time" matInput name="{{label()}}-{{key()}}-upper-time" | ||
| [required]="required()" [disabled]="disabled()" | ||
| [(ngModel)]="upperTime" (change)="onBetweenUpperChange()"> | ||
| </mat-form-field> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Inconsistency: readonly not propagated to between-mode inputs.
The existing non-preset branch (lines 66–78) binds [readonly]="readonly()", but the new between branch only binds required and disabled. If this component can be rendered read-only, the between fields will remain editable.
Proposed fix
- <input type="date" matInput name="{{label()}}-{{key()}}-lower-date"
- [required]="required()" [disabled]="disabled()"
- [(ngModel)]="lowerDate" (change)="onBetweenLowerChange()">
+ <input type="date" matInput name="{{label()}}-{{key()}}-lower-date"
+ [required]="required()" [disabled]="disabled()" [readonly]="readonly()"
+ [(ngModel)]="lowerDate" (change)="onBetweenLowerChange()">(Apply similarly to the three remaining inputs in the between block.)
📝 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.
| @if (filterMode === 'between') { | |
| <div class="value-field between-fields"> | |
| <div class="field-couple"> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>{{normalizedLabel()}} (from date)</mat-label> | |
| <input type="date" matInput name="{{label()}}-{{key()}}-lower-date" | |
| [required]="required()" [disabled]="disabled()" | |
| [(ngModel)]="lowerDate" (change)="onBetweenLowerChange()"> | |
| </mat-form-field> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>(from time)</mat-label> | |
| <input type="time" matInput name="{{label()}}-{{key()}}-lower-time" | |
| [required]="required()" [disabled]="disabled()" | |
| [(ngModel)]="lowerTime" (change)="onBetweenLowerChange()"> | |
| </mat-form-field> | |
| </div> | |
| <div class="field-couple"> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>{{normalizedLabel()}} (to date)</mat-label> | |
| <input type="date" matInput name="{{label()}}-{{key()}}-upper-date" | |
| [required]="required()" [disabled]="disabled()" | |
| [(ngModel)]="upperDate" (change)="onBetweenUpperChange()"> | |
| </mat-form-field> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>(to time)</mat-label> | |
| <input type="time" matInput name="{{label()}}-{{key()}}-upper-time" | |
| [required]="required()" [disabled]="disabled()" | |
| [(ngModel)]="upperTime" (change)="onBetweenUpperChange()"> | |
| </mat-form-field> | |
| </div> | |
| </div> | |
| `@if` (filterMode === 'between') { | |
| <div class="value-field between-fields"> | |
| <div class="field-couple"> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>{{normalizedLabel()}} (from date)</mat-label> | |
| <input type="date" matInput name="{{label()}}-{{key()}}-lower-date" | |
| [required]="required()" [disabled]="disabled()" [readonly]="readonly()" | |
| [(ngModel)]="lowerDate" (change)="onBetweenLowerChange()"> | |
| </mat-form-field> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>(from time)</mat-label> | |
| <input type="time" matInput name="{{label()}}-{{key()}}-lower-time" | |
| [required]="required()" [disabled]="disabled()" [readonly]="readonly()" | |
| [(ngModel)]="lowerTime" (change)="onBetweenLowerChange()"> | |
| </mat-form-field> | |
| </div> | |
| <div class="field-couple"> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>{{normalizedLabel()}} (to date)</mat-label> | |
| <input type="date" matInput name="{{label()}}-{{key()}}-upper-date" | |
| [required]="required()" [disabled]="disabled()" [readonly]="readonly()" | |
| [(ngModel)]="upperDate" (change)="onBetweenUpperChange()"> | |
| </mat-form-field> | |
| <mat-form-field appearance="outline"> | |
| <mat-label>(to time)</mat-label> | |
| <input type="time" matInput name="{{label()}}-{{key()}}-upper-time" | |
| [required]="required()" [disabled]="disabled()" [readonly]="readonly()" | |
| [(ngModel)]="upperTime" (change)="onBetweenUpperChange()"> | |
| </mat-form-field> | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.html`
around lines 19 - 52, The between-mode date/time inputs do not respect the
component's readonly state; add the readonly binding to each input in the
between branch so they mirror the non-preset branch. Specifically, for the
inputs named "{{label()}}-{{key()}}-lower-date", "-lower-time", "-upper-date",
and "-upper-time" (the inputs using
[(ngModel)]="lowerDate"/"lowerTime"/"upperDate"/"upperTime" and change handlers
onBetweenLowerChange/onBetweenUpperChange), add [readonly]="readonly()"
alongside the existing [required]="required()" and [disabled]="disabled()". This
ensures the component methods normalizedLabel(), label(), key(), required(),
disabled(), and readonly() are used consistently.
| } @else if (filterMode === 'in') { | ||
| <mat-form-field class="value-field" appearance="outline"> | ||
| <mat-label>{{normalizedLabel()}} (comma-separated ISO datetimes)</mat-label> | ||
| <input matInput name="{{label()}}-{{key()}}-in" | ||
| #inputElement | ||
| placeholder="2024-01-01T00:00:00Z, 2024-02-01T00:00:00Z" | ||
| [required]="required()" [disabled]="disabled()" | ||
| [(ngModel)]="inValueText" (ngModelChange)="onInTextChange($event)"> | ||
| </mat-form-field> |
There was a problem hiding this comment.
Same readonly propagation gap on the in branch.
readonly() is not bound on the comma-separated ISO input. Mirror the other branches for consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.html`
around lines 53 - 61, The comma-separated ISO datetimes input in the 'in' branch
is missing the readonly binding; update the <input> used for the 'in' branch
(the one bound to [(ngModel)]="inValueText" and
(ngModelChange)="onInTextChange($event)") to include the same readonly binding
as the other branches (i.e. add [readonly]="readonly()") alongside the existing
[required]="required()" and [disabled]="disabled()" so it respects the
readonly() state like the other inputs (use normalizedLabel(), label(), key()
references as-is).
| onInTextChange(text: string): void { | ||
| this.inValueText = text; | ||
| const parts = text | ||
| .split(',') | ||
| .map((v) => v.trim()) | ||
| .filter((v) => v.length > 0); | ||
|
|
||
| if (parts.length === 0) { | ||
| this.onFieldChange.emit(undefined); | ||
| return; | ||
| } | ||
|
|
||
| this.onFieldChange.emit(parts); | ||
| this.onComparatorChange.emit('in'); | ||
| } |
There was a problem hiding this comment.
onInTextChange does not validate each entry as a datetime.
Unlike the preset/gte/between branches that build ISO strings from parsed date/time fields, the in branch forwards whatever the user typed, comma-split and trimmed. Non-ISO values will be emitted as-is and presumably fail at the backend. Consider validating each token with new Date(v).toString() !== 'Invalid Date' (or an explicit parse) and either dropping invalid entries or surfacing an error state.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@frontend/src/app/components/ui-components/filter-fields/date-time/date-time.component.ts`
around lines 129 - 143, The onInTextChange handler currently emits user-typed
tokens raw; instead validate and normalize each comma-split token to an ISO
datetime before emitting: in onInTextChange use new Date(token) to check
validity (new Date(token).toString() !== 'Invalid Date'), map valid dates to new
Date(token).toISOString(), filter out invalid tokens, and then emit either the
array of ISO strings via onFieldChange or emit undefined if no valid tokens
remain; also keep emitting the comparator with onComparatorChange('in') and
consider setting a component error state/property when invalid tokens are
dropped so the UI can surface feedback (referencing onInTextChange, inValueText,
onFieldChange, onComparatorChange).
| ngOnInit(): void { | ||
| super.ngOnInit(); | ||
| this.hexData = bytesToHex(parseBinaryValue(this.value())); | ||
| this.emitCurrentValue(); | ||
| } |
There was a problem hiding this comment.
Emitting on ngOnInit may mark the form dirty on load.
emitCurrentValue() fires on every init, converting the incoming value (string or BinaryBufferJson) into a freshly constructed BinaryBufferJson and emitting it upward. If the parent tracks "field changed" via onFieldChange, every untouched binary field will be reported as modified the moment the edit view opens, potentially producing spurious dirty state or no-op updates sent back to the server.
Consider only emitting when the user actually interacts (in onHexChange), or guarding the init-time emission behind a check that the value needed normalization.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@frontend/src/app/components/ui-components/record-edit-fields/binary/binary.component.ts`
around lines 24 - 28, The ngOnInit currently calls emitCurrentValue() which
causes untouched binary fields to be reported as changed; remove that init-time
emission or guard it by comparing the normalized value before emitting: in
ngOnInit (method ngOnInit of this component) compute the normalized/parsed value
via parseBinaryValue(this.value()) and only call emitCurrentValue() if the
freshly constructed BinaryBufferJson differs from the incoming value() (or skip
calling emitCurrentValue() here entirely and instead call emitCurrentValue()
from onHexChange when the user edits the hexData); update references to
bytesToHex/hexData as needed so onHexChange performs the emission on real user
interaction.
| //binary | ||
| BLOB: BinaryEditComponent, | ||
| BFILE: BinaryEditComponent, | ||
| RAW: BinaryEditComponent, | ||
| 'LONG RAW': BinaryEditComponent, | ||
| LONG: BinaryEditComponent, | ||
|
|
There was a problem hiding this comment.
Oracle LONG is character data, not binary.
In Oracle, LONG stores up to 2 GB of character data; it's LONG RAW that is the binary variant. Grouping LONG under the //binary block and routing it to BinaryEditComponent will render text content as hex in the editor. Consider keeping LONG on a text-oriented editor (e.g. LongTextEditComponent) and only mapping LONG RAW/RAW/BLOB/BFILE to BinaryEditComponent.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/app/consts/record-edit-types.ts` around lines 219 - 225, The
mapping incorrectly treats Oracle LONG as binary; update the record-edit-types
mapping so that LONG is routed to a text editor (e.g., LongTextEditComponent)
instead of BinaryEditComponent and ensure only RAW, 'LONG RAW', BLOB, and BFILE
remain mapped to BinaryEditComponent; change the comment above the block to
reflect that LONG is character data and verify any imports/usages of
LongTextEditComponent and BinaryEditComponent (symbols: LONG, 'LONG RAW', RAW,
BLOB, BFILE, BinaryEditComponent, LongTextEditComponent).
| //binary | ||
| binary: BinaryEditComponent, | ||
| varbinary: BinaryEditComponent, | ||
| image: BinaryEditComponent, |
There was a problem hiding this comment.
Mapping for MSSQL image is inconsistent with the record-view side.
Here image is routed to BinaryEditComponent, but in frontend/src/app/consts/record-view-types.ts (line 236) image still resolves to ImageRecordViewComponent. Users will see the field rendered as an image in view mode and as a raw hex editor in edit mode. Pick one semantic and apply it consistently across edit/view/filter/table-display registries.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/app/consts/record-edit-types.ts` around lines 257 - 260, The
edit mapping for the MSSQL type "image" is inconsistent: record-edit-types.ts
maps "image" to BinaryEditComponent while record-view-types.ts maps it to
ImageRecordViewComponent; pick a single semantic (render-as-image or
edit-as-raw-binary) and make mappings consistent. To fix, change the "image"
entry in the edit registry (where BinaryEditComponent is referenced) to the
image-specific editor (e.g., ImageEditComponent) if you want view/edit to render
as an image, or change the view registry (where ImageRecordViewComponent is
referenced) to BinaryRecordViewComponent if you want raw hex; then propagate the
chosen mapping to other registries (filter/table-display) so the same "image"
symbol resolves the same across view/edit/filter/table-display.
| //binary | ||
| BLOB: BinaryRecordViewComponent, | ||
| BFILE: BinaryRecordViewComponent, | ||
| RAW: BinaryRecordViewComponent, | ||
| 'LONG RAW': BinaryRecordViewComponent, | ||
| LONG: BinaryRecordViewComponent, |
There was a problem hiding this comment.
Same Oracle LONG concern as in record-edit-types.ts.
LONG is character data in Oracle; only LONG RAW is binary. See the comment on frontend/src/app/consts/record-edit-types.ts lines 219–225 — please keep the mappings aligned once that is resolved.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/app/consts/record-view-types.ts` around lines 195 - 200, The
mapping currently assigns LONG to BinaryRecordViewComponent but in Oracle LONG
is character data; update the record view mappings so LONG maps to
CharacterRecordViewComponent while keeping BLOB, BFILE, RAW, and 'LONG RAW'
mapped to BinaryRecordViewComponent (i.e., change the LONG entry to use
CharacterRecordViewComponent and ensure 'LONG RAW' remains
BinaryRecordViewComponent to match the earlier fix in record-edit-types.ts).
| export function parseBinaryValue(value: unknown): number[] { | ||
| if (value == null || value === '') return []; | ||
| if (value instanceof Uint8Array) return Array.from(value); | ||
| if (typeof value === 'string') return stringToBytes(value); | ||
| if (typeof value === 'object') { | ||
| const data = (value as { data?: unknown }).data; | ||
| if (Array.isArray(data)) return (data as number[]).slice(); | ||
| } | ||
| return []; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for backend serialization of binary fields to see what shape reaches the frontend.
rg -nP -C3 '\b(bytea|binary|BLOB|RAW|LONG\s*RAW|varbinary)\b' --type=ts -g '!**/*.spec.ts' -g '!frontend/**'
rg -nP -C3 '\b(toBinaryBuffer|hexToBinary|binaryToHex|bytesToHex|bufferToJson)\b' --type=tsRepository: rocket-admin/rocketadmin
Length of output: 47378
🏁 Script executed:
#!/bin/bash
# Check where convertHexDataInRowUtil is actually called and how rows are returned
rg -n "convertHexDataInRowUtil" --type=ts -A 5 -B 5
# Check the get-table-rows response construction
rg -n "getTableRows\|buildDAOsTableSettings" backend/src/entities/table/use-cases/get-table-rows.use.case.ts | head -30
# Find record-edit-types to see supported drivers
fd -name "*record*edit*types*" -type fRepository: rocket-admin/rocketadmin
Length of output: 10767
🏁 Script executed:
#!/bin/bash
# Check the get-table-rows.use.case.ts file structure and how rows are returned
wc -l backend/src/entities/table/use-cases/get-table-rows.use.case.ts
sed -n '1,50p' backend/src/entities/table/use-cases/get-table-rows.use.case.ts
# Look for where rows are returned in response
rg -n "return {|rows:" backend/src/entities/table/use-cases/get-table-rows.use.case.ts | head -20Repository: rocket-admin/rocketadmin
Length of output: 4789
🏁 Script executed:
#!/bin/bash
# Look for return statements and processRowsUtil
rg -n "processRowsUtil\|return {" backend/src/entities/table/use-cases/get-table-rows.use.case.ts
# Check the processRowsUtil implementation
cat backend/src/entities/table/utils/process-found-rows-util.tsRepository: rocket-admin/rocketadmin
Length of output: 2427
🏁 Script executed:
#!/bin/bash
# Look at get-table-rows.use.case.ts around the DAO calls and response
sed -n '200,250p' backend/src/entities/table/use-cases/get-table-rows.use.case.ts
# Look for the main execute function
rg -n "async execute" backend/src/entities/table/use-cases/get-table-rows.use.case.ts -A 100 | head -80Repository: rocket-admin/rocketadmin
Length of output: 2462
🏁 Script executed:
#!/bin/bash
# Look for the DAO call that fetches rows
rg -n "dao\.getRows\|dao\.foundRows" backend/src/entities/table/use-cases/get-table-rows.use.case.ts -B 5 -A 5
# Also check if there's any conversion happening on rows before they're returned
sed -n '100,200p' backend/src/entities/table/use-cases/get-table-rows.use.case.tsRepository: rocket-admin/rocketadmin
Length of output: 3776
🏁 Script executed:
#!/bin/bash
# Check the test case to see what format binary data is received as from the backend
sed -n '80,130p' backend/test/ava-tests/non-saas-tests/non-saas-postgres-with-binary-e2e.test.tsRepository: rocket-admin/rocketadmin
Length of output: 1900
🏁 Script executed:
#!/bin/bash
# The test shows binaryToHex() is called on the response, meaning it's getting Buffer-like objects.
# Let's check if anywhere else the backend sends binary as hex strings instead
rg -n "binaryToHex\|hexToBinary" backend/src/entities/table/use-cases/get-table-rows.use.case.ts -B 3 -A 3
# Check all database drivers to see if any might return hex strings
rg -n "driver\|connection\.type" backend/src/entities/table/use-cases/get-table-rows.use.case.ts | head -20
# Look for where binary data might be returned as hex in various DAOs
fd -name "data-access-object*.ts" -type f backend/src shared-code/src | head -10Repository: rocket-admin/rocketadmin
Length of output: 298
🏁 Script executed:
#!/bin/bash
# Check what binaryToHex/hexToBinary are used for in get-table-rows
rg "binaryToHex|hexToBinary" backend/src/entities/table/use-cases/get-table-rows.use.case.ts -B 3 -A 3
# Find data access object implementations
find backend/src shared-code/src -name "data-access-object*.ts" -type fRepository: rocket-admin/rocketadmin
Length of output: 2224
🏁 Script executed:
#!/bin/bash
# Check how Postgres DAO handles binary data
rg "getRowsFromTable" shared-code/src/data-access-layer/data-access-objects/data-access-object-postgres.ts -A 30 | head -50
# Check MongoDB DAO for binary handling
rg "getRowsFromTable" shared-code/src/data-access-layer/data-access-objects/data-access-object-mongodb.ts -A 30 | head -50
# Check if any DAO converts binary to hex before returning
rg "binaryToHex|\.toString\('hex'\)" shared-code/src/data-access-layer/data-access-objects/ -B 2 -A 2Repository: rocket-admin/rocketadmin
Length of output: 4713
🏁 Script executed:
#!/bin/bash
# Check the full context of DynamoDB's binary conversion - where it happens
rg "binaryToHex" shared-code/src/data-access-layer/data-access-objects/data-access-object-dynamodb.ts -B 10 -A 5 | head -80
# Check what transformedRow is - is this the return data?
sed -n '770,800p' shared-code/src/data-access-layer/data-access-objects/data-access-object-dynamodb.tsRepository: rocket-admin/rocketadmin
Length of output: 2673
🏁 Script executed:
#!/bin/bash
# Critical finding: DynamoDB converts binary to hex strings!
# Check if OTHER DAOs do the same conversion
rg "binaryToHex" shared-code/src/data-access-layer/data-access-objects/ -l
# Check what getRowsFromTable returns in DynamoDB
sed -n '600,650p' shared-code/src/data-access-layer/data-access-objects/data-access-object-dynamodb.ts
# Check if there's any similar conversion in other DAOs (MySQL, Postgres, etc)
rg "\.toString\('hex'\)|binaryToHex" shared-code/src/data-access-layer/data-access-objects/data-access-object-mysql.ts shared-code/src/data-access-layer/data-access-objects/data-access-object-postgres.ts shared-code/src/data-access-layer/data-access-objects/data-access-object-oracle.tsRepository: rocket-admin/rocketadmin
Length of output: 1548
🏁 Script executed:
#!/bin/bash
# Check all DAOs for binary conversion on response
for dao in data-access-object-mysql data-access-object-postgres data-access-object-oracle data-access-object-mssql data-access-object-ibmdb2 data-access-object-cassandra data-access-object-clickhouse; do
echo "=== Checking $dao ==="
rg "binaryToHex|hexToBinary|toString\('hex'\)" "shared-code/src/data-access-layer/data-access-objects/$dao.ts" 2>/dev/null | head -5
done
# Check what format Postgres DAO returns (likely just Buffer objects from knex)
sed -n '60,120p' shared-code/src/data-access-layer/data-access-objects/data-access-object-postgres.tsRepository: rocket-admin/rocketadmin
Length of output: 2432
🏁 Script executed:
#!/bin/bash
# Critical finding: DynamoDB converts to hex but others don't!
# Check if there's any post-processing that converts ALL responses to hex before frontend
rg "binaryToHex" backend/src/entities/table/use-cases/get-table-rows.use.case.ts -B 3 -A 3
# Check the response object construction - does it convert rows?
sed -n '160,180p' backend/src/entities/table/use-cases/get-table-rows.use.case.ts
# Look for any middleware or interceptor that processes responses
find backend/src -name "*interceptor*" -o -name "*middleware*" | head -5Repository: rocket-admin/rocketadmin
Length of output: 965
🏁 Script executed:
#!/bin/bash
# Check the DynamoDB getRowsFromTable method - does it apply binaryToHex conversion?
sed -n '300,450p' shared-code/src/data-access-layer/data-access-objects/data-access-object-dynamodb.ts
# Look for where transformedRow is used in getRowsFromTable
rg "getRowsFromTable" shared-code/src/data-access-layer/data-access-objects/data-access-object-dynamodb.ts -A 100 | grep -A 100 "public async getRowsFromTable"Repository: rocket-admin/rocketadmin
Length of output: 9123
🏁 Script executed:
#!/bin/bash
# DynamoDB clearly converts to hex! Check if this is an issue
# Let me check the transformAndFilterRow implementation more carefully
sed -n '750,820p' shared-code/src/data-access-layer/data-access-objects/data-access-object-dynamodb.ts
# Check record-edit-types to see which drivers are supported
find . -name "record-edit-types*" -type fRepository: rocket-admin/rocketadmin
Length of output: 2169
🏁 Script executed:
#!/bin/bash
# Check the supported database drivers
cat frontend/src/app/consts/record-edit-types.tsRepository: rocket-admin/rocketadmin
Length of output: 11653
DynamoDB returns binary values as hex strings, not {type: 'Buffer', data: number[]} format — this will break the frontend parser.
The backend has an inconsistency: parseBinaryValue() on the frontend expects binary values to be either Uint8Array or {type: 'Buffer', data: number[]}. However, the DynamoDB DAO explicitly converts binary fields to hex strings via binaryToHex() in its transformAndFilterRow() method. When the frontend receives a hex string and processes it through stringToBytes(), it treats each character as a byte (e.g., "61" becomes char codes [0x36, 0x31] instead of [0x61]), resulting in corrupted display.
Other supported drivers (Postgres, MySQL, Oracle, MSSQL) return native Buffer objects which serialize to {type: 'Buffer', data: [...]} and work correctly. Ensure DynamoDB (and any other drivers) return the same format, or update the frontend parser to handle hex-string inputs from all drivers consistently.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/app/lib/binary.ts` around lines 3 - 12, parseBinaryValue
currently treats any string as raw characters which corrupts DynamoDB
hex-encoded binaries (produced by binaryToHex in transformAndFilterRow); update
parseBinaryValue in frontend/src/app/lib/binary.ts to detect hex-encoded strings
(e.g., a /^([0-9a-fA-F]{2})+$/ pattern) and, when matched, decode hex pairs into
bytes (byte per two hex chars) instead of calling stringToBytes; keep existing
branches for Uint8Array and {data: number[]} unchanged and fall back to
stringToBytes only for non-hex strings so all drivers (DynamoDB hex strings or
Buffer-style objects) are handled consistently.
Summary by CodeRabbit
Release Notes
New Features
Refactor