Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ All notable changes to the Prisma Flutter Connector.

## [Unreleased]

## [0.5.5] - 2026-04-04

### Fixed

#### Parser: Brace-counting for model body extraction
- **Fixed inline comments with `{` or `}` truncating model parsing** — e.g., `preferences Json? // e.g., { preferredDates: [] }` caused everything after the `}` in the comment to be lost (userId, webinarId, classId fields dropped from Waitlist model)
- Replaced `[^}]+` regex with brace-counting `_extractBlocks()` method for both model and enum parsing

#### Parser: Implicit relation detection
- **Fixed fields referencing other models not being marked as relations** when they lack an explicit `@relation` attribute (e.g., `subDomains SubDomain[]` on Domain)
- Parser now checks if `fieldType` is a known model name in addition to checking for `@relation`

#### Schema Registry: Correct FK for multi-relation models
- **Fixed wrong foreign key when a model has multiple relations to the same target** (e.g., ModerationReport has both `reportedBy` and `targetUser` pointing to User)
- Now uses the field's own `@relation(fields: [...])` first instead of picking the first matching relation on the model

## [0.5.4] - 2026-03-28

### Fixed
Expand Down
5 changes: 3 additions & 2 deletions lib/src/generator/cb_schema_registry_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,9 @@ class CbSchemaRegistryGenerator {
));
}
} else {
final fk = _findForeignKeyOnModel(model, target) ??
_findForeignKeyFromField(field);
// Use this field's own @relation(fields: [...]) first, then fall back to convention
final fk = _findForeignKeyFromField(field) ??
_findForeignKeyOnModel(model, target);
Comment on lines +132 to +133

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The call to _findForeignKeyOnModel is currently unreachable because _findForeignKeyFromField always returns a non-null string (it falls back to a default convention if no explicit relation is found). This logic should be restructured to prioritize explicit relations on the field, then check for explicit relations elsewhere in the model, and finally fall back to the naming convention.

        final fk = (field.relationFromFields?.isNotEmpty == true)
            ? field.relationFromFields!.first
            : (_findForeignKeyOnModel(model, target) ??
                '${toLowerCamelCase(field.type)}Id');

if (fk == null) continue;

final backRef = targetModel.fields
Expand Down
63 changes: 47 additions & 16 deletions lib/src/generator/prisma_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,11 @@ class PrismaParser {
datasourceProvider = datasourceMatch.group(1)!;
}

// Extract enums
final enumPattern = RegExp(r'enum\s+(\w+)\s*\{([^}]+)\}', multiLine: true);
for (final match in enumPattern.allMatches(schemaContent)) {
final name = match.group(1)!;
final body = match.group(2)!;
// Extract enums (use brace-counting to handle comments with braces)
final enumBlocks = _extractBlocks(schemaContent, 'enum');
for (final block in enumBlocks) {
final name = block.name;
final body = block.body;
final values = body
.split('\n')
.map((line) {
Expand All @@ -447,18 +447,16 @@ class PrismaParser {
final modelNameMap = <String, String>{}; // originalName -> dartName

// First pass: collect all model name mappings
final modelPattern =
RegExp(r'model\s+(\w+)\s*\{([^}]+)\}', multiLine: true);
for (final match in modelPattern.allMatches(schemaContent)) {
final originalModelName = match.group(1)!;
final modelResult = _handleReservedKeyword(originalModelName, 'model');
modelNameMap[originalModelName] = modelResult.dartName;
final modelBlocks = _extractBlocks(schemaContent, 'model');
for (final block in modelBlocks) {
final modelResult = _handleReservedKeyword(block.name, 'model');
modelNameMap[block.name] = modelResult.dartName;
}

// Second pass: parse models with resolved type names
for (final match in modelPattern.allMatches(schemaContent)) {
final originalModelName = match.group(1)!;
final modelBody = match.group(2)!;
for (final block in modelBlocks) {
final originalModelName = block.name;
final modelBody = block.body;

// Handle reserved keywords - auto-rename if needed
final modelResult = _handleReservedKeyword(originalModelName, 'model');
Expand Down Expand Up @@ -527,8 +525,11 @@ class PrismaParser {
}
}

// Check if it's a relation
final isRelation = attributes.contains('@relation');
// Check if it's a relation — explicit (@relation) or implicit (type is a model)
final baseFieldType =
fieldType.replaceAll('?', '').replaceAll('[]', '');
Comment on lines +529 to +530

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The replaceAll calls here are redundant because fieldType has already been stripped of ? and [] suffixes earlier in the parsing loop (lines 502 and 508).

          final baseFieldType = fieldType;

final isRelation = attributes.contains('@relation') ||
modelNameMap.containsKey(baseFieldType);
String? relationName;
List<String>? relationFromFields;
List<String>? relationToFields;
Expand Down Expand Up @@ -637,6 +638,29 @@ class PrismaParser {
);
}

/// Extract model/enum blocks using brace counting instead of [^}] regex.
///
/// Handles inline comments containing `{` or `}` which break simple regex.
List<_Block> _extractBlocks(String content, String keyword) {
final blocks = <_Block>[];
final pattern = RegExp('$keyword\\s+(\\w+)\\s*\\{');
for (final match in pattern.allMatches(content)) {
final name = match.group(1)!;
final start = match.end; // position after the opening {
var depth = 1;
var i = start;
while (i < content.length && depth > 0) {
final ch = content[i];
if (ch == '{') depth++;
if (ch == '}') depth--;
i++;
}
// i is now past the closing }, body is between start and i-1
blocks.add(_Block(name: name, body: content.substring(start, i - 1)));
Comment on lines +652 to +659

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The brace-counting logic is still susceptible to the same truncation issue if a comment contains an unbalanced closing brace (e.g., // }). Since the primary goal of this PR is to handle braces in comments, the extraction loop should explicitly skip characters within comments while tracking the block depth. Additionally, it's safer to verify that the block was correctly closed (depth == 0) before adding it to the list.

      while (i < content.length && depth > 0) {
        final ch = content[i];
        // Skip inline comments to avoid false depth triggers from braces in comments
        if (ch == '/' && i + 1 < content.length && content[i + 1] == '/') {
          while (i < content.length && content[i] != '\n') i++;
          continue;
        }
        if (ch == '{') depth++;
        if (ch == '}') depth--;
        i++;
      }
      if (depth == 0) {
        blocks.add(_Block(name: name, body: content.substring(start, i - 1)));
      }

}
return blocks;
}

/// Extracts content inside @default(...) handling nested parentheses.
///
/// For example:
Expand Down Expand Up @@ -670,3 +694,10 @@ class PrismaParser {
return attributes.substring(contentStart, i - 1);
}
}

/// A named block extracted from a Prisma schema (model or enum).
class _Block {
final String name;
final String body;
const _Block({required this.name, required this.body});
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: >-
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.
version: 0.5.4
version: 0.5.5
homepage: https://github.com/teetangh/prisma-flutter-connector
repository: https://github.com/teetangh/prisma-flutter-connector
issue_tracker: https://github.com/teetangh/prisma-flutter-connector/issues
Expand Down
Loading