generateSchema function
- String sourcePath, {
- String? outputPath,
- SqlDialect? dialect,
- String? defaultNamespace,
Analyzes one file or a definition directory without writing files.
Directories are discovered recursively and their names never determine
physical namespaces. An annotation's explicit namespace overrides
defaultNamespace, then PostgreSQL defaults to public. Other engines reject
explicit namespaces. A model source must not import generated clients.
Selected roots and libraries owning models found through exports or relations
and libraries owning applied mixins must have neither part nor part of
directives, matching build_runner.
A sibling {root}.dart can contribute models or exports. 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. Neither output may overwrite a selected source or its
transitive imports and exports, including filesystem aliases. 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,
String? defaultNamespace,
}) async {
final input = p.normalize(p.absolute(sourcePath));
final root = SchemaLayout.stem(input);
final layout = SchemaLayout(input, directory: await Directory(root).exists());
final requestedOutput = p.absolute(outputPath ?? layout.output);
final output = p.normalize(requestedOutput);
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, recursive: true)) {
if (entry is File && SchemaLayout.declaration(entry.path)) {
sources.add(entry.path);
}
}
sources.sort();
}
if (sources.isEmpty) {
throw GenerationException('No schema files found for $sourcePath.');
}
if (p.extension(output) != '.dart') {
throw const GenerationException(
'Client and snapshot outputs must be separate Dart files from the source.',
);
}
final contexts = AnalysisContextCollection(includedPaths: [p.dirname(root)]);
try {
void validateResolvedUnit(ResolvedUnitResult result) {
validateModelLibrary(result.unit, source: Uri.file(result.path));
final errors = result.diagnostics.where(
(e) => e.severity.name.toLowerCase() == 'error',
);
if (errors.isNotEmpty) throw GenerationException(errors.join('\n'));
}
Future<ResolvedUnitResult> resolvePath(String path) async {
final result = await contexts
.contextFor(path)
.currentSession
.getResolvedUnit(path);
if (result is! ResolvedUnitResult) {
throw GenerationException('Cannot analyze $path.');
}
validateResolvedUnit(result);
return result;
}
final roots = <ResolvedUnitResult>[];
for (final path in sources) {
roots.add(await resolvePath(path));
}
validateSchemaOutputs(roots.map((result) => result.libraryElement), {
output,
_schemaSnapshotPath(output),
requestedOutput,
_schemaSnapshotPath(requestedOutput),
});
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,
resolve: (library) async {
// Imported model/mixin libraries can live outside the selected roots.
// Keep their original session and identity rather than rediscovering
// them through the root-only analysis context collection.
final result = await library.session.getResolvedLibraryByElement(
library,
);
final path = library.firstFragment.source.fullName;
final unit = result is ResolvedLibraryResult
? result.unitWithPath(path)
: null;
if (unit == null) throw GenerationException('Cannot analyze $path.');
validateResolvedUnit(unit);
return unit.unit;
},
additionalRoots: [for (final result in roots.skip(1)) result.unit],
dialect: dialect,
defaultNamespace: defaultNamespace,
);
} finally {
await contexts.dispose();
}
}