Skip to content

Conversation

@satoren
Copy link
Owner

@satoren satoren commented Jan 2, 2026

…t, XmlFragment, and XmlText modules

Summary by CodeRabbit

  • New Features

    • Added callback registration for document updates and subdocument monitoring (new on_update / on_update_v1 / on_update_v2 and on_subdocs flows).
    • Added observe() and observe_deep() across shared types (Text, Map, Array, XmlElement, XmlFragment, XmlText) to receive change callbacks; observers now route notifications via the document worker for cross-process delivery.
    • Extended public event types to include a lightweight weak-event for broader change detection.
  • Tests

    • Added comprehensive tests for observer callbacks, update propagation, and subdocument behaviors across shared types.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

Warning

Rate limit exceeded

@satoren has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 6 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 4eaa6c5 and 2c723b0.

📒 Files selected for processing (17)
  • lib/doc.ex
  • lib/shared_type/array.ex
  • lib/shared_type/event.ex
  • lib/shared_type/map.ex
  • lib/shared_type/shared_type.ex
  • lib/shared_type/text.ex
  • lib/shared_type/xml_element.ex
  • lib/shared_type/xml_fragment.ex
  • lib/shared_type/xml_text.ex
  • lib/y_ex.ex
  • test/doc_test.exs
  • test/shared_type/array_test.exs
  • test/shared_type/map_test.exs
  • test/shared_type/text_test.exs
  • test/shared_type/xml_element_test.exs
  • test/shared_type/xml_fragment_test.exs
  • test/shared_type/xml_text_test.exs

Walkthrough

Extracts worker-routing logic from the run_in_worker_process macro into run_in_worker_process_fn/2, adds doc-level subscription APIs (on_subdocs/2, on_update*), adds per-shared-type observe/2 and observe_deep/2 APIs wired to the doc worker, introduces Yex.WeakEvent and extends Yex.event_type, and adds corresponding tests.

Changes

Cohort / File(s) Summary
Doc module
lib/doc.ex
Adds run_in_worker_process_fn/2 (macro now delegates), multi-branch worker execution and cross-process error propagation, public subscription APIs on_subdocs/2, on_update/2, on_update_v1/2, on_update_v2/2, and internal handle_on_update_handler/0. Review cross-process GenServer.call and reraise paths.
Shared types — observe APIs
lib/shared_type/{array.ex,map.ex,text.ex,xml_element.ex,xml_fragment.ex,xml_text.ex}
Adds observe/2 and observe_deep/2 to each shared-type module; they call Yex.SharedType.observe[_deep] with metadata {Yex.ObserveCallbackHandler, handler} and pass notify_pid: <type>.doc.worker_pid. Verify metadata shape and notify_pid use.
SharedType core
lib/shared_type/shared_type.ex
observe/observe_deep now accept :notify_pid via opts (default self()), allowing caller-specified notification targets. Check option propagation and defaults.
Event types & public types
lib/shared_type/event.ex, lib/y_ex.ex
Adds Yex.WeakEvent struct with :path and expands @type event_type to include Yex.WeakEvent.t(). Ensure type aliases and docs updated.
Tests
test/doc_test.exs, test/shared_type/...
Adds tests for on_update*, on_subdocs, and observe/observe_deep across arrays, maps, text, xml_element, xml_fragment, and xml_text. Confirm coverage of handler invocation, origin propagation, subscription cancellation, and subdoc events.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding observe and observe_deep functions to multiple shared type modules (Array, Map, Text, XmlElement, etc.).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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 and usage tips.

@coveralls
Copy link

coveralls commented Jan 2, 2026

Coverage Status

coverage: 98.06% (-0.5%) from 98.517%
when pulling 2c723b0 on callback
into e8b7e4d on main.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
lib/doc.ex (1)

78-87: Consider calling handle_on_update_handler() in the rescue block.

The rescue block returns the error tuple without processing any pending callback messages. If callbacks were queued before the error occurred, they will be lost. Consider adding handle_on_update_handler() before returning the error tuple to ensure callbacks are processed even when errors occur.

🔎 Proposed fix
         wrapped_fun = fn ->
           try do
             result = fun.()
             handle_on_update_handler()
             result
           rescue
             e ->
+              handle_on_update_handler()
               {Yex.Doc, :reraise, e, __STACKTRACE__}
           end
         end
lib/shared_type/map.ex (2)

328-337: Add documentation for the public API.

The observe/2 function is missing a @doc annotation. Public APIs should include documentation explaining their purpose, parameters, and return values.

🔎 Suggested documentation
+ @doc """
+ Observes changes to the map by registering a callback handler.
+ The handler will be invoked whenever the map is modified within a transaction.
+
+ ## Parameters
+   * `map` - The map to observe
+   * `handler` - A function that receives updates (Yex.MapEvent.t()) and origin
+
+ ## Returns
+   A reference that can be used to unobserve later
+
+ ## Examples
+     iex> doc = Yex.Doc.new()
+     iex> map = Yex.Doc.get_map(doc, "map")
+     iex> ref = Yex.Map.observe(map, fn event, origin ->
+     ...>   IO.inspect({event, origin})
+     ...> end)
+ """
  @spec observe(
          t,
          handler :: (update :: Yex.MapEvent.t(), origin :: term() -> nil)
        ) :: reference()
  def observe(%__MODULE__{doc: doc} = map, handler) do
    Yex.SharedType.observe(map,
      metadata: {Yex.ObserveCallbackHandler, handler},
      notify_pid: doc.worker_pid
    )
  end

339-348: Add documentation for the public API.

The observe_deep/2 function is missing a @doc annotation. Public APIs should include documentation explaining their purpose, parameters, and how they differ from regular observe/2.

🔎 Suggested documentation
+ @doc """
+ Observes deep changes to the map and nested structures by registering a callback handler.
+ Unlike observe/2, this also triggers for changes in nested shared types.
+
+ ## Parameters
+   * `map` - The map to observe
+   * `handler` - A function that receives a list of updates and origin
+
+ ## Returns
+   A reference that can be used to unobserve later
+
+ ## Examples
+     iex> doc = Yex.Doc.new()
+     iex> map = Yex.Doc.get_map(doc, "map")
+     iex> ref = Yex.Map.observe_deep(map, fn updates, origin ->
+     ...>   IO.inspect({updates, origin})
+     ...> end)
+ """
  @spec observe_deep(
          t,
          handler :: (update :: list(Yex.event_type()), origin :: term() -> nil)
        ) :: reference()
  def observe_deep(%__MODULE__{doc: doc} = map, handler) do
    Yex.SharedType.observe_deep(map,
      metadata: {Yex.ObserveCallbackHandler, handler},
      notify_pid: doc.worker_pid
    )
  end
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8b7e4d and 3a3249f.

📒 Files selected for processing (17)
  • lib/doc.ex
  • lib/shared_type/array.ex
  • lib/shared_type/event.ex
  • lib/shared_type/map.ex
  • lib/shared_type/shared_type.ex
  • lib/shared_type/text.ex
  • lib/shared_type/xml_element.ex
  • lib/shared_type/xml_fragment.ex
  • lib/shared_type/xml_text.ex
  • lib/y_ex.ex
  • test/doc_test.exs
  • test/shared_type/array_test.exs
  • test/shared_type/map_test.exs
  • test/shared_type/text_test.exs
  • test/shared_type/xml_element_test.exs
  • test/shared_type/xml_fragment_test.exs
  • test/shared_type/xml_text_test.exs
🧰 Additional context used
🧬 Code graph analysis (7)
lib/shared_type/text.ex (13)
lib/shared_type/array.ex (2)
  • observe (395-400)
  • observe_deep (406-411)
lib/shared_type/map.ex (2)
  • observe (332-337)
  • observe_deep (343-348)
lib/shared_type/xml_element.ex (2)
  • observe (358-363)
  • observe_deep (369-374)
