Skip to content

Conversation

@zxch3n
Copy link
Member

@zxch3n zxch3n commented Dec 8, 2025

Summary

This PR adds a subscribe_jsonpath method that allows subscribing to changes that may affect a JSONPath query result, without re-evaluating the query on every change. This enables efficient reactive patterns for applications that need to respond to specific data changes.

Motivation

When building reactive UIs or data pipelines on top of Loro documents, you often want to watch specific paths (e.g., $.users[*].name or $.config.theme). Without this feature, you'd need to:

  1. Subscribe to all document changes
  2. Re-evaluate the JSONPath on every change
  3. Diff the results to detect actual changes

This is expensive for large documents. The new API provides a lightweight notification system that filters events at the path-matching level.

API

Rust:

let subscription = doc.subscribe_jsonpath(
    "$.books[[email protected]>10].title",
    Arc::new(|| {
        println!("Books query may have changed!");
    }),
)?;
// Drop `subscription` to unsubscribe

JavaScript/TypeScript:

const unsubscribe = doc.subscribeJsonpath("$.books[[email protected]>10].title", () => {
    // Debounce/throttle, then re-run JSONPath if needed
    const titles = doc.JSONPath("$.books[[email protected]>10].title");
});

Design

The implementation uses an NFA-based (non-deterministic finite automaton) matcher optimized for minimal code size:

  1. Lightweight compilation: JSONPath is parsed and converted to a sequence of simple MatchSelectors (Name/Index/Wildcard) - no AST storage needed
  2. Event filtering: When a change occurs, we simulate the NFA to check if the change path could affect the query
  3. Conservative matching: May produce false positives (extra notifications) but never false negatives (missed changes)

Key design choices:

  • Simplified selectors: Complex selectors (filters, slices, wildcards, negative indices) are all treated as Wildcard during matching, keeping the matcher simple
  • Wildcard-aware triggering: When the path passes through a wildcard step (from [*], [?...], etc.), any key change in that container triggers the callback - this ensures filter changes are never missed
  • SmallVec optimization: Uses stack-allocated vectors for paths and NFA positions to minimize heap allocations

Behavior

Scenario Behavior
Direct path match ($.a.b.c) ✅ Triggers only on exact path
Wildcard/filter match ($[*], [?...]) ✅ Triggers on any child change
Recursive descent (..) ✅ Triggers at any matching depth
Unrelated path change ❌ Does not trigger

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions
Copy link
Contributor

github-actions bot commented Dec 8, 2025

WASM Size Report

  • Original size: 3197.99 KB
  • Gzipped size: 1013.52 KB
  • Brotli size: 702.99 KB

@zxch3n zxch3n changed the title Add JSONPath subscription with per-commit dedupe feat: add JSONPath subscription Dec 8, 2025
@zxch3n zxch3n merged commit fffaf45 into main Dec 8, 2025
1 check passed
@zxch3n zxch3n deleted the zxch3n/jsonpath-subscribe branch December 8, 2025 13:49
@github-actions github-actions bot mentioned this pull request Dec 8, 2025
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