readSnapshot function

Future<SchemaSnapshot> readSnapshot(
  1. String sourcePath
)

Reads a constant frozen schema without executing the Dart input file.

Accepts the generated frozenSchema or one unambiguous top-level constant SchemaSnapshot, including a private constant in a historical migration. The file must be an independent library. Only literal scalar defaults are supported, matching the model generator; application code is never loaded.

Implementation

Future<SchemaSnapshot> readSnapshot(String sourcePath) async {
  final file = p.normalize(p.absolute(sourcePath));
  final collection = AnalysisContextCollection(
    includedPaths: [file],
    sdkPath: p.dirname(p.dirname(Platform.resolvedExecutable)),
  );
  try {
    final result = await collection
        .contextFor(file)
        .currentSession
        .getResolvedLibrary(file);
    if (result is! ResolvedLibraryResult || result.units.length != 1) {
      throw FormatException('$file: expected one independent Dart library.');
    }
    final errors = result.units.single.diagnostics.where(
      (error) => error.severity.name == 'error',
    );
    if (errors.isNotEmpty) {
      throw FormatException('$file: ${errors.join('\n')}');
    }
    final snapshots = result.element.topLevelVariables
        .where((variable) => variable.isConst)
        .map((variable) => variable.computeConstantValue())
        .whereType<DartObject>()
        .where((value) => _isSchemaType(value, 'SchemaSnapshot'))
        .toList();
    final preferred = snapshots.where(
      (value) => value.variable?.name == 'frozenSchema',
    );
    final snapshot = preferred.singleOrNull ?? snapshots.singleOrNull;
    if (snapshot == null) {
      throw FormatException(
        '$file: declare const frozenSchema, or exactly one const SchemaSnapshot.',
      );
    }
    final schema = SchemaSnapshot(
      engine: Engine.values[_enumIndex(_field(snapshot, 'engine'))],
      tables: [
        for (final table in _field(snapshot, 'tables').toListValue()!)
          TableDefinition(_field(table, 'name').toStringValue()!, [
            for (final column in _field(table, 'columns').toListValue()!)
              ColumnDefinition(
                name: _field(column, 'name').toStringValue()!,
                field: _field(column, 'field').toStringValue()!,
                type: ScalarType.values[_enumIndex(_field(column, 'type'))],
                nullable: _field(column, 'nullable').toBoolValue()!,
                primaryKey: _field(column, 'primaryKey').toBoolValue()!,
                identity: _field(column, 'identity').toBoolValue()!,
                unique: _field(column, 'unique').toBoolValue()!,
                defaultValue: _literalDefault(_field(column, 'defaultValue')),
                references: _reference(_field(column, 'references')),
              ),
          ]),
      ],
    );
    // Validates cross-table constraints and freezes the analyzed collections.
    return MigrationPlan(
      engine: schema.engine,
      steps: const [],
      snapshot: schema,
    ).snapshot;
  } finally {
    await collection.dispose();
  }
}