Coder Social home page Coder Social logo

skilldevs / electric_dart Goto Github PK

View Code? Open in Web Editor NEW
90.0 8.0 8.0 11.61 MB

A Dart implementation for Electric (electric-sql.com).

License: Apache License 2.0

Dart 75.59% Kotlin 0.01% Ruby 0.14% Swift 0.10% Objective-C 0.01% CMake 0.96% C++ 1.19% C 0.07% JavaScript 21.15% HTML 0.13% Shell 0.26% Makefile 0.35% Dockerfile 0.07%

electric_dart's Introduction

Tests E2E

pub package pub package pub package

Electric Dart

🛠️ WORK IN PROGRESS 🛠️

Electric is currently in public alpha phase, and the Dart client is currently being developed introducing the new features from the official client as they come out. For development updates make sure to check out the official ElectricSQL Discord server, as well as the official Javascript client


Unofficial Dart client implementation for Electric.

Client based on the Typescript client from the clients/typescript subfolder from electric git repository

Reference implementation:

  • NPM package.
  • Version v0.11.3-dev
  • Commit: 7ac268ddfc636514568219a383fbcc8d818b92f2

What is ElectricSQL?

ElectricSQL is a local-first sync layer for modern apps. Use it to build reactive, realtime, local-first apps using standard open source Postgres and SQLite.

The ElectricSQL client provides a type-safe database client autogenerated from your database schema and APIs for Shape-based sync and live queries. The client combines with the drift package to provide a seamless experience for building local-first apps in Dart.

Local-first is a new development paradigm where your app code talks directly to an embedded local database and data syncs in the background via active-active database replication. Because the app code talks directly to a local database, apps feel instant. Because data syncs in the background via active-active replication it naturally supports multi-user collaboration and conflict-free offline.

Introduction & Live Demos

Run the Todos example

This is a simple Todos app which can sync across all the platforms supported by Flutter (iOS, Android, Web, Windows, macOS and Linux).

Instructions

Electric Flutter

Quickstart

Quickstart to integrate Electric into your own Flutter app.

Usage

Instantiate

To handle type conversions and reactivity of the sync system, this package can be integrated with drift. To start using Electric, you need to electrify your database as follows.

import 'package:electricsql/electricsql.dart';
import 'package:electricsql_flutter/drivers/drift.dart';

// This would be the Drift database
AppDatabase db;

final electric = await electrify<AppDatabase>(
    dbName: '<db_name>',
    db: db,
    // Bundled migrations. This variable is autogenerated using
    // `dart run electricsql_cli generate`
    migrations: kElectricMigrations,
    config: ElectricConfig(
        // Electric service URL
        url: 'http://<ip>:5133',
        // logger: LoggerConfig(
        //     level: Level.debug, // in production you can use Level.off
        // ),
    ),
);

// https://electric-sql.com/docs/usage/auth
// You can use the functions `insecureAuthToken` or `secureAuthToken` to generate one
final String jwtAuthToken = '<your JWT>';

// Connect to the Electric service
await electric.connect(jwtAuthToken);

Sync data

Shapes are the core primitive for controlling sync in the ElectricSQL system. Shapes docs

Wait for sync finished

If the shape subscription is invalid, the first promise will be rejected. If the data load fails for some reason, the second promise will be rejected.

// Resolves once the shape subscription is confirmed by the server.
final shape = await electric.syncTable(<some_shape>);

// Resolves once the initial data load for the shape is complete.
await shape.synced

Sync a full table

final shape = await electric.syncTable(db.projects);

Sync a filtered set of rows

final shape = await electric.syncTable(
    db.projects,
    // Boolean expression with drift syntax
    where: (p) => p.status.equals('active') & p.title.contains('foo'),
);

Sync deep nested shapes

The $relations field is autogenerated by the Electric CLI as part of your drift schema. In this example, projects are synced with all its related content (project issues, issue comments and comment authors).

final shape = await electric.syncTable(
    db.projects,
    include: (p) => [
        SyncInputRelation.from(
            p.$relations.issues,
            include: (i) => [
                SyncInputRelation.from(
                    i.$relations.comments,
                    include: (c) => [
                        SyncInputRelation.from(c.$relations.author),
                    ],
                ),
            ],
        ),
    ],
);

Read data

Bind live data to the widgets. This can be possible when using drift + its Stream queries.

Create a Stream query with the drift watch API

AppDatabase db;
// Since we've electrified it, we can now read from the drift db as usual.
// https://drift.simonbinder.eu/docs/dart-api/select/

// Watch query using drift Dart API
final Stream<List<Todo>> todosStream = db.select(db.todos).watch();

// Watch query using raw SQL
final Stream<List<QueryRow>> rawTodosStream = db.customSelect(
    'SELECT * FROM todos',
    // This is important so that Drift knows when to run this query again
    // if the table changes
    readsFrom: {db.todos},
).watch();

Make widgets reactive

// Stateful Widget + initState
todosStream.listen((List<Todo> liveTodos) {
    setState(() => todos = liveTodos.toList());
});

// StreamBuilder
StreamBuilder<List<Todo>>(
    stream: todosStream,
    builder: (context, snapshot) {
        // Manage loading/error/loaded states
        ...
    },
);

Write data

You can use the original database instance normally so you don't need to change your database code at all. The data will be synced automatically, even raw SQL statements.

AppDatabase db;

// Using the standard Drift API
// https://drift.simonbinder.eu/docs/dart-api/writes/
await db.into(db.todos).insert(
    TodosCompanion.insert(
        title: 'My todo',
        createdAt: DateTime.now(),
    ),
);

// Or raw SQL
// WARNING: Even though this is possible, it's not recommended to use raw SQL to
// insert/update data as you would be bypassing certain formats that Electric
// expects for some special data types like UUIDs, timestamps, int4, etc...
//
// It's perfectly safe to use raw SQL for SELECT queries though, you would only
// need to tell drift what tables are being used in the query so that Stream queries
// work correctly
//
// If you really need a raw INSERT/UPDATE you can encode the parameters using the
// `TypeConverters` class.
// Like: `TypeConverters.timestampTZ.encode(DateTime.now())`
await db.customInsert(
    'INSERT INTO todos (title, created_at) VALUES (?, ?)',
    variables: [
        Variable('My todo'),
        Variable(TypeConverters.timestampTZ.encode(DateTime.now())),
    ],
    updates: {db.todos}, // This will notify stream queries to rebuild the widget
);

This automatic reactivity works no matter where the write is made — locally, on another device, by another user, or directly into Postgres.

More about ElectricSQL

Check out the official docs from ElectricSQL here to look at live demos, API docs and integrations.

DevTools

The package provides a DevTools extension to interact with the Electric service during development. That is: check the status of the service connectivity, inspect the table schemas, delete the local database, check the status of the shape subscriptions...

Reset local database

To add support for the reset local database button you need to tell Electric how to reset the local database. On non-web platforms is simply closing the database connection and deleting the file. You can see a cross platform implementation in the todos_flutter example.

ElectricDevtoolsBinding.registerDbResetCallback(
    electricClient, // output of `electrify`
    () async {
        await db.close();
        await deleteDbFile(db); 
    },
);

Development instructions for maintainers and contributors

Dart 3.x and Melos required

dart pub global activate melos

Bootstrap the workspace

melos bs

Generate the Protobuf code

Install the protoc_plugin Dart package.

dart pub global activate protoc_plugin

To generate the code

melos generate_proto

Run the tests

melos test:all

electric_dart's People

Contributors

davidmartos96 avatar etienneolivier avatar opsb avatar simolus3 avatar

Stargazers

amjil avatar Kay avatar Fikri Razzaq avatar Bram Hoendervangers avatar  avatar Marcos Alejandro avatar Joel Riley avatar Mahesh Bansode avatar Cihan Cengiz avatar Steven John avatar Ikhwan avatar yet-another-tim avatar Nikita Vasin avatar Thomas Pucci avatar Brad Pillow avatar Hauke Schnau avatar sbhamad avatar Alexandru Tuca avatar  avatar  avatar ZF avatar Jacopo Guzzo avatar Chandan kumar yadav avatar James Doyle avatar Rohan avatar  avatar  avatar Bright Etornam Sunu  avatar Ilyas LEFEBVRE avatar Martin Hausleitner avatar Omid Raha avatar Viktor Marchenko avatar Burhanuddin Rashid avatar Zaki Mubarok avatar Aljan avatar Brian Kariuki  avatar  avatar Dave Hole avatar Suhabe Bugrara avatar Ayotomide avatar Tomiwa Idowu avatar Siyabonga Konile avatar Jeremiah Ogbomo avatar Mizanur Rahman avatar Olivier Heidemann avatar Albertus Angga Raharja avatar Seungwoo hong avatar Sacha Arbonel avatar Arman avatar  avatar Gabor Dolla avatar z10g avatar limit1219c avatar Mohan Singh Thagunna avatar Dmitry Grachikov avatar Einali avatar Rohan Taneja avatar Andy Chentsov avatar Bee Ing avatar Sebastian Kurfürst avatar Omar Jennane avatar yawnt avatar Leandro Azevedo avatar Chris Wickens avatar Jing Zhou avatar Arin Faraj avatar Jaspreet Singh avatar Alterhuman avatar Giovane de Loredo avatar Ciornii Maxim avatar Carlos Moises Arevalo avatar  avatar Mandy Schoep avatar  avatar  avatar Joohun, Maeng avatar Arvind T avatar Lucía María Álvarez-Crespo avatar  avatar Mahmoud Almontasser avatar alan runyan avatar Britannio Jarrett avatar Kevin avatar Valter Balegas avatar Volodymyr avatar O.T. avatar Oleksii Sholik avatar Ryan avatar  avatar Jorge Sardina avatar

Watchers

 avatar Kobie Botha avatar alan runyan avatar Jorge Sardina avatar  avatar Leandro Azevedo avatar limit1219c avatar Marcos Alejandro avatar

electric_dart's Issues

Schema versioning conflicts

When performing an update of Electric service (with new migration schemas for Postgres) + deploying a new Flutter app (that matches new schemas), some app users will still have the old version of the app

Now, the lib fetches the migrations (for sqlite dialect) from Electric service, on App startup, and apply them on the local sqlite db

The problem
The old app may not work with the new schema

The possible solution
I'm still reshaping my brain to think Offline first, does forward/additive migration planning will cover such cases?

Error when Satellite performs a Snapshot after DB Connection has been closed

Hello there 👋
I am slowly implementing electricsql in my app. Very promising 🚀

I've started some unit tests.
They are relatively quick and small.

When tearing down those tests, I close the electric client and the database (drift):

await electricClient.close();
await database.close();

However, even if the test passes, I see error logs:

CouldNotRollBackException: Bad state: Tried to send Request (id = 57): RunTransactionAction(TransactionControl.rollback, 5) over isolate channel, but the connection was closed!. 
For context: The transaction was rolled back because of Instance of 'ConnectionClosedException', which was thrown here: 
...
package:electricsql/src/drivers/drift/drift_adapter.dart 57:19   DriftAdapter.transaction.<fn>.<fn>
...
===== asynchronous gap ===========================
...
package:electricsql/src/drivers/drift/drift_adapter.dart 114:8   Transaction.run
package:electricsql/src/satellite/process.dart 1037:29           SatelliteProcess.performSnapshot.<fn>.<fn>.<fn>
...
===== asynchronous gap ===========================
...
package:electricsql/src/satellite/process.dart 142:14            SatelliteProcess._mutexSnapshot.<fn>
...
===== asynchronous gap ===========================
...
package:electricsql/src/drivers/drift/drift.dart 42:15           electrify

(logs cleared for clarity)

Any idea how to fix that? Happy to contribute if someone point me a direction 🙂

Offine First, Sync Optional

Can this be used as a simple Offline First solution without syncing? Syncing requires another host (I am using Supabase) is that correct? What if all I care about is offline capability? What if there is no need to sync? Just simply be able to work offline and have it push to Supabase when back online?

Thanks for the hard work, this is great stuff

Version solving failed in todos example

Getting this error on pubspec

Resolving dependencies... Because every version of electricsql_cli from path depends on electricsql from hosted and todos_electrified depends on electricsql from path, electricsql_cli from path is forbidden. So, because todos_electrified depends on electricsql_cli from path, version solving failed. exit code 1

Really excited to try electric btw. Thanks for building a dart client for it.

Error connecting to Electric: errorType: INVALID_REQUEST when pressing back button on main screen and reopening the app

Device: Android (14)

Flutter version/SDK: Flutter 3.22.0 (stable channel)

Plugin version: Tested both on v0.6.0 and v0.7.0 versions

Issue: Running the todos_flutter Flutter app following the todos_electrified instructions will generate an INVALID_REQUEST error when trying to reconnect to Electric after pressing the system back button and reopening the app from background. This might be an issue on newer Android version that the app would not retain its state upon waking from sleep.

Expected behaviour: After the first initialization, pressing the back button in the main screen (leaving the app running in background) and then reopening it should reconnect to Electric (like on a normal startup)

How to reproduce the issue:
After initializing the example docker and project following the README:

  1. Open the app
  2. Let it connect to electric
  3. Press the back button on the device, then reopen the app (without closing it first)
  4. The error will occur

Stack log (client):

