Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow import to be treated as Promise<Store> per the RDF/JS spec #503

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

jeswr
Copy link
Collaborator

@jeswr jeswr commented Mar 7, 2025

Uses a Proxy so that the return of import has the type EventEmitter & Promise<Store & EventEmitter> making it compatible both with the:

  • Existing return type of import (EventEmitter)
  • RDF/JS return type of import (Promise<Store>)

cc @RubenVerborgh

Summary by CodeRabbit

  • New Features
    • Introduced a new dependency for enhanced event handling.
    • Improved the asynchronous data import process with robust event handling and promise resolution.
  • Tests
    • Expanded test coverage to validate seamless stream processing, proper event handling, and reliable error management.

Copy link
Contributor

coderabbitai bot commented Mar 7, 2025

Walkthrough

The changes introduce a new dependency for event handling in the project’s package configuration and refactor the import method in the store. The updated implementation integrates promises with event-driven processing by creating a proxy that combines the store with EventEmitter functionality. In addition, new test cases have been added to verify asynchronous processing, listener management, promise resolution, and error handling during the stream import.

Changes

File(s) Summary of Changes
package.json Added dependency "events": "^3.3.0" for enhanced event handling.
src/N3Store.js Refactored the import(stream) method: now creates a promise, attaches event listeners for data, end, and error, and returns a proxy merging store methods with EventEmitter functionality.
test/N3Store-test.js Introduced new test cases for the updated import functionality, including tests for promise resolution, listener management, event emission, and error handling.

Suggested labels

released

Suggested reviewers

  • RubenVerborgh

Poem

I'm a hopping rabbit, quick on my feet,
Scurrying through code with a rhythmic beat.
Streams now dance with events so bright,
Their promise fulfilled in pure delight.
Cheers to the changes, crisp and sunny—let’s code hopping and funny!
🐇✨

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between feb5b32 and 9445788.

📒 Files selected for processing (1)
  • src/N3Store.js (2 hunks)
🔇 Additional comments (7)
src/N3Store.js (7)

2-2: Good addition of the EventEmitter import

This import is necessary to support the new implementation that combines EventEmitter and Promise functionality in the import method.


412-417: Well-documented return type

The JSDoc comment clearly explains the new dual-purpose return type that satisfies both the existing EventEmitter interface and the RDF/JS spec requirement for Promise.


418-420: Maintaining backward compatibility

Good approach to keep the existing event-based functionality by attaching the 'data' event listener immediately.


423-429: Elegant use of Proxy for combining N3Store with EventEmitter

The proxy implementation elegantly combines the Store's methods with the EventEmitter's methods, maintaining a clean interface for consumers.


432-435: Proper handling of already completed streams

Good defensive programming by checking if the stream is already closed or ended before setting up event listeners, avoiding potential race conditions.


437-453: Proper event listener cleanup

The implementation correctly removes event listeners when the promise is resolved or rejected, preventing potential memory leaks.


457-466: Clean promise integration

The outer proxy elegantly handles method delegation between the Promise interface and the stream interface without mutating the original stream object.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Comment on lines +449 to +450
stream.once('end', onEnd);
stream.once('error', onError);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wanted to only create this promise when then, catch, or finally were used in the below proxy - and thus only start listening to the stream at that point in time.

Unfortunately, that turned out to not be an option; as EventEmitters error if the error event is called and there are no error listeners - which causes the last added test case to break.

@jeswr jeswr requested a review from RubenVerborgh March 7, 2025 19:08
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: 0

🧹 Nitpick comments (4)
src/N3Store.js (3)

2-2: Check bundling approach for Node's built-in 'events' vs. external usage.

The import statement uses 'events' from npm. Make sure that depending on an external package (rather than the built-in module) is intentional and required for your use case (e.g., browser builds).


414-416: Potential backpressure concerns with large streams.

Calling this.addQuad(quad) on every 'data' event can be memory-intensive for large or fast streams. Consider implementing or documenting a strategy to handle backpressure (e.g., pausing the stream or using a pipeline) to avoid excessive memory usage.


417-452: Robust promise-based import logic.

The promise captures the stream’s completion or error, but be mindful of any corner cases:

  • If the stream is “half-closed” or partially readable, ensure data events are always consumed.
  • Provide clear guidance to users on how to abort/cancel the import if needed, since the code removes listeners on completion and rejects on error.

Overall, the proxy approach linking EventEmitter methods with the store is neatly done.