lib/shared_type/xml_fragment.ex (2)
  • observe (287-292)
  • observe_deep (298-303)
lib/shared_type/xml_text.ex (2)
  • observe (146-151)
  • observe_deep (157-162)
native/yex/src/event.rs (2)
  • observe (449-486)
  • observe_deep (397-438)
native/yex/src/sync.rs (4)
  • term (34-34)
  • term (83-83)
  • term (105-105)
  • term (112-112)
native/yex/src/map.rs (1)
  • reference (36-38)
native/yex/src/array.rs (1)
  • reference (40-42)
native/yex/src/text.rs (1)
  • reference (45-47)
native/yex/src/weak.rs (1)
  • reference (37-39)
native/yex/src/shared_type.rs (1)
  • reference (56-56)
native/yex/src/xml.rs (3)
  • reference (50-52)
  • reference (87-89)
  • reference (116-118)
test/doc_test.exs (1)
lib/doc.ex (5)
  • on_update (318-320)
  • on_update_v1 (324-337)
  • transaction (212-233)
  • on_update_v2 (341-354)
  • on_subdocs (302-315)
test/shared_type/xml_text_test.exs (2)
lib/shared_type/xml_text.ex (2)
  • observe (146-151)
  • observe_deep (157-162)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
test/shared_type/text_test.exs (4)
lib/shared_type/array.ex (2)
  • observe (395-400)
  • observe_deep (406-411)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/shared_type/xml_text.ex (2)
  • observe (146-151)
  • observe_deep (157-162)
lib/shared_type/xml_text.ex (6)
lib/shared_type/array.ex (2)
  • observe (395-400)
  • observe_deep (406-411)
lib/shared_type/map.ex (2)
  • observe (332-337)
  • observe_deep (343-348)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/shared_type/xml_element.ex (2)
  • observe (358-363)
  • observe_deep (369-374)
lib/shared_type/xml_fragment.ex (2)
  • observe (287-292)
  • observe_deep (298-303)
native/yex/src/event.rs (2)
  • observe (449-486)
  • observe_deep (397-438)
lib/shared_type/array.ex (7)
lib/shared_type/map.ex (2)
  • observe (332-337)
  • observe_deep (343-348)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/shared_type/xml_element.ex (2)
  • observe (358-363)
  • observe_deep (369-374)
lib/shared_type/xml_fragment.ex (2)
  • observe (287-292)
  • observe_deep (298-303)
lib/shared_type/xml_text.ex (2)
  • observe (146-151)
  • observe_deep (157-162)
native/yex/src/event.rs (2)
  • observe (449-486)
  • observe_deep (397-438)
lib/doc.ex (2)
native/yex/src/doc.rs (3)
  • doc_monitor_subdocs (455-487)
  • doc_monitor_update_v1 (297-328)
  • doc_monitor_update_v2 (330-360)
lib/subscription.ex (1)
  • register (49-53)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint
🔇 Additional comments (22)
lib/doc.ex (2)

317-354: LGTM!

The callback-based update APIs (on_update, on_update_v1, on_update_v2) are well-structured and consistent with the existing monitor_update pattern. The delegation to NIF functions and subscription registration is correct.


356-372: LGTM!

The recursive message handling in handle_on_update_handler/0 correctly drains callback messages from the mailbox. The non-blocking receive (timeout 0) ensures it terminates when no more messages are available, and the pattern matching covers the different callback handler types appropriately.

lib/shared_type/shared_type.ex (2)

40-57: LGTM!