Connecting to VM Service at ws://127.0.0.1:49634/vAFa1zbMXhQ=/ws
D/VRI[MainActivity](15904): Cancelling draw. cancelDueToPreDrawListener=true cancelDueToSync=false
I/Gralloc4(15904): Adding additional valid usage bits: 0x8202000
D/VRI[MainActivity](15904): Cancelling draw. cancelDueToPreDrawListener=true cancelDueToSync=false
I/flutter (15904): Using todos database at path /data/user/0/com.example.todos_electrified/app_flutter/todos-electric/todos.db
D/VRI[MainActivity](15904): Cancelling draw. cancelDueToPreDrawListener=true cancelDueToSync=false
I/flutter (15904): Dummy onCreate
I/flutter (15904): Electrifying database...
I/flutter (15904): Electric URL: http://192.168.11.245:5133
I/flutter (15904): [Electric] INFO:  10:48:48.479 Using SQLite version: 3.45.3 
I/flutter (15904): [Electric] INFO:  10:48:48.513 applying migration: 0 
I/flutter (15904): [Electric] INFO:  10:48:48.544 applying migration: 20230924100310 
I/flutter (15904): [Electric] INFO:  10:48:48.563 applying migration: 20230924100404 
I/flutter (15904): [Electric] INFO:  10:48:48.632 no lsn retrieved from store 
I/flutter (15904): Database electrified!
I/flutter (15904): [Electric] DEBUG: 10:48:48.729 notifying Electric of database changes. Changed tables: {todolist} 
I/flutter (15904): [Electric] INFO:  10:48:49.213 connecting to electric server 
I/flutter (15904): [Electric] DEBUG: 10:48:49.825 [rpc] send: #SatAuthReq{id: 956a17af-751b-4f62-af5a-6dbbc13ecb2b, token: eyJhbGciOiJub25lIn0=.eyJzdWIiOiJjNTVmMWYyOC1mNzQzLTQ3YjctYjY3MC0zOWY4YjY0MTg5M2UifQ==.} 
I/flutter (15904): [Electric] DEBUG: 10:48:49.834 [proto] send: #SatRpcRequest{method: authenticate, requestId: 1} 
I/flutter (15904): [Electric] DEBUG: 10:48:49.867 notifying client of database changes. Changed tables: [todolist]. Origin: local 
I/flutter (15904): [Electric] DEBUG: 10:48:49.907 [proto] recv: #SatRpcResponse{method: authenticate, requestId: 1} 
I/flutter (15904): [Electric] DEBUG: 10:48:49.912 [rpc] recv: #SatAuthResp{id: d4972d63-cc43-411c-b669-1a40bde4a74d} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.148 [proto] recv: #SatRpcRequest{method: startReplication, requestId: 0} 
I/flutter (15904): [Electric] INFO:  10:48:50.161 Server sent a replication request to start from 0, and options () 
I/flutter (15904): [Electric] DEBUG: 10:48:50.167 [proto] send: #SatRpcResponse{method: startReplication, requestId: 0} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.194 [proto] send: #SatRelation{for: public.todolist, as: 0, cols: id: TEXT, filter: TEXT, editing: TEXT} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.230 [proto] send: #SatOpLog{ops: [#Begin{lsn: AAAAAQ==, ts: 1715849328747, isMigration: false}, #Insert{for: 0, tags: [], new: ["LIST-ID-SAMPLE", ∅, ∅]}, #Commit{lsn: }]} 
I/flutter (15904): [Electric] INFO:  10:48:50.250 no previous LSN, start replication from scratch 
I/flutter (15904): [Electric] DEBUG: 10:48:50.252 [rpc] send: #SatInStartReplicationReq{lsn: , schema: 20230924100404, subscriptions: []} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.255 [proto] send: #SatRpcRequest{method: startReplication, requestId: 2} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.279 [proto] recv: #SatRpcResponse{method: startReplication, requestId: 2} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.283 [rpc] recv: #SatInStartReplicationResp{} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.412 [rpc] send: #SatSubsReq{id: ceeb9690-3883-44ba-863e-f2ace3cc9c4c, shapes: [{"1":"f22d7a4a-38b5-4254-b87d-9ab817cbf557","2":{"1":[{"1":"todolist","3":[{"1":["listid"],"2":{"1":"todo"}}]}]}}]} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.412 [proto] send: #SatRpcRequest{method: subscribe, requestId: 3} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.512 [proto] recv: #SatRpcResponse{method: subscribe, requestId: 3} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.520 [rpc] recv: #SatSubsResp{id: ceeb9690-3883-44ba-863e-f2ace3cc9c4c} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.523 [proto] recv: #SatOpLog{ops: [#Begin{lsn: MjY5Mjk4MTY=, ts: 1715849328747, isMigration: false}, #Commit{lsn: MjY5Mjk4MTY=}]} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.534 [proto] recv: #SatRelation{for: public.todolist, as: 16821, cols: id: text PK, filter: text, editing: text} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.536 [proto] recv: #SatSubsDataBegin{id: ceeb9690-3883-44ba-863e-f2ace3cc9c4c, lsn: MjY5MzIyNjQ=} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.539 [proto] recv: #SatShapeDataBegin{id: f22d7a4a-38b5-4254-b87d-9ab817cbf557} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.541 [proto] recv: #SatOpLog{ops: [#Insert{for: 16821, tags: [f1c3addf-a1ae-43e0-93e8-c6b1d1aa8eb6@1715847403745, 956a17af-751b-4f62-af5a-6dbbc13ecb2b@1715849328747], new: ["LIST-ID-SAMPLE", ∅, ∅]}]} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.542 [proto] recv: #SatShapeDataEnd{} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.542 [proto] recv: #SatSubsDataEnd{} 
I/flutter (15904): [Electric] DEBUG: 10:48:50.592 notifying client of database changes. Changed tables: [todolist]. Origin: initial 
I/flutter (15904): [Electric] DEBUG: 10:48:50.594 notifying Drift of database changes: Changed tables: {todolist}. Origin: initial 
I/flutter (15904): [Electric] DEBUG: 10:48:50.596 [proto] send: #SatOpLogAck{lsn: MjY5Mjk4MTY=, txid: 762} 
W/dos_electrified(15904): ApkAssets: Deleting an ApkAssets object '<empty> and /data/app/~~wvIKYnQN8Z6uwyHGNzBTcA==/com.google.android.marvin.talkback-lSfObUgBlT6vOfsmG0LA2w==/base.apk' with 1 weak references
W/dos_electrified(15904): ApkAssets: Deleting an ApkAssets object '<empty> and /data/app/~~wvIKYnQN8Z6uwyHGNzBTcA==/com.google.android.marvin.talkback-lSfObUgBlT6vOfsmG0LA2w==/split_config.arm64_v8a.apk' with 1 weak references

# Reopening the app after back button press

W/WindowOnBackDispatcher(15904): sendCancelIfRunning: isInProgress=falsecallback=android.view.ViewRootImpl$$ExternalSyntheticLambda17@72cd5ff
I/AdrenoGLES-0(15904): QUALCOMM build                   : 329cf4c2a7, I63533b1e29
I/AdrenoGLES-0(15904): Build Date                       : 01/31/23
I/AdrenoGLES-0(15904): OpenGL ES Shader Compiler Version: EV031.35.01.12
I/AdrenoGLES-0(15904): Local Branch                     :
I/AdrenoGLES-0(15904): Remote Branch                    : refs/tags/AU_LINUX_ANDROID_LA.UM.9.14.11.00.00.571.148
I/AdrenoGLES-0(15904): Remote Branch                    : NONE
I/AdrenoGLES-0(15904): Reconstruct Branch               : NOTHING
I/AdrenoGLES-0(15904): Build Config                     : S P 10.0.7 AArch64
I/AdrenoGLES-0(15904): Driver Path                      : /vendor/lib64/egl/libGLESv2_adreno.so
I/AdrenoGLES-0(15904): PFP: 0x016dc094, ME: 0x00000000
E/OpenGLRenderer(15904): Unable to match the desired swap behavior.
D/VRI[MainActivity](15904): Cancelling draw. cancelDueToPreDrawListener=true cancelDueToSync=false
I/flutter (15904): Using todos database at path /data/user/0/com.example.todos_electrified/app_flutter/todos-electric/todos.db
I/flutter (15904): Electrifying database...
I/flutter (15904): Electric URL: http://192.168.11.245:5133
I/flutter (15904): [Electric] INFO:  10:50:09.082 Using SQLite version: 3.45.3 
I/flutter (15904): [Electric] INFO:  10:50:09.175 retrieved lsn [50, 54, 57, 51, 50, 50, 54, 52] 
I/flutter (15904): Database electrified!
I/flutter (15904): [Electric] INFO:  10:50:09.690 connecting to electric server 
I/flutter (15904): [Electric] DEBUG: 10:50:10.388 [rpc] send: #SatAuthReq{id: 956a17af-751b-4f62-af5a-6dbbc13ecb2b, token: eyJhbGciOiJub25lIn0=.eyJzdWIiOiJmMGVjMTUzNi04NTU1LTRmNmYtYTI4ZS00M2FhNTJhYzk1ZWIifQ==.} 
I/flutter (15904): [Electric] DEBUG: 10:50:10.398 [proto] send: #SatRpcRequest{method: authenticate, requestId: 1} 
I/flutter (15904): [Electric] DEBUG: 10:50:11.752 [proto] recv: #SatRpcResponse{method: authenticate, requestId: 1, error: #SatErrorResp{type: INVALID_REQUEST}} 
I/flutter (15904): [Electric] WARN:  10:50:11.753 RPC call authenticate/1 failed with #SatErrorResp{type: INVALID_REQUEST} 
I/flutter (15904): [Electric] DEBUG: 10:50:11.763 server returned an error while establishing connection: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] DEBUG: 10:50:11.764 connectAndStartRetryHandler was cancelled: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): Error connecting to Electric: errorType: INVALID_REQUEST
I/flutter (15904):
I/flutter (15904): [Electric] WARN:  10:50:11.783 an error occurred in satellite: socket closed 
I/flutter (15904): [Electric] WARN:  10:50:11.784 Client disconnected with a non fatal error, reconnecting 
I/flutter (15904): [Electric] INFO:  10:50:11.785 connecting to electric server 
I/flutter (15904): [Electric] DEBUG: 10:50:11.900 [rpc] send: #SatAuthReq{id: 956a17af-751b-4f62-af5a-6dbbc13ecb2b, token: eyJhbGciOiJub25lIn0=.eyJzdWIiOiJmMGVjMTUzNi04NTU1LTRmNmYtYTI4ZS00M2FhNTJhYzk1ZWIifQ==.} 
I/flutter (15904): [Electric] DEBUG: 10:50:11.901 [proto] send: #SatRpcRequest{method: authenticate, requestId: 2} 
I/flutter (15904): [Electric] DEBUG: 10:50:12.947 [proto] recv: #SatRpcResponse{method: authenticate, requestId: 2, error: #SatErrorResp{type: INVALID_REQUEST}} 
I/flutter (15904): [Electric] WARN:  10:50:12.948 RPC call authenticate/2 failed with #SatErrorResp{type: INVALID_REQUEST} 
I/flutter (15904): [Electric] DEBUG: 10:50:12.952 server returned an error while establishing connection: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] DEBUG: 10:50:12.953 connectAndStartRetryHandler was cancelled: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] WARN:  10:50:12.960 an error occurred in satellite: socket closed 
I/flutter (15904): [Electric] WARN:  10:50:12.962 Client disconnected with a non fatal error, reconnecting 
I/flutter (15904): [Electric] INFO:  10:50:12.963 connecting to electric server 
I/flutter (15904): [Electric] DEBUG: 10:50:12.984 [rpc] send: #SatAuthReq{id: 956a17af-751b-4f62-af5a-6dbbc13ecb2b, token: eyJhbGciOiJub25lIn0=.eyJzdWIiOiJmMGVjMTUzNi04NTU1LTRmNmYtYTI4ZS00M2FhNTJhYzk1ZWIifQ==.} 
I/flutter (15904): [Electric] DEBUG: 10:50:12.985 [proto] send: #SatRpcRequest{method: authenticate, requestId: 3} 
I/flutter (15904): [Electric] DEBUG: 10:50:14.171 [proto] recv: #SatRpcResponse{method: authenticate, requestId: 3, error: #SatErrorResp{type: INVALID_REQUEST}} 
I/flutter (15904): [Electric] WARN:  10:50:14.172 RPC call authenticate/3 failed with #SatErrorResp{type: INVALID_REQUEST} 
I/flutter (15904): [Electric] DEBUG: 10:50:14.174 server returned an error while establishing connection: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] DEBUG: 10:50:14.175 connectAndStartRetryHandler was cancelled: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] WARN:  10:50:14.178 an error occurred in satellite: socket closed 
I/flutter (15904): [Electric] WARN:  10:50:14.178 Client disconnected with a non fatal error, reconnecting 
I/flutter (15904): [Electric] INFO:  10:50:14.179 connecting to electric server 
I/flutter (15904): [Electric] DEBUG: 10:50:14.206 [rpc] send: #SatAuthReq{id: 956a17af-751b-4f62-af5a-6dbbc13ecb2b, token: eyJhbGciOiJub25lIn0=.eyJzdWIiOiJmMGVjMTUzNi04NTU1LTRmNmYtYTI4ZS00M2FhNTJhYzk1ZWIifQ==.} 
I/flutter (15904): [Electric] DEBUG: 10:50:14.206 [proto] send: #SatRpcRequest{method: authenticate, requestId: 4} 
I/flutter (15904): [Electric] DEBUG: 10:50:15.400 [proto] recv: #SatRpcResponse{method: authenticate, requestId: 4, error: #SatErrorResp{type: INVALID_REQUEST}} 
I/flutter (15904): [Electric] WARN:  10:50:15.401 RPC call authenticate/4 failed with #SatErrorResp{type: INVALID_REQUEST} 
I/flutter (15904): [Electric] DEBUG: 10:50:15.403 server returned an error while establishing connection: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] DEBUG: 10:50:15.405 connectAndStartRetryHandler was cancelled: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] WARN:  10:50:15.407 an error occurred in satellite: socket closed 
I/flutter (15904): [Electric] WARN:  10:50:15.408 Client disconnected with a non fatal error, reconnecting 
I/flutter (15904): [Electric] INFO:  10:50:15.408 connecting to electric server 
I/flutter (15904): [Electric] DEBUG: 10:50:15.445 [rpc] send: #SatAuthReq{id: 956a17af-751b-4f62-af5a-6dbbc13ecb2b, token: eyJhbGciOiJub25lIn0=.eyJzdWIiOiJmMGVjMTUzNi04NTU1LTRmNmYtYTI4ZS00M2FhNTJhYzk1ZWIifQ==.} 
I/flutter (15904): [Electric] DEBUG: 10:50:15.447 [proto] send: #SatRpcRequest{method: authenticate, requestId: 5} 
I/flutter (15904): [Electric] DEBUG: 10:50:16.627 [proto] recv: #SatRpcResponse{method: authenticate, requestId: 5, error: #SatErrorResp{type: INVALID_REQUEST}} 
I/flutter (15904): [Electric] WARN:  10:50:16.629 RPC call authenticate/5 failed with #SatErrorResp{type: INVALID_REQUEST} 
I/flutter (15904): [Electric] DEBUG: 10:50:16.632 server returned an error while establishing connection: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] DEBUG: 10:50:16.633 connectAndStartRetryHandler was cancelled: errorType: INVALID_REQUEST
I/flutter (15904):  
I/flutter (15904): [Electric] WARN:  10:50:16.636 an error occurred in satellite: socket closed 
I/flutter (15904): [Electric] WARN:  10:50:16.637 Client disconnected with a non fatal error, reconnecting 

# ^ Reconnection loop and exception happening until manual app termination

Application finished.

Exited (-1).

Stack log (server):

electric-1  | 08:48:49.122 pid=<0.4027.0> [info] GET /ws
electric-1  | 08:48:49.124 pid=<0.4027.0> instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] Sent 101 in 1ms
electric-1  | 08:48:49.305 pid=<0.4027.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d user_id=c55f1f28-f743-47b7-b670-39f8b641893e [info] Successfully authenticated the client
electric-1  | 08:48:49.305 pid=<0.4027.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d user_id=c55f1f28-f743-47b7-b670-39f8b641893e [info] Postgres.Client.with_conn(%{database: ~c"todos_electrified", host: ~c"postgres", ip_addr: ~c"172.19.0.2", ipv6: false, nulls: [nil, :null, :undefined], password: ~c"******", port: 5432, ssl: false, ssl_opts: [server_name_indication: ~c"postgres"], timeout: 5000, username: ~c"postgres"})
electric-1  | 08:48:49.872 pid=<0.4032.0> [info] Postgres.Client.with_conn(%{database: ~c"todos_electrified", host: ~c"postgres", ip_addr: ~c"172.19.0.2", ipv6: false, nulls: [nil, :null, :undefined], password: ~c"******", port: 5432, ssl: false, ssl_opts: [server_name_indication: ~c"postgres"], timeout: 5000, username: ~c"postgres"})
electric-1  | 08:48:49.898 pid=<0.4034.0> [info] Postgres.Client.with_conn(%{database: ~c"todos_electrified", host: ~c"postgres", ip_addr: ~c"172.19.0.2", ipv6: false, nulls: [nil, :null, :undefined], password: ~c"******", port: 5432, ssl: false, ssl_opts: [server_name_indication: ~c"postgres"], timeout: 5000, username: ~c"postgres"})
electric-1  | 08:50:04.140 pid=<0.4027.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d user_id=c55f1f28-f743-47b7-b670-39f8b641893e [info] Client is not responding to ping, disconnecting
electric-1  | 08:50:09.532 pid=<0.4036.0> [info] GET /ws
electric-1  | 08:50:09.532 pid=<0.4036.0> instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] Sent 101 in 194µs
electric-1  | 08:50:10.870 pid=<0.4036.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] attempted multiple connections from the same client
electric-1  | 08:50:11.338 pid=<0.4037.0> [info] GET /ws
electric-1  | 08:50:11.338 pid=<0.4037.0> instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] Sent 101 in 246µs
electric-1  | 08:50:12.350 pid=<0.4037.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] attempted multiple connections from the same client
electric-1  | 08:50:12.419 pid=<0.4039.0> [info] GET /ws
electric-1  | 08:50:12.419 pid=<0.4039.0> instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] Sent 101 in 189µs
electric-1  | 08:50:13.435 pid=<0.4039.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] attempted multiple connections from the same client
electric-1  | 08:50:13.641 pid=<0.4040.0> [info] GET /ws
electric-1  | 08:50:13.642 pid=<0.4040.0> instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] Sent 101 in 377µs
electric-1  | 08:50:14.656 pid=<0.4040.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] attempted multiple connections from the same client
electric-1  | 08:50:14.879 pid=<0.4041.0> [info] GET /ws
electric-1  | 08:50:14.879 pid=<0.4041.0> instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] Sent 101 in 170µs
electric-1  | 08:50:15.897 pid=<0.4041.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] attempted multiple connections from the same client
electric-1  | 08:50:16.099 pid=<0.4042.0> [info] GET /ws
electric-1  | 08:50:16.099 pid=<0.4042.0> instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] Sent 101 in 279µs
electric-1  | 08:50:17.119 pid=<0.4042.0> client_id=956a17af-751b-4f62-af5a-6dbbc13ecb2b instance_id=d4972d63-cc43-411c-b669-1a40bde4a74d [info] attempted multiple connections from the same client

Not updating views with streambuilder

I am using plain flutter, without flutter_hooks, riverpod and dont know how to reflect data in the UI.
I see that its getting into the client via console debug mode, when i open the local db, the data is there.

im using simple, which loads data properly, when its updated in backend db, its the same, when i do hot reload, then its updated.

StreamBuilder<Stat>(
        stream: StatsDAO(db!).watchStat(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Text('loading');
          }
          if (snapshot.hasError) {
            return Text(snapshot.error.toString());
          }
          if (snapshot.hasData && snapshot.data != null) {
            final stat = snapshot.data;
            return Text(
              "${stat?.content}",
            );
          }

          return const Text('No stat');
        },
      )

How can we do this with plain flutter?

[CLI] cant use custom .env file

Hello, im trying to do custom stats.env file to be processed when running generate, but its loading always the default one.
When i try with-config, then i have to specify everything in the command and its not convenient and also i experienced, that its not using it sometimes and loading default.

Why i need it is to have 2 databases (with 2 electrics, because "Specifically, right now, ElectricSQL works with a single database in a single Postgres installation with tables in the public schema.") and to properly generate schemas, it would be handy.

Is there an option to load custom env files like this?

#!/bin/bash

#Load environment file called chat.env
source ./stats.env

dart run ./electricsql_cli show-config

Todos_flutter errors

Hello, just downloaded this repo and did everything in readme to hook it up.
In init_app.dart

typedef InitData = ({
  TodosDatabase todosDb,
  ElectricClient<AppDatabase> electricClient,
  ConnectivityStateController connectivityStateController,
});

i get error that The type 'ElectricClient' is declared with 0 type parameters, but 1 type arguments were given. Try adjusting the number of type arguments to match the number of type parameters.

this is also in electric.dart and main.dart. Also its yelling that syncTable() and SyncInputRelation does not exist.

Can someone take a peek so the example works properly?

Thank you!

Supabase example

Electric v0.8 and the Electric Dart client v0.4+ now support Supabase for authentication and for the Postgres data synchronization.

It would be great to have an example that uses Supabase authentication supabase_auth_ui + a simple database schema that synchronizes with the Supabase Postgres.

For inspiration, the official Typescript client has a Checkout example, which uses auth and edge functions https://electric-sql.com/docs/examples/checkout

Contributions are welcome!

Bad state: No element when generating tables.

dart run electricsql_cli generate       
Generating the Electric client code...
Service URL: http://localhost:5133
Proxy URL: postgresql://prisma:********@IP:65432/electric
✓ Prisma CLI installed (1.4s)
✓ Database introspected (0.6s)
✗ Generating Drift DB schema (14ms)
generate command failed: Bad state: No element
Unhandled exception:
Bad state: No element
#0      ListBase.firstWhere (dart:collection/list.dart:132:5)
#1      _getPrismaRelationValue (package:electricsql_cli/src/commands/generate/prisma.dart:421:25)
#2      _extractOutgoindRelation (package:electricsql_cli/src/commands/generate/prisma.dart:364:24)
#3      _extractFromModel (package:electricsql_cli/src/commands/generate/prisma.dart:289:11)
#4      extractInfoFromPrismaSchema.<anonymous closure> (package:electricsql_cli/src/commands/generate/prisma.dart:172:25)
#5      MappedListIterable.elementAt (dart:_internal/iterable.dart:425:31)
#6      ListIterator.moveNext (dart:_internal/iterable.dart:354:26)
#7      new _GrowableList._ofEfficientLengthIterable (dart:core-patch/growable_array.dart:189:27)
#8      new _GrowableList.of (dart:core-patch/growable_array.dart:150:28)
#9      new List.of (dart:core-patch/array_patch.dart:39:18)
#10     ListIterable.toList (dart:_internal/iterable.dart:224:7)
#11     extractInfoFromPrismaSchema (package:electricsql_cli/src/commands/generate/prisma.dart:186:6)
#12     _generateClient (package:electricsql_cli/src/commands/generate/command.dart:370:22)
<asynchronous suspension>
#13     wrapWithProgress (package:electricsql_cli/src/util.dart:142:17)
<asynchronous suspension>
#14     _runGeneratorInner (package:electricsql_cli/src/commands/generate/command.dart:338:5)
<asynchronous suspension>
#15     _runGenerator (package:electricsql_cli/src/commands/generate/command.dart:275:5)
<asynchronous suspension>
#16     runElectricCodeGeneration (package:electricsql_cli/src/commands/generate/command.dart:154:3)
<asynchronous suspension>
#17     GenerateElectricClientCommand.run (package:electricsql_cli/src/commands/generate/command.dart:95:5)
<asynchronous suspension>
#18     CommandRunner.runCommand (package:args/command_runner.dart:212:13)
<asynchronous suspension>
#19     ElectricCliCommandRunner.runCommand (package:electricsql_cli/src/command_runner.dart:124:18)
<asynchronous suspension>
#20     ElectricCliCommandRunner.run (package:electricsql_cli/src/command_runner.dart:65:14)
<asynchronous suspension>
#21     main (file:///Users/user/.pub-cache/hosted/pub.dev/electricsql_cli-0.6.0/bin/electricsql_cli.dart:6:24)

Version 0.6.0

[How to] Use the latest master version, skipping the pub.dev

Sharing an small how to use the latest master version in your flutter project (omitting the pub.dev) you can modify the pubspec.yaml as follows:

dependencies:
  electricsql:
     git:
       url: https://github.com/SkillDevs/electric_dart.git
       ref: master
       path: packages/electricsql/
  electricsql_flutter:
    git:
      url: https://github.com/SkillDevs/electric_dart.git
      ref: master
      path: packages/electricsql_flutter/

dev_dependencies:
  electricsql_cli:
    git:
      url: https://github.com/SkillDevs/electric_dart.git
      ref: master
      path: packages/electricsql_cli/

dependency_overrides:
  electricsql:
    git:
      url: https://github.com/SkillDevs/electric_dart.git
      ref: master
      path: packages/electricsql/

Electric custom types (eg, Postgres date type) not generating correct SQL for query.

All ElectricTypes custom types currently implement DialectAwareSqlType via CustomElectricTypeGeneric. Which is great, but there is currently a bug in drift which, when creating variables via query builders, it does not correctly pass through the electric type as the helper is checking against CustomSqlType and not UserDefinedSqlType.

See: https://github.com/SkillDevs/electric_dart/blob/master/packages/electricsql/lib/src/client/conversions/custom_types.dart

Helper:

/// Utilities to derive other expressions with a type compatible to `this`
/// expression.
extension WithTypes<T extends Object> on Expression<T> {
  /// Creates a variable with a matching [driftSqlType].
  Variable<T> variable(T? value) {
    return switch (driftSqlType) {
      CustomSqlType<T> custom => Variable(value, custom),
      _ => Variable(value),
    };
  }
}

I've currently opened a PR to type-check against UserDefinedSqlType instead of CustomSqlType as that is what the Variable constructor is expecting: simolus3/drift#2909

This is more of an FYI issue as queries on for example the Postgres date type are currently not working in Electric Dart.

Issue after upgrade `_electric_trigger_settings has no column named namespace`

Hello again :)

After upgrading electric, and creating a new migration, I have the following error:

[Electric] INFO:  09:27:48.758 Using SQLite version: 3.45.3 
[Electric] Using SQLite version: 3.45.3
[Electric] INFO:  09:27:48.768 applying migration: 20240522072322 
[Electric] applying migration: 20240522072322
[Electric] ERROR: 09:27:48.780 Query error: SqliteException(1): while executing, table _electric_trigger_settings has no column named namespace, SQL logic error (code 1)
             Causing statement: INSERT OR IGNORE INTO _electric_trigger_settings (namespace, tablename, flag) VALUES ('main', 'arrow', 1);, parameters: 
           	Statement: 'INSERT OR IGNORE INTO _electric_trigger_settings (namespace, tablename, flag) VALUES ('main', 'arrow', 1);' - args: null 

Any idea how to solve this ?

Cheers !

  electricsql: ^0.7.1+4
  drift: ^2.15.0
  electricsql_flutter: ^0.7.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.