Skip to content

Add Collection.observeRecord for records that may be missing - #1982

Open
joker-777 wants to merge 1 commit into
Nozbe:masterfrom
kenhub:fix/observe-record
Open

joker-777 wants to merge 1 commit into
Nozbe:masterfrom
kenhub:fix/observe-record

Conversation

@joker-777

Copy link
Copy Markdown

Summary

Adds Collection.observeRecord(id), a failsafe version of findAndObserve(id) for records that may not exist (yet), and makes @experimentalFailsafe relations use it so they recover when the related record appears.

Refs #1257 and #1719.

Problem

findAndObserve(id) calls observer.error() when the record does not exist and observer.complete() when it is deleted. withObservables rethrows that error in render() and ignores completion. In practice:

  • A list row whose related record is missing (not synced yet, or deleted on another device) crashes the whole React tree unless every row has its own Error Boundary.
  • A record that arrives later, for example through sync, is never shown, because the observable has already errored.
  • Relation.observe() goes through findAndObserve, so relations pointing at missing records behave the same way.
  • @experimentalFailsafe wraps observe() in catchError(() => of(fallback)). That emits the fallback once and completes, so the relation never recovers either.

Changes

  • Collection.observeRecord(id) emits:

    • the record if it exists, then again on every change;
    • null while the record is missing;
    • the record once a created change for that id arrives on the collection;
    • null again instead of completing when the record is deleted, and keeps observing after that.

    It still errors for invalid ids such as null, like find(). It reuses the existing plumbing: _fetchRecord now delegates to a new _fetchRecordIfExists that returns null instead of throwing, and creation is detected with Collection.experimentalSubscribe. There are no new dependencies. The behaviour of findAndObserve and find is unchanged.

  • Relation accepts a new failsafe option. When it is set, observe() uses observeRecord instead of findAndObserve, for both mutable and immutable relations.

  • @experimentalFailsafe marks the decorated relation as failsafe. A missing related record is mapped to the decorator's fallback value, while a relation whose id is null still emits null. The observable now recovers when the record is created. catchError stays in place for other errors and for non-relation values.

  • Flow types and the hand-maintained .d.ts for Collection and Relation.

  • Docs in CRUD.md, Components.md and Relation.md, plus a line in CHANGELOG-Unreleased.md.

I picked a separate method rather than findAndObserve(id, { failsafe: true }) because the emitted type differs (Observable<?Record> vs Observable<Record>). A separate name keeps both signatures precise in Flow and TypeScript.

Tests

  • src/Collection/test.js: the first tests for findAndObserve, covering emit, update, complete on delete, and error when missing. observeRecord cases cover missing → null, created later → record, deleted → null without completing, then re-created → record, and an uncached record whose id contains colons (12:Quiz:hand). There are also tests for a creation racing with the initial fetch, invalid ids, and cleanup on unsubscribe.
  • src/Relation/test.js: a non-failsafe relation still errors. Failsafe mutable and immutable relations emit null and recover on create, update, delete and re-point.
  • src/decorators/experimentalFailsafe/test.js (new): relations are marked failsafe, fetch() falls back, and observe() emits the fallback and then recovers. A null id stays null, and arbitrary observables still get catchError.
  • src/react/withObservables/withObservables.test.js: a null prop from observeRecord renders without throwing and re-renders when the record is created or deleted. Observable errors are still rethrown in render.

Jest for src/Collection src/Relation src/decorators src/react src/observation passes, as do eslint, tslint and flow check.

Example

withObservables(['postId'], ({ postId, database }) => ({
  post: database.get('posts').observeRecord(postId), // null until the post exists
}))

🤖 Generated with Claude Code

Adds Collection.observeRecord(id), a failsafe version of findAndObserve(id). It emits null while the record is missing, the record once it exists or is created, and null again instead of completing when it is deleted, and keeps observing throughout.

Relations get a failsafe option that makes observe() use observeRecord, and @experimentalFailsafe sets it, so failsafe relations recover when a missing related record is created. Model and Collection now notify a copy of their subscriber lists, so subscribers can unsubscribe while being notified. In TypeScript, Relation accepts a nullable record type.

Refs Nozbe#1257 and Nozbe#1719.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@StasDoskalenko

Copy link
Copy Markdown

Hi @joker-777, thank you for this, it's a really nice addition!

I maintain NitromelonDB, a maintained fork of WatermelonDB (New Architecture, Nitro Modules). We recently shipped Collection.findAndObserveOrNull(id) there with the same behaviour as observeRecord: null while the record is missing, the record once it's created (e.g. by sync), and null instead of completing when it's deleted.

What we don't have yet is the Relation / @experimentalFailsafe part, where relations recover once the related record shows up. A PR against our repo for that part (built on findAndObserveOrNull) would be very welcome. Our fork was rewritten in TypeScript (strict mode) to reduce the chance of bugs slipping through, so I think the community would really benefit if you opened your PRs against it too.

If you don't have the time or would rather not, no worries at all, I'm happy to port it myself. Just let me know. Thanks a lot!

This branch has not been deployed

No deployments
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