Skip to content

feat: implement StreamCreated handler#43

Open
d3vobed wants to merge 1 commit into
Fundable-Protocol:devfrom
d3vobed:feat/stream-created-handler
Open

feat: implement StreamCreated handler#43
d3vobed wants to merge 1 commit into
Fundable-Protocol:devfrom
d3vobed:feat/stream-created-handler

Conversation

@d3vobed

@d3vobed d3vobed commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Implements the StreamCreated event handler for the payment streams domain as specified in #34.

Changes

  • Parse: parseStreamCreatedPayload validates the event topic matches StreamCreated and extracts the structured payload (streamId, sender, recipient, amount, startTime, endTime)
  • Map: mapStreamCreatedToRecord combines the parsed payload with event identity metadata into a StreamRecord
  • Identity: getEventIdentity produces a deterministic string from contractId:ledger:txHash:eventIndex for idempotency checks
  • Handler: handleStreamCreated composes the above into a single call, returning both the stream record and identity

Testing

  • Payload parsing with valid and invalid topics
  • Identity derivation and uniqueness across all identity fields
  • Record mapping correctness
  • Handler idempotency (same input → same output)
  • Multiple stream ID handling

Verification

  • bun run indexer:type-check
  • bun run indexer:test ✅ (10 tests)
  • bun run indexer:lint

Closes #34

Summary by CodeRabbit

  • New Features

    • Added support for processing stream-created events and converting them into stream records.
    • Exposed stream event utilities and related types for easier integration.
  • Bug Fixes

    • Improved event validation to reject mismatched or missing topics with clear errors.
    • Ensured event identity generation is consistent and changes when key event details change.
  • Tests

    • Added coverage for payload parsing, identity generation, record mapping, and handler behavior.

- Add StreamCreated event handler for parsing Soroban events
- Map event payload to Stream record with event identity tracking
- Add deterministic event identity for idempotency checks
- Add mocked payload tests covering:
  - Payload parsing and validation
  - Event identity derivation
  - Record mapping
  - Handler idempotency
  - Multiple stream ID handling
@drips-wave

drips-wave Bot commented Jun 26, 2026

Copy link
Copy Markdown

@d3vobed Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds StreamCreated event types, payload validation, identity generation, record mapping, handler composition, package exports, and Vitest coverage for parsing and handler behavior.

Changes

StreamCreated handler

