Skip to content

feat(events): change notifications on GraphScopedDataset with cross-graph dedup - #96

Draft
jeswr wants to merge 4 commits into
rdfjs:mainfrom
jeswr:feat/graph-scoped-events
Draft

feat(events): change notifications on GraphScopedDataset with cross-graph dedup#96
jeswr wants to merge 4 commits into
rdfjs:mainfrom
jeswr:feat/graph-scoped-events

Conversation

@jeswr

@jeswr jeswr commented Jul 6, 2026

Copy link
Copy Markdown
Member

Adds change notifications to GraphScopedDataset: listeners attached with on(listener) / off(listener) observe the projected view — filtered to the read scope, delivered in the default graph, and deduplicated across read graphs.

Note

Stacked PR. This branch builds on #94 (DatasetWrapper.on/off) and #95 (GraphScopedDataset) and includes their commits; only the last commit (feat(events): change notifications on GraphScopedDataset with cross-graph dedup) is new here. It becomes a two-file diff once those two merge.

What it does

GraphScopedDataset inherits on/off from #94, but the inherited semantics are wrong for a projected view: listeners would see raw source quads from every graph, with their original graph components. This PR overrides on/off so that:

  • Read-scope filtering — changes to graphs outside the configured readGraphs are not reported (union views report every graph).
  • Cross-graph dedup — because the view collapses all read graphs onto the default graph, a triple occurring in several read graphs is reported as added only once (when its first copy appears) and as deleted only when its last copy disappears from the read scope. Adding a copy of a triple another read graph already contains, or deleting a copy while another read graph still holds one, emits nothing.
  • Projection — listeners receive the quad projected onto the default graph, regardless of which read graph triggered the change, consistent with all reads through the view.
  • Lazy source subscription — the view attaches a single shared callback to the underlying dataset when the first listener subscribes (via DatasetWrapper.on, so it reuses feat(events): change notifications on DatasetWrapper (on/off) #94's eventful core and listener bookkeeping) and detaches it when the last listener leaves.
  • Writes performed through the view itself (add/delete, or wrapper-driven mutations) are observed like any other change, provided the write graph is within the read scope.

No new exports and no new dependencies beyond what #94/#95 introduce: the listener contract is #94's existing IDatasetChangeListener.

Relation to #73

Supersedes the projected-view event slice of #73 (Omnibus): the ProjectedDatasetCoreWrapper event callback (isReadGraph filter + existsInOtherReadGraph suppression + default-graph re-emission) and its lazy attach/detach-on-last-listener bookkeeping, now expressed as a GraphScopedDataset override on top of #94's notification surface instead of the bespoke in-repo NotifyingDatasetCore/EventEmitter. The event-dedup tests from #73's projected_dataset.test.ts are ported here. Not included (left to their own slices): the Triple/default-graph type restrictions, datasetFactory plumbing, lazy match materialization, and the async work.

Tests

test/unit/graph_scoped_events.test.ts:

  • explicit read graphs: add/delete events, out-of-scope suppression, add-reported-once and delete-only-on-last-copy dedup (ported from Omnibus #73), write-through-the-view notifications, and listener bookkeeping (off, double-subscribe, unknown-listener off, multiple listeners);
  • union views (readGraphs === undefined): events from any graph, dedup against copies in any other graph;
  • source subscription bookkeeping: a single shared source callback, attached lazily on the first on and detached on the last off (asserted via listenerCount on an EventfulDatasetCore source).

Every delivered quad is asserted to be in the default graph.

npm i && npx tsc && npm test && npx typedoc && npm audit --omit=dev --audit-level=moderate all pass locally (192 tests, 0 fail; typedoc warnings unchanged from main).

Review timing: This draft was prepared with Claude; I (@jeswr) will personally review it before it progresses. I'm currently batching a lot of work in flight, so expect active review Wednesday-Friday (8-10 July).

jeswr and others added 4 commits July 6, 2026 03:54
DatasetWrapper now layers @jeswr/eventful-dataset's EventfulDatasetCore
over the wrapped dataset (constructor signature unchanged; write-through
storage preserved) and exposes on(listener)/off(listener) so consumers
can observe effective additions and deletions - whether performed
directly or through mapped properties, Set mappings, or named graph
views sharing the same eventful core.

Adds the IDatasetChangeListener/ChangeEvent types to the public surface
and ports the dataset_events test slices from rdfjs#73.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generalizes NamedGraphDataset (which stays untouched) into a view that
reads a configurable set of graphs — or every graph when the read scope
is undefined — projected onto the default graph with per-triple
deduplication, while directing all writes to a single configured graph.

- src/GraphScopedDataset.ts: reads union the read graphs and project to
  the default graph; add/delete/has enforce default-graph input via
  ensureDefaultGraph; match mirrors NamedGraphDataset's self-wrapping
  pattern and rejects non-default graph arguments with TermTypeError.
- DatasetWrapper.scoped(writeGraph, readGraphs, klass) beside named().
- IGraphScopedDatasetConstructor in src/type/, both exported from mod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Combines the DatasetWrapper change-notification surface (on/off) with the
GraphScopedDataset view so the scoped view can take part in eventing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…raph dedup

GraphScopedDataset overrides on/off so listeners observe the projected
view rather than the raw underlying dataset:

- changes are filtered to the read scope and delivered with the quad
  projected onto the default graph;
- a triple occurring in several read graphs is reported as added only
  once and as deleted only when its last copy disappears from the read
  scope;
- the view subscribes to the underlying dataset lazily: the first
  listener attaches a single shared callback (via DatasetWrapper.on) and
  the last off() detaches it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jeswr jeswr mentioned this pull request Jul 6, 2026
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