The addition of :notify_pid option with a default to self() is well-designed. It maintains backward compatibility while enabling callers to route notifications to a different process (e.g., the document's worker process).


88-105: LGTM!

The :notify_pid option is consistently implemented in observe_deep/2, matching the pattern in observe/2.

lib/y_ex.ex (1)

18-23: LGTM!

The event_type union type provides a clean type definition for all supported event structures. This enhances type safety and documentation for the observe/observe_deep APIs.

lib/shared_type/xml_element.ex (1)

354-374: LGTM!

The observe/2 and observe_deep/2 implementations correctly delegate to Yex.SharedType with appropriate metadata wrapping and worker process notification routing. The pattern is consistent with other shared type modules in the codebase.

lib/shared_type/xml_text.ex (1)

142-162: LGTM!

The observe/2 and observe_deep/2 functions are correctly implemented, following the same pattern used across all other shared type modules (Map, Text, Array, XmlElement, XmlFragment). The implementation properly routes notifications through the document's worker process.

lib/shared_type/event.ex (1)

112-128: LGTM!

The Yex.WeakEvent module follows the established pattern of other event types in this file. The struct and type definitions are correct and consistent.

test/shared_type/xml_element_test.exs (2)

789-804: LGTM!

The test correctly verifies that the observe handler is invoked with the expected event type and origin. The implementation follows the established testing pattern.


806-821: LGTM!

The test correctly verifies deep observation callback behavior. The implementation is consistent with the observe test and follows established patterns.

test/shared_type/text_test.exs (2)

761-779: LGTM!

The test correctly verifies the observe callback functionality for Text. Implementation is consistent with the pattern used across other shared types.


781-799: LGTM!

The test correctly validates deep observation behavior for Text, following the established testing pattern.

test/shared_type/xml_text_test.exs (2)

355-375: LGTM!

The test correctly validates the observe callback functionality for XmlText. The implementation is consistent with the testing patterns used across other shared types.


377-397: LGTM!

The test correctly verifies deep observation callback behavior for XmlText, following the established testing pattern.

test/shared_type/map_test.exs (1)

248-280: LGTM! Well-structured tests for the new observe APIs.

The tests properly verify handler invocation, event types, and origin propagation through the transaction mechanism.

test/shared_type/xml_fragment_test.exs (1)

483-515: LGTM! Consistent test coverage for XmlFragment observation.

The tests follow the established pattern and properly validate callback invocation with XmlEvent payloads.

lib/shared_type/text.ex (1)

205-225: LGTM! Clean implementation following established patterns.

The observe/observe_deep implementations correctly delegate to Yex.SharedType with proper metadata wrapping and worker PID routing, consistent with the patterns used in Map, Array, and XML shared types.

test/shared_type/array_test.exs (1)

520-558: LGTM! Comprehensive test coverage for Array observation with handlers.

The tests properly validate both Array.observe and Array.observe_deep with callback handlers, complementing the existing SharedType-based observation tests.

test/doc_test.exs (1)

549-660: LGTM! Excellent comprehensive test coverage for the new callback-based APIs.

The test suite thoroughly validates:

  • Basic callback invocation and update data structure
  • Origin propagation through transactions
  • Both v1 and v2 update variants
  • Multiple concurrent callbacks
  • Practical integration usage (lines 627-645 demonstrate cross-document sync)
  • Subdocs callback registration

The integration test at lines 627-645 is particularly valuable as it demonstrates real-world usage of the update callback to synchronize documents.

lib/shared_type/array.ex (1)

391-400: LGTM! Consistent implementation with other shared types.

The observe/2 function correctly follows the established pattern used in Map, Text, XmlElement, XmlText, and XmlFragment modules by delegating to Yex.SharedType.observe with appropriate metadata and notify_pid.

lib/shared_type/xml_fragment.ex (2)

283-292: LGTM! Consistent implementation with other shared types.

The observe/2 function correctly follows the established pattern by delegating to Yex.SharedType.observe with appropriate metadata and notify_pid from the document's worker process.


294-303: LGTM! Consistent implementation with other shared types.

The observe_deep/2 function correctly follows the established pattern by delegating to Yex.SharedType.observe_deep with appropriate metadata and notify_pid. The typespec correctly matches the function name.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
lib/shared_type/array.ex (1)

402-411: Fix typespec name mismatch.

The @spec on line 402 declares observe but the function defined on line 406 is observe_deep. This causes the typespec to be incorrectly associated with the observe/2 function above.

🔎 Proposed fix
-  @spec observe(
+  @spec observe_deep(
           Yex.Array.t(),
           handler :: (update :: list(Yex.event_type()), origin :: term() -> nil)
         ) :: reference()
lib/doc.ex (1)

302-315: Fix typo in module name.

Line 305 references Yex.Dox.CallbackHandler which should be Yex.ObserveCallbackHandler to match the pattern in handle_on_update_handler/0 at line 366.

🔎 Proposed fix
   def on_subdocs(%__MODULE__{} = doc, handler) do
     result =
       run_in_worker_process(doc,
-        do: Yex.Nif.doc_monitor_subdocs(doc, self(), {Yex.Dox.CallbackHandler, handler})
+        do: Yex.Nif.doc_monitor_subdocs(doc, self(), {Yex.ObserveCallbackHandler, handler})
       )
🧹 Nitpick comments (2)
test/doc_test.exs (1)

647-660: Consider adding a test that actually triggers subdocs events.

The current test only verifies callback registration via refute_receive. While useful for ensuring registration doesn't crash, it doesn't validate the callback is actually invoked when subdocs events occur. Consider adding a follow-up test that creates/loads subdocuments to trigger the event.

lib/doc.ex (1)

63-66: Clarify the typespec for the doc parameter.

The atom() type in the spec seems unnecessary since the function accesses doc.worker_pid, which requires a map/struct. Consider using just the map type or the Yex.Doc.t() type.

🔎 Proposed fix
-  @spec run_in_worker_process_fn(
-          atom() | %{:worker_pid => any(), optional(any()) => any()},
-          any()
-        ) :: any()
+  @spec run_in_worker_process_fn(
+          %{:worker_pid => pid() | nil, optional(any()) => any()},
+          (-> any())
+        ) :: any()
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a3249f and efad8e5.

📒 Files selected for processing (17)
  • lib/doc.ex
  • lib/shared_type/array.ex
  • lib/shared_type/event.ex
  • lib/shared_type/map.ex
  • lib/shared_type/shared_type.ex
  • lib/shared_type/text.ex
  • lib/shared_type/xml_element.ex
  • lib/shared_type/xml_fragment.ex
  • lib/shared_type/xml_text.ex
  • lib/y_ex.ex
  • test/doc_test.exs
  • test/shared_type/array_test.exs
  • test/shared_type/map_test.exs
  • test/shared_type/text_test.exs
  • test/shared_type/xml_element_test.exs
  • test/shared_type/xml_fragment_test.exs
  • test/shared_type/xml_text_test.exs
🚧 Files skipped from review as they are similar to previous changes (9)
  • test/shared_type/xml_fragment_test.exs
  • lib/shared_type/map.ex
  • test/shared_type/map_test.exs
  • lib/shared_type/xml_element.ex
  • test/shared_type/array_test.exs
  • lib/shared_type/event.ex
  • lib/shared_type/shared_type.ex
  • lib/shared_type/xml_fragment.ex
  • lib/shared_type/text.ex
🧰 Additional context used
🧬 Code graph analysis (4)
test/shared_type/text_test.exs (2)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/doc.ex (2)
native/yex/src/doc.rs (3)
  • doc_monitor_subdocs (455-487)
  • doc_monitor_update_v1 (297-328)
  • doc_monitor_update_v2 (330-360)
lib/subscription.ex (1)
  • register (49-53)
test/shared_type/xml_text_test.exs (2)
lib/shared_type/xml_text.ex (2)
  • observe (146-151)
  • observe_deep (157-162)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
test/shared_type/xml_element_test.exs (3)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
lib/shared_type/xml_element.ex (2)
  • observe (358-363)
  • observe_deep (369-374)
native/yex/src/event.rs (2)
  • observe (449-486)
  • observe_deep (397-438)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint
🔇 Additional comments (15)
lib/shared_type/xml_text.ex (1)

142-162: LGTM! Observer APIs follow established patterns.

The observe/2 and observe_deep/2 implementations correctly delegate to Yex.SharedType with appropriate metadata and routing notifications through doc.worker_pid, consistent with similar implementations across other shared types.

lib/y_ex.ex (1)

18-23: LGTM! Event type union correctly defined.

The event_type alias appropriately includes all event types (XmlTextEvent, TextEvent, MapEvent, ArrayEvent, WeakEvent), supporting the new observer APIs across shared types.

test/shared_type/xml_text_test.exs (1)

355-397: LGTM! Tests adequately cover new observer APIs.

Both tests correctly validate that:

  • Handlers are invoked with the expected event types (XmlTextEvent for observe, list of events for observe_deep)
  • Origin values are properly propagated through the callback chain

The test structure aligns with existing observer tests in the module.

test/shared_type/xml_element_test.exs (1)

789-821: LGTM! Observer handler tests are well-structured.

The tests properly verify:

  • Handler registration via XmlElement.observe/observe_deep
  • Event delivery with correct event types (XmlEvent for observe, list for observe_deep)
  • Origin propagation through the transaction

Consistent with the module's existing observer test patterns.

test/shared_type/text_test.exs (1)

761-799: LGTM! Handler-based observer tests are correctly implemented.

Both tests validate the new Text.observe/observe_deep APIs by:

  • Registering handlers that send messages to the test process
  • Performing mutations within a transaction with a test origin
  • Asserting proper callback invocation with TextEvent and origin

The pattern matches existing observer tests in the module.

test/doc_test.exs (5)

548-564: LGTM!

The test properly verifies the on_update callback registration and invocation, including assertions for both the callback being called and the update data being a non-empty binary.


566-583: LGTM!

Good test coverage for on_update_v1 with transaction origin propagation. The test correctly verifies that the origin value is passed through to the callback.


585-599: LGTM!

Basic coverage for on_update_v2 callback registration and invocation.


601-625: LGTM!

Excellent test for multiple callback registration. It verifies that both callbacks are invoked independently and receive consistent update sizes, which is important for ensuring the callback mechanism doesn't corrupt data between handlers.


627-645: LGTM!

This is a valuable integration test that verifies the update data can be used to synchronize another document, demonstrating the practical utility of the callback mechanism.

lib/shared_type/array.ex (1)

391-400: LGTM!

The observe/2 implementation correctly delegates to Yex.SharedType.observe with the handler wrapped in metadata and uses the document's worker_pid for notifications.

lib/doc.ex (4)

55-61: LGTM!

Good refactoring to extract the worker process routing logic into a dedicated function. The macro now cleanly delegates to run_in_worker_process_fn/2.


67-104: LGTM!

The worker process routing logic is well-structured with proper error handling and cross-process exception propagation via reraise. The three cases (self, nil, other pid) are handled correctly.


317-337: LGTM!

The on_update and on_update_v1 functions are implemented correctly, registering the handler with the appropriate metadata wrapper and returning a subscription reference.


339-354: LGTM!

The on_update_v2 implementation follows the same pattern as on_update_v1, correctly using the v2 variant of the NIF function.

@satoren satoren force-pushed the callback branch 2 times, most recently from 4eaa6c5 to fb7c308 Compare January 2, 2026 12:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
lib/doc.ex (1)

317-321: Consider using a more precise return type spec.

The on_update/2, on_update_v1/2, and on_update_v2/2 functions return any() in their specs, but based on the implementation they return {:ok, reference()} | {:error, term()}. Consider aligning with the more specific return type used by monitor_update/2 for consistency and better documentation.

🔎 Proposed fix
-  @spec on_update(Yex.Doc.t(), handler :: (update :: binary(), origin :: term() -> nil)) :: any()
+  @spec on_update(Yex.Doc.t(), handler :: (update :: binary(), origin :: term() -> nil)) ::
+          {:ok, reference()} | {:error, term()}
   def on_update(%__MODULE__{} = doc, handler) do
     on_update_v1(doc, handler)
   end
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efad8e5 and 4eaa6c5.

📒 Files selected for processing (17)
  • lib/doc.ex
  • lib/shared_type/array.ex
  • lib/shared_type/event.ex
  • lib/shared_type/map.ex
  • lib/shared_type/shared_type.ex
  • lib/shared_type/text.ex
  • lib/shared_type/xml_element.ex
  • lib/shared_type/xml_fragment.ex
  • lib/shared_type/xml_text.ex
  • lib/y_ex.ex
  • test/doc_test.exs
  • test/shared_type/array_test.exs
  • test/shared_type/map_test.exs
  • test/shared_type/text_test.exs
  • test/shared_type/xml_element_test.exs
  • test/shared_type/xml_fragment_test.exs
  • test/shared_type/xml_text_test.exs
🚧 Files skipped from review as they are similar to previous changes (8)
  • lib/shared_type/event.ex
  • test/shared_type/xml_text_test.exs
  • lib/shared_type/array.ex
  • lib/shared_type/shared_type.ex
  • lib/shared_type/text.ex
  • test/shared_type/map_test.exs
  • lib/shared_type/xml_element.ex
  • test/shared_type/xml_element_test.exs
🧰 Additional context used
🧬 Code graph analysis (7)
test/shared_type/array_test.exs (2)
lib/shared_type/array.ex (2)
  • observe (395-400)
  • observe_deep (406-411)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
test/doc_test.exs (3)
lib/doc.ex (5)
  • new (123-125)
  • on_update (318-320)
  • on_update_v1 (324-337)
  • on_update_v2 (341-354)
  • on_subdocs (302-315)
lib/y_ex.ex (1)
  • apply_update (103-105)
lib/subscription.ex (1)
  • unsubscribe (66-78)
lib/shared_type/map.ex (6)
lib/shared_type/array.ex (2)
  • observe (395-400)
  • observe_deep (406-411)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/shared_type/xml_element.ex (2)
  • observe (358-363)
  • observe_deep (369-374)
lib/shared_type/xml_fragment.ex (2)
  • observe (287-292)
  • observe_deep (298-303)
lib/shared_type/xml_text.ex (2)
  • observe (146-151)
  • observe_deep (157-162)
lib/shared_type/xml_fragment.ex (11)
lib/shared_type/array.ex (2)
  • observe (395-400)
  • observe_deep (406-411)
lib/shared_type/map.ex (2)
  • observe (332-337)
  • observe_deep (343-348)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/shared_type/xml_element.ex (2)
  • observe (358-363)
  • observe_deep (369-374)
lib/shared_type/xml_text.ex (2)
  • observe (146-151)
  • observe_deep (157-162)
native/yex/src/event.rs (2)
  • observe (449-486)
  • observe_deep (397-438)
native/yex/src/array.rs (1)
  • reference (40-42)
native/yex/src/map.rs (1)
  • reference (36-38)
native/yex/src/text.rs (1)
  • reference (45-47)
native/yex/src/shared_type.rs (1)
  • reference (56-56)
native/yex/src/xml.rs (3)
  • reference (50-52)
  • reference (87-89)
  • reference (116-118)
test/shared_type/text_test.exs (2)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/shared_type/xml_text.ex (6)
lib/shared_type/array.ex (2)
  • observe (395-400)
  • observe_deep (406-411)
lib/shared_type/map.ex (2)
  • observe (332-337)
  • observe_deep (343-348)
lib/shared_type/shared_type.ex (2)
  • observe (40-57)
  • observe_deep (88-105)
lib/shared_type/text.ex (2)
  • observe (209-214)
  • observe_deep (220-225)
lib/shared_type/xml_element.ex (2)
  • observe (358-363)
  • observe_deep (369-374)
lib/shared_type/xml_fragment.ex (2)
  • observe (287-292)
  • observe_deep (298-303)
test/shared_type/xml_fragment_test.exs (1)
lib/shared_type/xml_fragment.ex (2)
  • observe (287-292)
  • observe_deep (298-303)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint
🔇 Additional comments (17)
test/shared_type/array_test.exs (2)

520-538: LGTM!

The test correctly verifies that Array.observe/2 invokes the handler with a Yex.ArrayEvent and the transaction origin.


540-558: LGTM!

The test correctly verifies that Array.observe_deep/2 invokes the handler with a list of updates and the transaction origin.

test/shared_type/xml_fragment_test.exs (2)

483-498: LGTM!

The test correctly verifies that XmlFragment.observe/2 invokes the handler with a Yex.XmlEvent and the transaction origin.


500-515: LGTM!

The test correctly verifies that XmlFragment.observe_deep/2 invokes the handler with a list of updates and the transaction origin.

lib/shared_type/map.ex (2)

328-337: LGTM!

The observe/2 implementation is consistent with other shared types and correctly wires the handler through the document's worker process.


339-348: LGTM!

The observe_deep/2 implementation is consistent with other shared types and correctly wires the deep handler through the document's worker process.

test/shared_type/text_test.exs (2)

761-779: LGTM!

The test correctly verifies that Text.observe/2 invokes the handler with a Yex.TextEvent and the transaction origin.


781-799: LGTM!

The test correctly verifies that Text.observe_deep/2 invokes the handler with a list of updates and the transaction origin.

test/doc_test.exs (4)

4-4: LGTM!

The alias addition for Map is appropriate for the new subdocs tests that use Map.set/3.


549-564: LGTM!

The test correctly validates the on_update callback registration, invocation, and data types.


566-645: LGTM!

The on_update_v1, on_update_v2, multiple callbacks, and sync tests are well-structured with correct callback signatures and comprehensive assertions.


662-692: LGTM!

The subdocs event test correctly validates the callback receives added, loaded, and removed fields, and the error path test properly mocks the NIF with [:passthrough].

lib/shared_type/xml_fragment.ex (1)

283-303: LGTM!

The observe/2 and observe_deep/2 implementations are consistent with the pattern used across other shared types (Map, Text, Array, XmlElement, XmlText). The specs correctly reference Yex.XmlEvent.t() for shallow observation and list(Yex.event_type()) for deep observation.

lib/shared_type/xml_text.ex (1)

142-162: LGTM!

The observe/2 and observe_deep/2 implementations follow the same consistent pattern as other shared types, correctly using Yex.XmlTextEvent.t() for the observe callback and delegating to Yex.SharedType.

lib/doc.ex (3)

55-104: LGTM!

The refactored run_in_worker_process macro and new run_in_worker_process_fn/2 cleanly encapsulate worker-routing logic with proper error propagation via reraise. The three cases (self, nil, other pid) are handled correctly with stacktrace preservation.


302-315: The code is correct as written. Yex.CallbackHandler is not a module but an atom constant used for message identification in the handle_on_update_handler/0 pattern matching (lines 358, 362, 366). This is intentional design—atoms tag different callback signatures. The distinction between Yex.CallbackHandler (doc-level subscriptions) and Yex.ObserveCallbackHandler (shared-type observers) is consistent throughout the codebase.


356-376: Verify NIF message formats match the receive patterns.

The handle_on_update_handler/0 function handles multiple message formats. The patterns should match what the NIF sends:

  • Line 358: 3-tuple {_, arg1, {Yex.CallbackHandler, handler}}
  • Line 362: 4-tuple {:subdocs, event, origin, {Yex.CallbackHandler, handler}}
  • Line 366: 4-tuple {_, arg1, arg2, {Yex.CallbackHandler, handler}}
  • Line 370: 5-tuple {_, _ref, event, origin, {Yex.ObserveCallbackHandler, handler}}

These patterns need confirmation against the actual NIF message construction in native/yex/src/doc.rs to ensure they match the tuples being sent.

Add callback function-based event handler registration functions to shared type modules:
- Array, Map, Text, XmlElement, XmlFragment, and XmlText
- observe: Subscribe to events with a callback function
- observe_deep: Subscribe to deep events with a callback function

This provides an alternative to the existing Subscription-based approach,
allowing simpler event handling patterns for user applications.
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.

3 participants