Layer / File(s) Summary
Event contract and parsing
indexer/streams/src/handlers/types.ts, indexer/streams/src/handlers/streamCreated.ts, indexer/streams/src/handlers/streamCreated.test.ts
StreamCreatedEvent and StreamRecord are defined, STREAM_CREATED_TOPIC is added, and parseStreamCreatedPayload() validates the first topic and parses the JSON payload; tests cover valid parsing and topic mismatch failures.
Identity and record mapping
indexer/streams/src/handlers/streamCreated.ts, indexer/streams/src/handlers/streamCreated.test.ts
getEventIdentity() builds a deterministic identity from contract, ledger, transaction hash, and event index, and mapStreamCreatedToRecord() converts payload data and event metadata into a stream record; tests cover identity stability and record field mapping.
Handler composition and exports
indexer/streams/src/handlers/streamCreated.ts, indexer/streams/src/index.ts, indexer/streams/src/handlers/streamCreated.test.ts
handleStreamCreated() combines parsing, mapping, and identity generation, and index.ts re-exports the handler API and types; tests cover the composed return value, repeated-input behavior, and differing stream IDs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I hopped through payloads, thump thump thump,
Found stream IDs in a tidy lump.
JSON carrots, crisp and bright,
Identities now line up right.
Ears up! The handler sings tonight 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: implementing the StreamCreated handler.
Description check ✅ Passed The description covers the summary, changes, testing, and verification, so it is mostly complete despite missing some template sections.
Linked Issues check ✅ Passed The changes satisfy #34 by parsing StreamCreated payloads, mapping records, deriving identity, adding idempotent handling, and including tests.
Out of Scope Changes check ✅ Passed The PR stays within the streams indexer handler scope and does not introduce unrelated package, CI, or deployment changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@indexer/streams/src/handlers/streamCreated.ts`:
- Around line 22-31: The StreamCreated handler currently trusts a type cast
after JSON.parse, so invalid shapes can still produce a malformed StreamRecord.
Update the logic in streamCreated.ts, around the parsed payload handling in the
StreamCreatedPayload flow, to perform a real runtime validation of the parsed
object before building the return value. Use the existing streamCreated event
handler path to reject or guard against missing or wrong-typed streamId, sender,
recipient, amount, startTime, and endTime fields, and only return the record
once validation passes.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc30f585-3708-4e5f-b38f-f39951ab7f30

📥 Commits

Reviewing files that changed from the base of the PR and between bcfa08d and 6e5b3c8.

📒 Files selected for processing (4)
  • indexer/streams/src/handlers/streamCreated.test.ts
  • indexer/streams/src/handlers/streamCreated.ts
  • indexer/streams/src/handlers/types.ts
  • indexer/streams/src/index.ts

Comment on lines +22 to +31
const parsed = JSON.parse(event.data) as StreamCreatedPayload;

return {
streamId: parsed.streamId,
sender: parsed.sender,
recipient: parsed.recipient,
amount: parsed.amount,
startTime: parsed.startTime,
endTime: parsed.endTime,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the parsed payload at runtime before returning it.

Line 22 only proves that event.data is valid JSON. The as StreamCreatedPayload cast does not verify shape, so {} or a payload with non-string fields will flow through Lines 24-30 and produce a malformed StreamRecord. That breaks the handler’s insert contract and undermines the “payload validation” objective for this event.

Proposed fix
+function assertStreamCreatedPayload(value: unknown): asserts value is StreamCreatedPayload {
+  if (
+    typeof value !== "object" ||
+    value === null ||
+    typeof (value as Record<string, unknown>).streamId !== "string" ||
+    typeof (value as Record<string, unknown>).sender !== "string" ||
+    typeof (value as Record<string, unknown>).recipient !== "string" ||
+    typeof (value as Record<string, unknown>).amount !== "string" ||
+    typeof (value as Record<string, unknown>).startTime !== "string" ||
+    typeof (value as Record<string, unknown>).endTime !== "string"
+  ) {
+    throw new Error("Invalid StreamCreated payload");
+  }
+}
+
 export function parseStreamCreatedPayload(event: StreamCreatedEvent): StreamCreatedPayload {
   const eventName = event.topics[0];
   if (!eventName || eventName !== STREAM_CREATED_TOPIC) {
     throw new Error(
       `Expected ${STREAM_CREATED_TOPIC} event topic, got ${eventName ?? "undefined"}`,
     );
   }
 
-  const parsed = JSON.parse(event.data) as StreamCreatedPayload;
+  const parsed: unknown = JSON.parse(event.data);
+  assertStreamCreatedPayload(parsed);
 
   return {
     streamId: parsed.streamId,
     sender: parsed.sender,
     recipient: parsed.recipient,
📝 Committable suggestion

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

Suggested change
const parsed = JSON.parse(event.data) as StreamCreatedPayload;
return {
streamId: parsed.streamId,
sender: parsed.sender,
recipient: parsed.recipient,
amount: parsed.amount,
startTime: parsed.startTime,
endTime: parsed.endTime,
};
function assertStreamCreatedPayload(value: unknown): asserts value is StreamCreatedPayload {
if (
typeof value !== "object" ||
value === null ||
typeof (value as Record<string, unknown>).streamId !== "string" ||
typeof (value as Record<string, unknown>).sender !== "string" ||
typeof (value as Record<string, unknown>).recipient !== "string" ||
typeof (value as Record<string, unknown>).amount !== "string" ||
typeof (value as Record<string, unknown>).startTime !== "string" ||
typeof (value as Record<string, unknown>).endTime !== "string"
) {
throw new Error("Invalid StreamCreated payload");
}
}
export function parseStreamCreatedPayload(event: StreamCreatedEvent): StreamCreatedPayload {
const eventName = event.topics[0];
if (!eventName || eventName !== STREAM_CREATED_TOPIC) {
throw new Error(
`Expected ${STREAM_CREATED_TOPIC} event topic, got ${eventName ?? "undefined"}`,
);
}
const parsed: unknown = JSON.parse(event.data);
assertStreamCreatedPayload(parsed);
return {
streamId: parsed.streamId,
sender: parsed.sender,
recipient: parsed.recipient,
amount: parsed.amount,
startTime: parsed.startTime,
endTime: parsed.endTime,
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@indexer/streams/src/handlers/streamCreated.ts` around lines 22 - 31, The
StreamCreated handler currently trusts a type cast after JSON.parse, so invalid
shapes can still produce a malformed StreamRecord. Update the logic in
streamCreated.ts, around the parsed payload handling in the StreamCreatedPayload
flow, to perform a real runtime validation of the parsed object before building
the return value. Use the existing streamCreated event handler path to reject or
guard against missing or wrong-typed streamId, sender, recipient, amount,
startTime, and endTime fields, and only return the record once validation
passes.

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.

Implement StreamCreated handler

1 participant