Skip to content

Fix serialization of zero-parameter events in raw_events - #1356

Merged
DZakh merged 9 commits into
mainfrom
claude/bold-mayer-lpnpel
Jun 29, 2026
Merged

Fix serialization of zero-parameter events in raw_events#1356
DZakh merged 9 commits into
mainfrom
claude/bold-mayer-lpnpel

Conversation

@DZakh

@DZakh DZakh commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a regression where zero-parameter events were not being serialized correctly for the raw_events table. The native decoder returns an empty object {} for events with no parameters, but the raw_events table expects null for the params field.

Changes

  • EventConfigBuilder.res: Updated buildParamsSchema to use a transform serializer that converts any decoded value (including {}) to null for zero-parameter events, instead of relying on a magic cast that didn't properly handle serialization.

  • MockIndexer.res: Updated test helper configurations to use EventConfigBuilder.buildParamsSchema([]) and EventConfigBuilder.buildSimulateParamsSchema([]) instead of manually constructing schemas with magic casts, ensuring consistency with the fixed implementation.

  • HyperSyncDecoder_test.res: Added regression test that verifies toRawEvent correctly serializes empty params decoded by the native decoder without crashing the batch write.

Implementation Details

The fix uses S.transform with a serializer function to ensure that regardless of what value the decoder produces for zero-parameter events, it gets serialized as null in the raw_events table. This maintains the contract that raw_events stores null for events with no parameters while allowing the decoder to emit {}.

https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m

Summary by CodeRabbit

  • Bug Fixes

    • Improved event serialization so events with no decoded parameters are now represented consistently in raw event payloads.
    • Preserved existing behavior for events that do include parameters.
  • Tests

    • Added a regression test covering zero-parameter event decoding and raw event output formatting.

claude added 6 commits June 29, 2026 08:05
Add a failing test that captures the issue: the decoder produces an
empty object `{}` for zero-parameter events, but buildParamsSchema([])
builds a schema expecting unit/null, so RawEvent.make's reverse
conversion throws "Expected undefined, received {}".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
Replace the manufactured `%raw({})` repro with an end-to-end test that
decodes a real `event Empty()` log through the native HyperSync decoder,
which produces the empty object `{}`, then serializes it via
paramsRawEventSchema (the raw_events write path). buildParamsSchema([])
builds a schema expecting unit/null, so the reverse conversion throws
"Expected undefined, received {}" on the real decoder output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
Instead of hand-rolling the raw-event serialization with
buildParamsSchema + reverseConvertToJsonOrThrow, decode a real
`event Empty()` log via the native decoder (which yields `{}`) and feed
the result through the real ecosystem.toRawEvent path. The crash now
surfaces from RawEvent.make exactly as it does in production.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
With raw_events enabled, a zero-parameter event crashed the batch write:
the native decoder hands toRawEvent an empty object `{}`, but the
paramsRawEventSchema is built to expect no params, so reverse conversion
threw "Expected undefined, received {}".

Short-circuit empty params in RawEvent.make to the existing "null"
sentinel before attempting conversion, and turn the prior repro into a
passing regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
Treat unit/undefined and empty objects uniformly as empty params via a
single isEmptyParams check, dropping the redundant post-conversion
null-sentinel branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
Move empty-params handling into buildParamsSchema, the only place that
knows (from the param metadata) that an event has no params: the empty
branch now serializes any decoded value to null, so the empty object `{}`
the decoder emits no longer crashes reverse conversion. RawEvent.make's
existing null->"null" sentinel handles the non-null params column
unchanged.

MockIndexer's evm event configs now build their params schemas via the
real EventConfigBuilder instead of a hardcoded copy, so the regression
test exercises the actual schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd3afd96-5019-4206-836d-7890e061ebab

📥 Commits

Reviewing files that changed from the base of the PR and between 4697df2 and c5b919c.

📒 Files selected for processing (1)
  • packages/envio/src/sources/Evm.res

📝 Walkthrough

Walkthrough

toRawEvent in Evm.res now checks eventConfig.paramsMetadata length and passes unit (serialized as "null") for zero-parameter events instead of payload.params. MockIndexer.res replaces hardcoded S.literal(null)/S.unknown schemas with EventConfigBuilder.buildParamsSchema([])/buildSimulateParamsSchema([]). A regression test verifies the params: "null" output for an Empty event.

Changes

Zero-param event params serialization fix

Layer / File(s) Summary
toRawEvent conditional null serialization
packages/envio/src/sources/Evm.res
Derives eventConfig from eventItem.eventConfig and conditionally passes unit for zero-parameter events or payload.params otherwise when building RawEvent.
MockIndexer empty-params schemas
scenarios/test_codegen/test/helpers/MockIndexer.res
Replaces hardcoded S.literal(null)/S.unknown schemas with EventConfigBuilder.buildParamsSchema([]) and buildSimulateParamsSchema([]) in event construction and evmEventConfig.
Regression test
scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res
Adds a Vitest test decoding an Empty event and asserting toRawEvent serializes params as %raw("null").

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

  • enviodev/hyperindex#1335: Introduced the ecosystem-pluggable toRawEvent in Evm.res that this PR extends with zero-params handling.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing raw_events serialization for zero-parameter events.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

Keep buildParamsSchema using shape and move the zero-parameter handling
into the raw-event make logic: when the decoded params are empty, store
the "null" sentinel directly instead of reverse-converting (which the
schema rejects for the empty object the decoder emits).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
Only EVM events hit the crash (SVM rejects raw events; Fuel params use
S.json, which serializes the empty object fine). Move the check out of
the shared RawEvent.make into Evm.toRawEvent, where the config is an
evmEventConfig: when paramsMetadata is empty, pass unit so the existing
schema + null sentinel produce "null". RawEvent.make stays generic and
Fuel's empty-struct params are no longer rewritten to "null".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNLV8wTTY4D4mJmSTvkf4m
@DZakh
DZakh merged commit 7a6d708 into main Jun 29, 2026
8 checks passed
@DZakh
DZakh deleted the claude/bold-mayer-lpnpel branch June 29, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants