Skip to content

Add read-only subtype views - #45

Open
danReynolds wants to merge 7 commits into
mainfrom
claude/extension-type-views
Open

danReynolds wants to merge 7 commits into
mainfrom
claude/extension-type-views

Conversation

@danReynolds

@danReynolds danReynolds commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Adds read-only subtype views: a collection of Animal can be read as its Dogs, by ID or through queries, with snapshots typed as Dog.

final CollectionView<Dog> dogs = animals.view<Dog>();

final Dog? rex = dogs.doc('rex').get()?.data; // null if rex is missing or not a dog

dogs
    .where((snap) => snap.data.age >= 2)
    .sortBy((a, b) => a.data.name.compareTo(b.data.name))
    .stream();

How it works

collection.view<S>() and query.view<S>() add a type test to the underlying query, and a view's where() adds its filter to that query too. The query therefore selects the view's documents and handles hydration, dependency invalidation and incremental updates as it does for any query. A document that enters or leaves the view, by changing type or by starting or stopping to pass a filter, appears in the view's change stream as added or removed. The view wraps each result in a DocumentSnapshotView<S> whose data is typed as S, and applies sortBy to the wrapped snapshots.

CollectionView.doc(id) returns a DocumentView<S>, which reads its document as absent while it is missing or not an S; its stream emits a single null for that time. Views are created only through view() and doc(), so every view's query includes its type test.

The views live in lib/src/views.dart, a part of the core library. Besides view() on Collection and Query, the only engine change is that a query without filters returns its results without copying them.

Notes

  • Views are read-only. Write through the collection, as in animals.doc(snap.id).update(...).
  • Each read or emission wraps every result once, and view snapshots compare by identity.
  • A where filter wraps each snapshot it checks. Against filtering after the query, get() measured the same, and the time from a write to its stream emission was 20–30% shorter, because only the changed document is rechecked.
  • Views don't plug into QueryStreamBuilder or DocumentStreamBuilder. To build from a view, observe it and use a StreamBuilder.
  • Earlier commits in this PR used extension types. Those were replaced because their lists rejected view-typed values (reduce and orElse threw) and views had no runtime type of their own.

Validation

  • test/core/collection_view_test.dart has 20 tests, passing on the VM and in Chrome. They cover reads by type and ID, typed filters and sorting, membership changes in values and change streams (including filter transitions), hydration through the parent codec, dependency writes, ancestor deletion, multicast observers, widening a view's static type, and list operations that take view-typed values.
  • Locally, 182 core, 90 native and 42 web tests pass, both CI benchmark workload checks pass, and flutter analyze reports no issues for the package and the example.
  • Removing the query-side type test, the query-side filters, the sort or the document stream's null deduplication each fails the suite.

🤖 Generated with Claude Code

danReynolds and others added 3 commits September 30, 2026 14:55
`collection.view<S>()` and `document.view<S>()` read the documents whose data
is an S. A view is the collection's ordinary query or document, filtered by
type and given a narrower static type with extension types. Its snapshots,
lists and streams are the store's own, so views need no projection objects,
caches or invalidation, and no changes to existing classes: the query engine
already reports a document that changes type as removed or added.

QueryViewStreamBuilder and DocumentViewStreamBuilder build from views by
delegating to the existing builders. Extension types require Dart 3.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`collection.view<S>()` returns a CollectionView again: a query view that
also reads documents by ID, as in `view<Dog>().doc('rex').get()`. It
replaces `document.view<S>()`, so views have one entry point.

A view's queries carry its type test, so a document read through a view is
narrowed by the view's own type even when the view's static type is widened.
`where` and `sortBy` share one helper that keeps the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Views no longer have their own stream builders, so the view layer depends
only on Loon's core library and the existing builders are the only ones.
Also tightens the views' documentation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 15:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The advertised stream-builder APIs are missing, and the documentation overstates the runtime allocation benefits.

Review effort: Balanced
Findings: 1 High severity · 2 Low severity

Open (3)
What changed in this PR

Adds read-only subtype views over collections using Dart extension types.

Changes:

  • Introduces subtype-filtered collection, query, document, and snapshot views.
  • Raises the minimum Dart SDK to 3.3 and documents the API.
  • Adds extensive subtype-view tests and supporting test models.
File Description
lib/​src/​views.dart Implements subtype view APIs.
lib/​loon.dart Exports the view API.
pubspec.yaml Requires Dart 3.3.
README.md Documents subtype views.
CHANGELOG.md Records the feature and SDK requirement.
test/​core/​collection_view_test.dart Tests view reads, streams, hydration, and caching.
test/​models/​test_animal_model.dart Adds a polymorphic test model.
test/​models/​test_persistor.dart Generalizes persistence seed snapshots.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/loon.dart Outdated
export 'src/utils/id.dart' show generateFastId, generateSecureId;
export 'src/widgets/document_stream_builder.dart';
export 'src/widgets/query_stream_builder.dart';
export 'src/views.dart';
Comment thread CHANGELOG.md Outdated
@@ -1,3 +1,8 @@
## Unreleased

* Add `collection.view<S>()`: a live, read-only view of the documents whose data is an `S`, read by ID with `doc(id)` or through queries. Views are extension types over the collection's queries and documents, so they add no runtime objects.
Comment thread README.md Outdated
Comment on lines +228 to +230
A view is the collection's own query, given a narrower type at compile time, so it
costs nothing extra at runtime: its snapshots are the store's own, and its lists and
streams are its query's.
danReynolds and others added 3 commits October 1, 2026 13:23
`view<S>()` on collections and queries puts the type test in the query, and
a view's `where()` adds its filter there too. The query then selects the
view's documents, so its change stream reports documents that enter or
leave the view as added or removed, and each write rechecks only the
documents it changed.

- `sortBy` sorts the wrapped snapshots.
- `where()` callbacks are typed by the view.
- A document view's stream emits a single null while its document is
  missing or another type.
- The view types are exported, and views are created only through
  `view()` and `doc()`.
- Queries without filters return their results without copying them.
- The README, changelog and SDK constraint describe class-based views,
  which need no newer Dart than main.
- `.rk/` is ignored, and its lock file is no longer tracked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danReynolds danReynolds changed the title Add read-only subtype views as extension types Add read-only subtype views Oct 1, 2026
A view knows its type at runtime, so it adds the type test to the query it
is given. `Collection.view` and `Query.view` pass a plain query, and views
derived through `where`, `sortBy` and `observe` reuse their already
selected query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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