generateSchema function

Future<GeneratedSchema> generateSchema(
  1. String sourcePath, {
  2. String? outputPath,
  3. SqlDialect? dialect,
})

Analyzes one file or a definition directory without writing files.

dialect selects the directory layout and physical namespace rules. PostgreSQL uses {root}/{schema}/*.dart; other engines use {root}/*.dart. A sibling {root}.dart contributes to the default namespace. Discovery is not recursive. PostgreSQL generation records public explicitly for default models. Single-file generation without a dialect retains engine-neutral metadata.

outputPath determines relative imports and defaults to the source basename with an .orm.dart extension. Invalid declarations or output collisions throw GenerationException. Directory outputs must not be discovered as schema inputs on later runs. Application default factories are never executed.

Implementation

Future<GeneratedSchema> generateSchema(
  String sourcePath, {
  String? outputPath,
  SqlDialect? dialect,
}) async {
  final input = p.normalize(p.absolute(sourcePath));
  final root = SchemaLayout.stem(input);
  final layout = SchemaLayout(
    input,
    dialect: dialect,
    directory: await Directory(root).exists(),
  );
  final output = p.normalize(p.absolute(outputPath ?? layout.output));
  if (layout.directory &&
      SchemaLayout.declaration(output) &&
      (layout.includes(output) || p.dirname(output) == root)) {
    throw const GenerationException(
      'Generated outputs must not become schema inputs. '
      'Choose a path outside the schema layout or use an .orm.dart filename.',
    );
  }
  final sources = <String>[];
  if (await File(layout.file).exists()) sources.add(layout.file);
  if (layout.directory) {
    await for (final entry in Directory(root).list(followLinks: false)) {
      if (entry is File && SchemaLayout.declaration(entry.path)) {
        if (dialect == SqlDialect.postgres) {
          throw GenerationException(
            'Put PostgreSQL declarations in $root/{schema}/*.dart: ${entry.path}',
          );
        }
        sources.add(entry.path);
      } else if (entry is Directory && dialect == SqlDialect.postgres) {
        await for (final file in entry.list(followLinks: false)) {
          if (file is File && SchemaLayout.declaration(file.path)) {
            sources.add(file.path);
          }
        }
      }
    }
    sources.sort();
  }
  if (sources.isEmpty) {
    throw GenerationException('No schema files found for $sourcePath.');
  }
  if (p.extension(output) != '.dart' ||
      sources.contains(output) ||
      sources.contains(_schemaSnapshotPath(output))) {
    throw const GenerationException(
      'Client and snapshot outputs must be separate Dart files from the source.',
    );
  }
  final contexts = AnalysisContextCollection(includedPaths: [p.dirname(root)]);
  try {
    Future<ResolvedUnitResult> resolvePath(String path) async {
      final result = await contexts
          .contextFor(path)
          .currentSession
          .getResolvedUnit(path);
      if (result is! ResolvedUnitResult) {
        throw GenerationException('Cannot analyze $path.');
      }
      final errors = result.diagnostics.where(
        (e) => e.severity.name.toLowerCase() == 'error',
      );
      if (errors.isNotEmpty) throw GenerationException(errors.join('\n'));
      return result;
    }

    final roots = <ResolvedUnitResult>[];
    for (final path in sources) {
      roots.add(await resolvePath(path));
    }
    String importPath(Uri uri) => uri.scheme == 'file'
        ? p
              .relative(uri.toFilePath(), from: p.dirname(output))
              .replaceAll(r'\', '/')
        : uri.toString();
    final resolved = roots.first;
    return await generateResolvedSchema(
      resolved.unit,
      resolved.libraryElement,
      importPath(Uri.file(sources.first)),
      importPath,
      resolve: (library) async =>
          (await resolvePath(library.firstFragment.source.fullName)).unit,
      additionalRoots: [for (final result in roots.skip(1)) result.unit],
      namespaceOf: (variable) => layout.namespace(
        variable
            .declaredFragment!
            .element
            .library!
            .firstFragment
            .source
            .fullName,
      ),
      stableOrder: layout.directory || dialect != null,
      dialect: dialect,
    );
  } finally {
    await contexts.dispose();
  }
}