prisma_flutter_connector 0.2.4
prisma_flutter_connector: ^0.2.4 copied to clipboard
A type-safe Flutter connector for Prisma backends. Generate Dart models and type-safe APIs from your Prisma schema with support for PostgreSQL, MySQL, SQLite, and Supabase.
Changelog #
All notable changes to the Prisma Flutter Connector.
[Unreleased] #
0.2.4 - 2025-12-19 #
Added #
-
Relation Filtering SQL Compilation - Compile
some/every/noneoperators to EXISTS subqueries- One-to-Many:
EXISTS (SELECT 1 FROM related WHERE related.fk = parent.id AND ...) - Many-to-Many:
EXISTS (SELECT 1 FROM junction INNER JOIN target ON ... WHERE junction.A = parent.id AND ...) - One-to-One: Handles both owner and non-owner sides correctly
- All providers supported (PostgreSQL, Supabase, MySQL, SQLite)
- One-to-Many:
-
Relation Filter Operators
some→EXISTS (...)- At least one matchnone→NOT EXISTS (...)- No matchesevery→NOT EXISTS (... AND NOT condition)- All matchisEmpty()→NOT EXISTS (...)with no conditionisNotEmpty()→EXISTS (...)with no condition
Example Usage #
// Register schema with relation metadata
final schema = SchemaRegistry();
schema.registerModel(ModelSchema(
name: 'Product',
tableName: 'Product',
fields: {...},
relations: {
'reviews': RelationInfo(
name: 'reviews',
type: RelationType.oneToMany,
targetModel: 'Review',
foreignKey: 'productId',
references: ['id'],
),
'categories': RelationInfo(
name: 'categories',
type: RelationType.manyToMany,
targetModel: 'Category',
joinTable: '_ProductToCategory',
joinColumn: 'A',
inverseJoinColumn: 'B',
foreignKey: '',
references: ['id'],
),
},
));
// Query with relation filters
final query = JsonQueryBuilder()
.model('Product')
.action(QueryAction.findMany)
.where({
'isActive': true,
'reviews': FilterOperators.some({
'rating': FilterOperators.gte(4),
}),
'categories': FilterOperators.some({
'id': 'category-electronics',
}),
})
.orderBy({
'price': {'sort': 'asc', 'nulls': 'last'},
})
.build();
final compiler = SqlCompiler(provider: 'postgresql', schema: schema);
final sql = compiler.compile(query);
// Generates:
// SELECT * FROM "Product" WHERE "isActive" = $1
// AND EXISTS (SELECT 1 FROM "Review" WHERE "Review"."productId" = "Product"."id" AND "rating" >= $2)
// AND EXISTS (SELECT 1 FROM "_ProductToCategory" INNER JOIN "Category" ON "Category"."id" = "_ProductToCategory"."B" WHERE "_ProductToCategory"."A" = "Product"."id" AND "id" = $3)
// ORDER BY "price" ASC NULLS LAST
Why This Matters #
Previously, filtering on relations required raw SQL with manual EXISTS subqueries. Now you can:
- Use the same type-safe
FilterOperatorsAPI for relation filters - Automatically generate correct JOIN patterns for M:N relations
- Combine scalar filters with relation filters in a single query
- Support all relation types (1:N, N:1, 1:1, M:N)
0.2.3 - 2025-12-19 #
Added #
-
NULLS LAST/FIRST Ordering - Extended
orderBysyntax for null positioning- PostgreSQL and Supabase: Full support for
NULLS LASTandNULLS FIRST - MySQL/SQLite: Gracefully ignored (not supported by these databases)
- Backward compatible - simple
{'field': 'desc'}syntax still works
- PostgreSQL and Supabase: Full support for
-
Relation Filter Helpers - New
FilterOperatorsfor filtering on relationsFilterOperators.some(where)- At least one related record matchesFilterOperators.every(where)- All related records matchFilterOperators.noneMatch(where)- No related records matchFilterOperators.isEmpty()- Relation has no recordsFilterOperators.isNotEmpty()- Relation has at least one record- Note: These helpers generate the correct JSON structure. SQL compilation added in v0.2.4.
Example Usage #
// NULLS LAST/FIRST ordering
final query = JsonQueryBuilder()
.model('Product')
.action(QueryAction.findMany)
.orderBy({
'price': {'sort': 'asc', 'nulls': 'last'}, // Extended syntax
'createdAt': 'desc', // Simple syntax still works
})
.build();
// Relation filter helpers (SQL compilation added in v0.2.4)
final where = {
'reviews': FilterOperators.some({
'rating': FilterOperators.gte(4),
'verified': true,
}),
'tags': FilterOperators.isEmpty(),
};
0.2.2 - 2025-12-19 #
Added #
- Case-Insensitive Search - New
mode: 'insensitive'option for string filtersFilterOperators.containsInsensitive(value)- Case-insensitive LIKE searchFilterOperators.startsWithInsensitive(value)- Case-insensitive prefix searchFilterOperators.endsWithInsensitive(value)- Case-insensitive suffix search- Generates
ILIKEfor PostgreSQL/Supabase (case-insensitive) - Falls back to
LIKEfor MySQL/SQLite (they are already case-insensitive by default) - Backward compatible - existing
contains(),startsWith(),endsWith()unchanged
Example Usage #
// Case-insensitive search
final query = JsonQueryBuilder()
.model('User')
.action(QueryAction.findMany)
.where({
'name': FilterOperators.containsInsensitive('john'), // Matches "John", "JOHN", "john"
})
.build();
0.2.1 - 2025-12-19 #
Fixed #
createManytype cast error - Fixed bug where_compileCreateManyQueryfailed withtype '_Map<String, dynamic>' is not a subtype of type 'List<dynamic>?'. The SQL compiler now properly unwraps nested{'data': [...]}format generated by delegate methods.
0.2.0 - 2025-12-19 #
Added - Production-Grade Features #
Exception System
PrismaException- Base sealed class for all connector exceptionsUniqueConstraintException- Duplicate key violations (code: P2002)ForeignKeyException- Reference constraint violations (code: P2003)RecordNotFoundException- Record not found (code: P2025)QueryTimeoutException- Query execution timeout (code: P5008)InternalException- General database errors (code: P5000)
Query Logging
QueryLogger- Abstract interface for query loggingConsoleQueryLogger- Simple console output loggerMetricsQueryLogger- Tracks query metrics (count, avg/min/max duration)- Events:
onQueryStart,onQueryEnd,onQueryError
Raw SQL API
executeRaw(sql, params)- Execute raw SELECT queriesexecuteMutationRaw(sql, params)- Execute raw INSERT/UPDATE/DELETE- Parameterized queries with type inference
- Full logging integration
Aggregations
QueryAction.count- Count records matching filterQueryAction.aggregate- Planned for future (_avg, _sum, _min, _max)
Upsert Operation
QueryAction.upsert- Insert or update based on conflict- PostgreSQL:
ON CONFLICT DO UPDATE ... RETURNING * - SQLite:
ON CONFLICT DO UPDATE ... RETURNING *(requires SQLite 3.35.0+) - MySQL:
ON DUPLICATE KEY UPDATE(see Known Limitations)
Relations with JOINs
includeoption for eager loading related dataSchemaRegistry- Stores relation metadata from Prisma schemaRelationCompiler- Generates LEFT JOIN clauses- Automatic result nesting (flat rows → nested objects)
- Falls back to N+1 queries if relations not configured
Known Limitations #
MySQL Upsert
MySQL's ON DUPLICATE KEY UPDATE does not support the RETURNING clause. Unlike PostgreSQL and SQLite 3.35+, MySQL upsert operations return the affected row count instead of the actual record. If you need the upserted record, perform a follow-up SELECT query:
// MySQL workaround for upsert
final result = await executor.executeQueryAsSingleMap(upsertQuery);
if (result == null || result.isEmpty) {
// Fetch the record manually
final selectQuery = JsonQueryBuilder()
.model('User')
.action(QueryAction.findUnique)
.where({'email': email})
.build();
return executor.executeQueryAsSingleMap(selectQuery);
}
return result;
Breaking Changes #
- None - fully backward compatible with v0.1.x
0.1.8 - 2025-12-18 #
Fixed #
-
UPDATE/CREATE RETURNING Clause - PostgreSQL and Supabase queries now include
RETURNING *- UPDATE queries previously returned no data, causing "Failed to update" errors
- Both CREATE and UPDATE now return the affected row for PostgreSQL/Supabase providers
- Enables proper response handling in upsert and update operations
-
DateTime Type Inference - Strict ISO 8601 date detection prevents misidentification
- Previously, phone numbers like "9876543210" were incorrectly detected as dates
- Now requires: dash separator, 4-digit year prefix, and reasonable year range (1000-9999)
- Fixes data corruption where phone numbers were stored as garbage date values
Verified #
- Comprehensive end-to-end testing with complex consultant onboarding:
- ✅ String fields (name, bio, description, URLs)
- ✅ Numeric fields (experience: 15.5 as double)
- ✅ DateTime fields (dateOfBirth)
- ✅ Enum fields (gender, scheduleType, sessionTypes)
- ✅ Array fields (languages with 4 items, toolsAndTechnologies with 20 items)
- ✅ Foreign key relations (Domain)
- ✅ Many-to-many relations (SubDomains via join table)
- ✅ Transaction support (atomic operations)
0.1.7 - 2025-12-18 #
Added #
- Server Mode - New
--serverflag for code generation- Use
--serverwhen generating for pure Dart servers (Dart Frog, Shelf, etc.) - Imports
runtime_server.dartinstead ofruntime.dart(no Flutter/sqflite dependencies) - Example:
dart run prisma_flutter_connector:generate --server --schema=... --output=...
- Use
0.1.6 - 2025-12-18 #
Fixed #
- SortOrder Enum Duplication - Moved
SortOrderenum to sharedfilters.dartinstead of generating it in every model file- Previously caused "ambiguous export" errors when re-exporting all models from index.dart
- Now defined once in filters.dart and imported by all model files
- @Default + required Conflict - Fields with
@Defaultannotation are no longer marked asrequired- Fixes "Required named parameters can't have a default value" errors in Freezed-generated code
- Applies to both main model classes and CreateInput types
- Transaction Executor Type Mismatch - Added
BaseExecutorabstract interface- Allows delegates to work with both
QueryExecutor(normal ops) andTransactionExecutor(transactions) - Fixes "argument type not assignable" errors when using
$transaction
- Allows delegates to work with both
0.1.5 - 2025-12-18 #
Fixed #
- Enum Comment Stripping - Parser now correctly strips inline comments from enum values
- Prisma Type Conversion - Fixed
Int→int,Float/Decimal→double,Json→Map<String, dynamic>,Bytes→List<int> - Runtime Defaults - Skip
uuid(),now(),autoincrement(),dbgenerated()for@Defaultannotation - Reserved Keywords - Enum values like
classare renamed toclassValueto avoid Dart conflicts - Relation Fields - Excluded from
Create/Updateinput types (handled separately) - Enum Imports - Filter types now properly import all enum definitions
- toSnakeCase Bug - Use
replaceFirstinstead ofsubstring(1)to safely handle edge cases - Const Constructor - Added
consttoConnectionSettingsin Supabase adapter
Changed #
- DRY Refactor - Extracted
toSnakeCase,toCamelCase,toLowerCamelCaseto sharedstring_utils.dart - Performance - Use
const Setinstead ofListforruntimeFunctionslookup (O(1) vs O(n))
0.1.4 - 2025-12-16 #
Fixed #
- PostgreSQL Enum Type Handling - Fixed
UndecodedByteserror when querying tables with enum columns- PostgreSQL custom types (enums like
UserRole,Gender, etc.) are now properly decoded to strings - Added
_convertValuehelper in PostgresAdapter to handleUndecodedBytesfrom the postgres package
- PostgreSQL custom types (enums like
0.1.3 - 2025-12-16 #
Added #
- Server Runtime Support - New
runtime_server.dartentry point for pure Dart server environments- Use
import 'package:prisma_flutter_connector/runtime_server.dart'in Dart Frog, Shelf, or other server frameworks - Exports only PostgreSQL and Supabase adapters (no Flutter/sqflite dependencies)
- Fixes "dart:ui not available" errors when using the package in server environments
- Use
Why This Matters #
The main runtime.dart exports the SQLite adapter which depends on sqflite (a Flutter plugin).
When imported in pure Dart servers, this caused compilation errors because sqflite imports dart:ui.
Now you can:
- Use
runtime_server.dartfor Dart servers (Dart Frog, Shelf, etc.) - Use
runtime.dartfor Flutter apps (includes SQLite for offline-first mobile)
Usage #
// In Dart Frog backend:
import 'package:prisma_flutter_connector/runtime_server.dart';
// In Flutter app:
import 'package:prisma_flutter_connector/runtime.dart';
0.1.2 - 2025-12-14 #
0.1.1 - 2025-12-14 #
Added #
- Automated publishing via GitHub Actions (OIDC authentication)
- pub.flutter-io.cn package metadata (topics, homepage, repository)
Changed #
- Renamed
docs/todoc/(pub.flutter-io.cn convention) - Renamed
examples/toexample/(pub.flutter-io.cn convention) - Renamed
Readme.mdtoREADME.md(pub.flutter-io.cn convention)
Removed #
- Removed prisma-submodule (not needed for package users)
0.1.0 - 2025-12-14 #
🎉 MAJOR: Architecture Transformation - True Prisma-Style ORM for Dart #
This release represents a revolutionary transformation from a GraphQL client generator to a true Prisma-style ORM for Dart/Flutter - enabling direct database access similar to how Prisma works in TypeScript/Next.js!
✨ Added - Direct Database Access #
Database Adapter System
SqlDriverAdapterinterface - Database-agnostic query executionPostgresAdapter- Direct PostgreSQL connection (postgrespackage)SupabaseAdapter- Direct Supabase connection (no backend!)SQLiteAdapter- Mobile offline-first support (sqflite)- Full transaction support with ACID guarantees
- Connection pooling and type conversion
Query System
- JSON Protocol - Prisma's query protocol in pure Dart
- SQL Compiler - Converts JSON queries → Parameterized SQL
- Query Executor - Runtime execution with type-safe results
- Filter Operators - WHERE clauses (equals, in, contains, lt, gt, etc.)
✅ Validated with Real Database #
All CRUD operations tested and working with Supabase:
- ✅ CREATE - Insert with UUID generation
- ✅ READ - findMany, findUnique with complex filters
- ✅ UPDATE - Modify records
- ✅ DELETE - Remove records
- ✅ COUNT - Aggregate queries
- ✅ FILTER - Complex WHERE with AND/OR/NOT
- ✅ Transactions - Atomic operations with rollback
🚀 Key Benefits #
- No Backend Required - Connect directly from Dart to databases
- Offline-First - SQLite adapter for mobile apps
- Type-Safe - Parameterized queries with full type conversion
- Database-Agnostic - Swap adapters without code changes
- Better Performance - No HTTP/GraphQL overhead
- Familiar DX - Same API as Prisma in TypeScript
📦 New Dependencies #
dependencies:
postgres: ^3.0.0 # PostgreSQL support
sqflite: ^2.3.0 # Mobile SQLite support
supabase_flutter: ^2.5.0 # Supabase integration
📁 New Files #
Runtime Library:
lib/runtime.dart- Main runtime exportlib/src/runtime/adapters/types.dart- Core typeslib/src/runtime/adapters/postgres_adapter.dartlib/src/runtime/adapters/supabase_adapter.dartlib/src/runtime/adapters/sqlite_adapter.dartlib/src/runtime/query/json_protocol.dartlib/src/runtime/query/sql_compiler.dartlib/src/runtime/query/query_executor.dart
Examples & Tests:
test/validation/crud_validation.dart- Full CRUD validationexample/adapter_example.dart- Usage examples
💻 Usage Example #
import 'package:prisma_flutter_connector/runtime.dart';
import 'package:postgres/postgres.dart' as pg;
// Connect to database
final connection = await pg.Connection.open(
pg.Endpoint(host: 'localhost', database: 'mydb'),
);
final adapter = PostgresAdapter(connection);
final executor = QueryExecutor(adapter: adapter);
// Build query
final query = JsonQueryBuilder()
.model('User')
.action(QueryAction.findMany)
.where({'email': FilterOperators.contains('@example.com')})
.orderBy({'createdAt': 'desc'})
.build();
// Execute
final users = await executor.executeQueryAsMaps(query);
print('Found ${users.length} users');
🗺️ Roadmap #
Phase 3: Code Generation (Next)
- Update generator to produce adapter-based client
- Type-safe generated client from Prisma schema
- Auto-generated CRUD methods per model
Phase 4: Advanced Features
- Relation loading (include, select)
- Nested writes
- Aggregations (avg, sum, min, max)
- Raw SQL queries
Phase 5: Publication
- pub.flutter-io.cn release
- Comprehensive documentation
- Example applications
0.1.0 - 2025-11-01 #
Added #
- Initial release of Prisma Flutter Connector
- GraphQL client integration using Ferry
- Type-safe models with Freezed
- E-commerce example (Product, Order, User models)
- Basic CRUD operations (queries and mutations)
- Error handling with custom exceptions
- Backend example using Prisma + Pothos + Apollo Server
- Comprehensive documentation
Architecture #
- GraphQL API protocol (chosen over REST for better Prisma integration)
- Pothos for GraphQL schema generation from Prisma
- Ferry for type-safe Dart code generation
- No offline caching in v0.1.0 (planned for v0.2.0)