prisma_flutter_connector 0.3.2
prisma_flutter_connector: ^0.3.2 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.3.2 - 2025-12-29 #
Fixed #
Nested Include JOINs Bug
- Fixed nested
include()generating invalid SQL - "missing FROM-clause entry for table" error- When using nested includes like
.include({'relation': {'include': {'nestedRelation': true}}}) - The nested JOIN clauses were not being added to the SQL output
- This caused errors like
missing FROM-clause entry for table "t2"because columns from the nested relation were selected but the table was never joined - Fixed
RelationCompiler._compileRelation()to collect and combine nested JOIN clauses with the parent JOIN
- When using nested includes like
Example
// This now works correctly:
final query = JsonQueryBuilder()
.model('ConsultationPlan')
.action(QueryAction.findUnique)
.where({'id': planId})
.include({
'consultantProfile': {
'include': {'user': true} // ✅ Nested include now generates correct JOINs
}
})
.build();
Deeply Nested Relation Filters Validation (Issue #13)
-
Added validation for invalid relation filter patterns
- Relation fields used without
some(),every(), ornone()operators now throw clear errors - Unknown filter operators on scalar fields are now detected with helpful error messages
- Previously, these patterns would silently generate invalid SQL
- Relation fields used without
-
Error messages now guide users to the correct syntax
- Suggests using
FilterOperators.some(),every(), ornone()for relation fields - Lists valid scalar operators when an unknown operator is detected
- Mentions
FilterOperators.relationPath()for complex OR conditions across relations
- Suggests using
Example #
// This invalid pattern now throws a helpful error:
.where({
'posts': { // ❌ Relation field without operator
'title': {'equals': 'Test'},
},
})
// Error: Relation field "posts" on model "User" requires a filter operator.
// Use FilterOperators.some(), every(), or none().
// Correct usage:
.where({
'posts': FilterOperators.some({ // ✅ Using some() operator
'title': {'equals': 'Test'},
}),
})
0.3.1 - 2025-12-28 #
Fixed #
Computed Fields with Relations Bug
- Fixed computed fields returning
nullwhen used withinclude()- Computed fields (e.g.,
ComputedField.min(),ComputedField.max()) now correctly return values when combined with relation includes - Previously, computed fields were dropped during relation deserialization because they weren't tracked in
columnAliases - Added
computedFieldNamestoSqlQueryto track computed field names - After relation deserialization, computed fields are now copied back from the flat result maps
- Computed fields (e.g.,
Example #
// This now works correctly:
final consultants = await prisma.consultant.findMany(
include: {
'user': {'select': {'name': true, 'image': true}},
'domain': true,
},
computed: {
'minPrice': ComputedField.min('price', from: 'ConsultationPlan',
where: {'consultantProfileId': FieldRef('id')}),
},
);
// consultants[0]['minPrice'] now returns the correct value instead of null
0.3.0 - 2025-12-25 #
Added #
Many-to-Many Relation Mutations (Connect/Disconnect)
-
compileWithRelations()- Compile mutations with M2M relation operations- Automatically extracts
connectanddisconnectfrom data - Generates junction table INSERT/DELETE statements
- Works with
createandupdateoperations
- Automatically extracts
-
executeMutationWithRelations()- Execute mutations with M2M support- Executes main mutation first, then relation mutations
- Supports non-atomic execution for performance
-
executeMutationWithRelationsAtomic()- Atomic M2M mutations- Wraps all operations in a transaction
- Rolls back if any mutation fails
CompiledMutation Type
- New
CompiledMutationclass for structured mutation resultsmainQuery- The primary INSERT/UPDATE queryrelationMutations- List of junction table operationshasRelationMutations- Helper to check if M2M operations exist
Provider-Specific Connect Syntax
- PostgreSQL/Supabase:
INSERT ... ON CONFLICT DO NOTHING - MySQL:
INSERT IGNORE INTO ... - SQLite:
INSERT OR IGNORE INTO ...
Example Usage #
// Create with M2M connect
final result = await executor.executeMutationWithRelations(
JsonQueryBuilder()
.model('SlotOfAppointment')
.action(QueryAction.create)
.data({
'id': 'slot-123',
'startsAt': DateTime.now(),
'users': {
'connect': [{'id': 'user-1'}, {'id': 'user-2'}],
},
})
.build(),
);
// Update with connect/disconnect
final result = await executor.executeMutationWithRelationsAtomic(
JsonQueryBuilder()
.model('SlotOfAppointment')
.action(QueryAction.update)
.where({'id': 'slot-123'})
.data({
'users': {
'connect': [{'id': 'user-new'}],
'disconnect': [{'id': 'user-old'}],
},
})
.build(),
);
0.2.9 - 2025-12-23 #
Added #
DISTINCT Support
distinct()- Select unique rows withSELECT DISTINCT- Standard DISTINCT:
.distinct()→SELECT DISTINCT * - PostgreSQL DISTINCT ON:
.distinct(['email'])→SELECT DISTINCT ON ("email") * - Works with
selectFields()for specific column deduplication
- Standard DISTINCT:
NULL-Coalescing Filter Operators
New operators for handling NULL values in complex queries (especially with LEFT JOINs):
FilterOperators.isNull()- Check if column is NULLFilterOperators.isNotNull()- Check if column is NOT NULLFilterOperators.notInOrNull(values)-NOT IN (...) OR IS NULLpatternFilterOperators.inOrNull(values)-IN (...) OR IS NULLpatternFilterOperators.equalsOrNull(value)-= value OR IS NULLpattern
Deep Relation Path Filtering
FilterOperators.relationPath(path, where)- Filter through nested relations- Generates efficient EXISTS subqueries with chained JOINs
- Supports arbitrary nesting depth (e.g.,
appointment.consultation.consultationPlan) - Works with OR conditions for multiple relation paths
.where({
'OR': [
FilterOperators.relationPath(
'appointment.consultation.consultationPlan',
{'consultantProfileId': profileId},
),
FilterOperators.relationPath(
'appointment.subscription.subscriptionPlan',
{'consultantProfileId': profileId},
),
],
})
Explicit JOIN Type Control
includeRequired()- Use INNER JOIN instead of LEFT JOIN for required relations_joinType: 'inner'- Inline JOIN type specification ininclude()
// Method 1: Separate method
.includeRequired({'appointment': true}) // INNER JOIN
.include({'consultation': true}) // LEFT JOIN (default)
// Method 2: Inline specification
.include({
'appointment': {'_joinType': 'inner'},
'consultation': {'_joinType': 'left'},
})
Use Case #
These features enable complex multi-table queries that previously required raw SQL:
// Before v0.2.9: Raw SQL required
final sql = '''
SELECT DISTINCT soa."startsAt", soa."endsAt"
FROM "SlotOfAppointment" soa
INNER JOIN "Appointment" a ON soa."appointmentId" = a.id
LEFT JOIN "Consultation" c ON a."consultationId" = c.id
LEFT JOIN "ConsultationPlan" cp ON c."consultationPlanId" = cp.id
WHERE (cp."consultantProfileId" = $1 OR sp."consultantProfileId" = $1)
AND (c."requestStatus" NOT IN ('CANCELLED', 'REJECTED') OR c."requestStatus" IS NULL)
''';
// After v0.2.9: Full ORM support
final query = JsonQueryBuilder()
.model('SlotOfAppointment')
.action(QueryAction.findMany)
.distinct()
.selectFields(['startsAt', 'endsAt', 'isTentative'])
.includeRequired({'appointment': true})
.where({
'OR': [
FilterOperators.relationPath(
'appointment.consultation.consultationPlan',
{'consultantProfileId': profileId},
),
FilterOperators.relationPath(
'appointment.subscription.subscriptionPlan',
{'consultantProfileId': profileId},
),
],
'startsAt': FilterOperators.gte(startDate.toIso8601String()),
})
.build();
Known Limitations #
relationPathdoes not support many-to-many relations - Paths containing many-to-many relations will be silently ignored. This is because many-to-many relations require joining through a junction table, which adds significant complexity. Many-to-many support is planned for v0.3.0. For now, use the existingsome/every/noneoperators or raw SQL.
Notes #
- All features are backward compatible - no breaking changes
- DISTINCT ON is PostgreSQL/Supabase specific; other databases use standard DISTINCT
- Relation path filtering requires
SchemaRegistryto resolve relation metadata
0.2.8 - 2025-12-21 #
Fixed #
- @@map directive support - Model names now correctly resolve to database table names when using
JsonQueryBuilderdirectly SqlCompilernow consultsSchemaRegistryto resolve model-to-table mappings via@@mapdirectives
Added #
SqlCompiler._resolveTableName()helper method for transparent model-to-table name resolution- Comprehensive test suite for
@@mapdirective support
Notes #
- Backward compatible: If no
SchemaRegistryis provided or model is not registered, model names are used as-is - Generated delegates (from code generation) continue to work as before
0.2.7 - 2025-12-20 #
Fixed #
- Dependency compatibility - Upgraded
freezedto ^3.0.6 to resolve version conflicts withtestandflutter_testpackages - Lint compliance - Added
constconstructors where applicable and fixed package imports - Code formatting - Applied consistent dart format across all source files
Notes #
- No functional changes from v0.2.6
- This release ensures CI compatibility with Flutter 3.27.x
0.2.6 - 2025-12-19 #
Added #
- Computed Fields (Correlated Subqueries) - Add computed fields via correlated subqueries in SELECT
ComputedField.min()- MIN aggregate subqueryComputedField.max()- MAX aggregate subqueryComputedField.avg()- AVG aggregate subqueryComputedField.sum()- SUM aggregate subqueryComputedField.count()- COUNT aggregate subquery (accepts optionalfieldparameter)ComputedField.first()- Fetch first matching value with ORDER BYFieldRefclass for referencing parent table columns in subqueries
Fixed #
- Alias conflict with include + computed - Fixed "table name 't0' specified more than once" error
- Missing relation columns in SELECT - Relations now correctly included when using
include()with computed fields - Ambiguous column names with JOINs - Added table alias prefix to WHERE and ORDER BY clauses
- Aggregate FILTER parameter numbering - Fixed "could not determine data type of parameter" error
- Computed field WHERE clause parameterization - Improved security with parameterized queries
- selectFields respects dot notation - Only explicitly requested relation columns are fetched
0.2.5 - 2025-12-19 #
Added #
- Select Specific Fields - New
selectFields()method to select specific columns instead ofSELECT * - FILTER Clause for Aggregations (PostgreSQL/Supabase) - Conditional COUNT with
_countFiltered - Include with Select - Select specific fields from included relations
0.2.4 - 2025-12-19 #
Added #
- Relation Filtering SQL Compilation - Compile
some/every/noneoperators to EXISTS subqueries - Relation Filter Operators -
some,none,every,isEmpty(),isNotEmpty()
0.2.3 - 2025-12-19 #
Added #
- NULLS LAST/FIRST Ordering - Extended
orderBysyntax for null positioning - Relation Filter Helpers - New
FilterOperatorsfor filtering on relations
0.2.2 - 2025-12-19 #
Added #
- Case-Insensitive Search -
containsInsensitive(),startsWithInsensitive(),endsWithInsensitive()
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)