test/N3Store-test.js (1)

2400-2523: Excellent test coverage for async import logic.

Tests validate order of event listeners, promise resolution, error handling, and concurrency checks. Consider adding a test for partial reads or user interrupt/cancel scenarios to further strengthen reliability under edge cases.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 023fdb9 and e145c3b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • package.json (1 hunks)
  • src/N3Store.js (2 hunks)
  • test/N3Store-test.js (1 hunks)
🔇 Additional comments (2)
package.json (1)

28-28: Consider verifying necessity of external 'events' dependency.

NodeJS (≥12) provides a built-in events module, so confirm that the separately installed package is strictly required (e.g., for browser bundling). Otherwise, removing an extra dependency could simplify maintenance and reduce the bundle size.

src/N3Store.js (1)

454-464: Clever proxy for returning both a promise and a stream.

The proxy merges promise- and stream-like behaviors:

  • This is convenient but can be confusing if users are not aware of the shape of the returned object.
  • Ensure the documentation clarifies that the returned value can be awaited or used as an EventEmitter without conflict.

Otherwise, the approach looks solid.

@jeswr jeswr changed the title fix: allow import to be treated as Promise<Store> per the RDF/JS spec fix: allow import to be treated as Promise<Store> per the RDF/JS spec Mar 16, 2025
@@ -25,6 +25,7 @@
],
"dependencies": {
"buffer": "^6.0.3",
"events": "^3.3.0",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is already a transitive dependency via readable-stream

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: 0

🧹 Nitpick comments (2)
src/N3Store.js (2)

422-423: Consider being explicit about the prototype chain delegation.

While the current implementation works, being more explicit about which object receives the property access could improve clarity and maintainability.

- return Reflect.get(prop in EventEmitter.prototype ? stream : target, prop, receiver);
+ return prop in EventEmitter.prototype 
+   ? Reflect.get(stream, prop, receiver)
+   : Reflect.get(target, prop, receiver);

414-462: Consider adding JSDoc documentation for the enhanced method.

The implementation significantly changes the behavior of the import method, but there's no documentation explaining its dual nature as both an EventEmitter and a Promise. Adding JSDoc comments would help users understand how to use the method correctly.

/**
 * `import` adds a stream of quads to the store
+ * 
+ * @returns {EventEmitter & Promise<Store & EventEmitter>} A proxy object that acts as both an EventEmitter 
+ * (for backward compatibility) and a Promise that resolves to the store when the stream is complete.
 */
import(stream) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e145c3b and 8626c61.

📒 Files selected for processing (1)
  • src/N3Store.js (2 hunks)
🔇 Additional comments (4)
src/N3Store.js (4)

2-2: Good addition of the EventEmitter import.

This import is necessary to support the new dual-nature return type of the import method, which now needs to combine event-driven functionality with promises.


414-415: LGTM: Maintained existing event handling behavior.

The basic functionality of adding quads to the store as they arrive from the stream is preserved, ensuring backward compatibility.


417-449: Well-implemented Promise integration with EventEmitter.

The code effectively creates a promise that resolves when the stream ends or rejects on error, while also checking for already closed streams. The design of the storeProxy elegantly combines N3Store methods with EventEmitter capabilities, allowing for a seamless dual interface.

I see from your previous comment that this implementation addresses the issue where EventEmitters error when the error event is emitted without listeners.


451-461: Excellent Proxy implementation for dual nature return type.

This proxy elegantly allows the return value to be used as both a Promise and an EventEmitter without modifying the original stream. It forwards Promise methods to the promise object and all other properties to the stream, which meets the PR objective of allowing import to be treated as Promise<Store> per the RDF/JS spec while maintaining compatibility with existing code.

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

🧹 Nitpick comments (2)
src/N3Store.js (2)

427-427: Simplification opportunity for the proxy's get handler

The current implementation works, but there's a slightly clearer way to express the property access logic.

- return Reflect.get(prop in EventEmitter.prototype ? stream : target, prop, receiver);
+ return Reflect.get(prop in EventEmitter.prototype ? stream : target, prop, receiver);

This approach works well because it prioritizes EventEmitter methods from the stream while falling back to the store (target) for other properties.


432-435: Consider normalizing stream end state detection

The current check includes multiple properties to determine if a stream has ended. While thorough, it might be slightly over-engineered.

Consider using a helper function to improve readability:

- if (stream.readableEnded || stream.destroyed || stream.readable === false || stream.closed || stream.ended) {
+ if (isStreamEnded(stream)) {
   // Resolve immediately if stream is already closed
   resolve(storeProxy);
 }

+ // Helper to determine if a stream has ended
+ function isStreamEnded(stream) {
+   return stream.readableEnded || stream.destroyed || 
+          stream.readable === false || stream.closed || stream.ended;
+ }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8626c61 and feb5b32.

📒 Files selected for processing (1)
  • src/N3Store.js (2 hunks)
🧰 Additional context used
🪛 GitHub Check: lint (22.x)
src/N3Store.js

[failure] 415-415:
Trailing spaces not allowed


[failure] 414-414:
Trailing spaces not allowed

🪛 ESLint
src/N3Store.js

[error] 414-414: Trailing spaces not allowed.

(no-trailing-spaces)


[error] 415-415: Trailing spaces not allowed.

(no-trailing-spaces)

🪛 GitHub Actions: CI
src/N3Store.js

[error] 414-414: ESLint: Trailing spaces not allowed. (no-trailing-spaces)

🔇 Additional comments (5)
src/N3Store.js (5)

2-2: Good addition of EventEmitter import

The inclusion of the EventEmitter import is necessary to support the new functionality where the import method now returns an object that fulfills both Promise and EventEmitter interfaces.


419-421: LGTM!

This section correctly sets up the event handler to add quads to the store as they arrive in the stream.


422-454: Excellent implementation of the Promise functionality

The code creates a promise that appropriately resolves to a proxy combining the store and stream capabilities. It correctly handles early resolution for already-ended streams and properly cleans up event listeners to prevent memory leaks.

The implementation addresses the constraint mentioned in your previous comment about needing to set up error listeners immediately (rather than deferring until Promise methods are called).


456-466: Well-designed proxy implementation

This proxy elegantly allows the return value to be used as both an EventEmitter (for backwards compatibility) and a Promise (for the RDF/JS spec compatibility). The implementation selectively forwards Promise methods to the promise object while delegating all other functionality to the stream.


458-466:

✅ Verification successful

Verify promise resolution is fully compatible with RDF/JS spec

The implementation treats then, catch, and finally specially for Promise compatibility. This seems correct, but it would be beneficial to verify against the RDF/JS specification that no other Promise methods are required.


🌐 Web query:

What methods must be implemented for a Promise according to the RDF/JS spec for import to return Promise<Store>?

💡 Result:

To implement a Promise that complies with the RDF/JS specification for the .import() method returning Promise<Store>, the following elements must be addressed:

Promise Handling Requirements

  1. Standard Promise Methods
    The Promise must implement:

    • .then() for fulfillment/rejection handling
    • .catch() for error handling (equivalent to .then(null, onRejected))
    • .finally() for post-settlement cleanup

    These methods are part of the ECMAScript specification and required for all Promises[2][6].

  2. Stream Event Wrapping
    The .import() method must wrap stream events (end and error) into a Promise:

    dataset.import(stream).then(() => /* success */).catch((error) => /* handle error */)

    This ensures asynchronous resolution when the stream finishes or rejects on errors[9][7].

RDF/JS-Specific Implementation

  • Dataset Interface Compliance:
    The import method is defined in the Dataset interface as:

    Promise<Dataset> import(Stream stream);

    This requires the returned Promise to resolve to the modified dataset instance after importing all quads[9].

  • Quad Stream Processing:
    The implementation must handle quad streams according to RDF/JS Sink/Source interfaces, where parsers/serializers implement .import() to emit quads or serialized data[1][9].

Error Handling Constraints

  • Rejection reasons must be Error instances per W3C guidance[4].
  • Synchronous errors during stream processing must be converted into rejected Promises rather than thrown exceptions[4].

This structure ensures compatibility with RDF/JS datasets while adhering to JavaScript Promise standards.

Citations:


Promise Resolution Implementation Verified as Compliant with RDF/JS Spec

  • Location: src/N3Store.js, lines 458-466.
  • Verification Result: The RDF/JS specification for the import() method requires that the returned Promise implement the standard ECMAScript methods: .then(), .catch(), and .finally(). The web query confirmed that these are the only methods needed for Promise compatibility.
  • Conclusion: The current implementation—forwarding these methods to the underlying promise—is sufficient and meets the RDF/JS requirements. No additional Promise methods are necessary.

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.

1 participant