Skip to content

LegacyJavaScriptObject crash on Flutter web DDC after hot restart #389

@thang72

Description

@thang72

Bug Description

After a hot restart on Flutter web (DDC debug mode), SqliteAsyncDriftConnection's BroadcastChannel listener crashes with a TypeError. This kills all Drift watch query update notifications, resulting in stale data. The error does not occur on:

  • Fresh app launch
  • Full page reload (Ctrl+Shift+R)
  • Hot reload

Error

DartError: TypeError: Instance of 'LegacyJavaScriptObject': type 'LegacyJavaScriptObject' is not a subtype of type 'UpdateNotification?'
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 274:3       throw_
dart-sdk/lib/_internal/js_shared/lib/rti.dart 1516:3                              _generalNullableAsCheckImplementation
dart-sdk/lib/_internal/js_shared/lib/rti.dart 89:34                               _installSpecializedAsCheck
dart-sdk/lib/async/stream_pipe.dart 152:15                                        [_add]
dart-sdk/lib/async/stream_pipe.dart 252:9                                         [_handleData]
dart-sdk/lib/async/stream_pipe.dart 184:5                                         [_handleData]
package:web/src/helpers/events/streams.dart 165:63                                <fn>
dart-sdk/lib/_internal/js_dev_runtime/patch/js_allow_interop_patch.dart 224:27    _callDartFunctionFast1

Root Cause

Hot restart recompiles Dart code and regenerates DDC runtime type information. However, the JavaScript BroadcastChannel.onmessage callback (created by SqliteAsyncDriftConnection via sqlite_async's BroadcastUpdates) retains references to the old DDC type system. When BroadcastChannel fires after hot restart, the old callback casts the message using stale type references → TypeError → listener dies → all future Drift watch query updates lost.

The crash occurs in sqlite_async/lib/src/web/database/broadcast_updates.dart where event.data is cast to _BroadcastMessage. After hot restart, the DDC runtime cannot resolve the type, and it arrives as a raw LegacyJavaScriptObject.

Impact

  • Dev workflow: Hot restart is unusable on web — developers must use full page refresh
  • Production: Not affected (release builds don't hot restart)

Environment

  • powersync: 1.18.0
  • powersync_core: 1.8.0
  • drift_sqlite_async: 0.2.6
  • sqlite_async: 0.13.1
  • drift: 2.31.0
  • Dart SDK: 3.9.0
  • Flutter web, DDC (debug mode)
  • Chrome 130+
  • macOS

Steps to Reproduce

  1. Create a Flutter web app with PowerSync + Drift using SqliteAsyncDriftConnection
  2. Run on Chrome in debug mode (DDC): flutter run -d chrome
  3. Verify data loads and watch queries work (Drift streams emit)
  4. Trigger a hot restart (Shift+R in terminal, or restart from IDE)
  5. Perform any database write that triggers a BroadcastChannel notification
  6. Observe the LegacyJavaScriptObject error in console
  7. Drift watch queries stop updating — data is stale

Workaround

Use full page refresh (Ctrl+Shift+R) instead of hot restart during web development.

Suggested Fix

The BroadcastUpdates class in sqlite_async could wrap the BroadcastChannel.onmessage handler with error handling, or re-register the listener on hot restart. Alternatively, the _BroadcastMessage JS interop type could use a more resilient deserialization path that doesn't rely on DDC runtime type identity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions