generateClient method

String generateClient(
  1. SchemaDocument schema, {
  2. String? schemaSource,
})

Returns the generated client source for schema.

If schemaSource is provided, a // schema-hash: comment is embedded in the file header. The CLI uses this to skip re-writing the output file when the schema hasn't changed (incremental generation, see 5.3).

Implementation

String generateClient(SchemaDocument schema, {String? schemaSource}) {
  final effectiveSchema = schema.withoutIgnored();
  final imports = <String>[
    "import 'package:comon_orm/comon_orm.dart';",
    ...options.additionalImports,
  ];
  final buffer = StringBuffer()
    ..writeln('// Generated code. Do not edit by hand.')
    ..writeln(
      '// ignore_for_file: unused_element, non_constant_identifier_names',
    );
  if (schemaSource != null) {
    final hash = sha256.convert(utf8.encode(schemaSource)).toString();
    buffer.writeln('// schema-hash: $hash');
  }
  buffer
    ..writeln(imports.join('\n'))
    ..writeln();
  if (schemaSource != null) {
    buffer
      ..writeln(
        'const String _comonOrmGeneratedSchemaSource = ${jsonEncode(schemaSource)};',
      )
      ..writeln();
  }
  buffer
    ..writeln()
    ..writeln('class ComonOrm {')
    ..writeln('  ComonOrm({')
    ..writeln('    required DatabaseAdapter adapter,')
    ..writeln(
      '    List<DatabaseMiddleware> middlewares = const <DatabaseMiddleware>[],',
    )
    ..writeln('    QueryLogWriter? logger,')
    ..writeln('  })')
    ..writeln(
      '    : _client = ComonOrmClient(adapter: adapter, schemaView: runtimeSchemaView, middlewares: middlewares, logger: logger);',
    )
    ..writeln()
    ..writeln('  ComonOrm._fromClient(this._client);')
    ..writeln()
    ..writeln(
      '  static const GeneratedRuntimeSchema runtimeSchema = _ComonOrmRuntimeMetadata.schema;',
    )
    ..writeln()
    ..writeln('  static final RuntimeSchemaView runtimeSchemaView =')
    ..writeln('      runtimeSchemaViewFromGeneratedSchema(runtimeSchema);')
    ..writeln()
    ..writeln('  static InMemoryDatabaseAdapter createInMemoryAdapter() {')
    ..writeln('    return InMemoryDatabaseAdapter.fromGeneratedSchema(')
    ..writeln('      schema: runtimeSchema,')
    ..writeln('    );')
    ..writeln('  }')
    ..writeln()
    ..writeln('  factory ComonOrm.inMemory({')
    ..writeln(
      '    List<DatabaseMiddleware> middlewares = const <DatabaseMiddleware>[],',
    )
    ..writeln('    QueryLogWriter? logger,')
    ..writeln('  }) {')
    ..writeln(
      '    return ComonOrm(adapter: createInMemoryAdapter(), middlewares: middlewares, logger: logger);',
    )
    ..writeln('  }')
    ..writeln()
    ..writeln('  static Future<void> _migrateWithAdapter({')
    ..writeln('    required DatabaseAdapter adapter,')
    ..writeln('    required MigrationReader migrations,')
    ..writeln('  }) async {')
    ..writeln('    if (adapter is! MigrationCapableDatabaseAdapter) {')
    ..writeln(
      '      throw StateError(\'Adapter \${adapter.runtimeType} does not support runtime migrations.\');',
    )
    ..writeln('    }')
    ..writeln('    final executor = adapter.createMigrationExecutor();')
    ..writeln('    final artifacts = await migrations.loadAll();')
    ..writeln('    await executor.applyPending(artifacts);')
    ..writeln('  }')
    ..writeln();

  for (final member in options.additionalClassMembers) {
    buffer
      ..writeln(member)
      ..writeln();
  }

  if (options.emitDefaultMigrateMethod) {
    buffer
      ..writeln('  static Future<void> migrate({')
      ..writeln('    required DatabaseAdapter adapter,')
      ..writeln('    required MigrationReader migrations,')
      ..writeln('  }) {')
      ..writeln(
        '    return _migrateWithAdapter(adapter: adapter, migrations: migrations);',
      )
      ..writeln('  }')
      ..writeln();
  }

  buffer.writeln('  final ComonOrmClient _client;');

  buffer
    ..writeln()
    ..writeln('  void reset() {')
    ..writeln('    _client.reset();')
    ..writeln('  }')
    ..writeln()
    ..writeln('  void seed(')
    ..writeln('    Map<String, List<Map<String, Object?>>> data, {')
    ..writeln('    bool replace = false,')
    ..writeln('  }) {')
    ..writeln('    _client.seed(data, replace: replace);')
    ..writeln('  }')
    ..writeln();

  for (final model in effectiveSchema.models) {
    final delegateName = '${model.name}Delegate';
    final propertyName = _lowercaseFirst(model.name);
    buffer.writeln(
      '  late final $delegateName $propertyName = $delegateName._(_client);',
    );
  }

  buffer
    ..writeln()
    ..writeln('  Future<T> transaction<T>(')
    ..writeln('    Future<T> Function(ComonOrm tx) action,')
    ..writeln('  ) {')
    ..writeln(
      '    return _client.transaction((tx) => action(ComonOrm._fromClient(tx)));',
    )
    ..writeln('  }')
    ..writeln()
    ..writeln('  Future<List<T>> batch<T>(')
    ..writeln('    List<Future<T> Function(ComonOrm tx)> operations,')
    ..writeln('  ) {')
    ..writeln('    if (operations.isEmpty) {')
    ..writeln('      return Future<List<T>>.value(<T>[]);')
    ..writeln('    }')
    ..writeln('    return _client.batch<T>(')
    ..writeln('      operations')
    ..writeln('          .map(')
    ..writeln(
      '            (operation) => (ComonOrmClient tx) => operation(ComonOrm._fromClient(tx)),',
    )
    ..writeln('          )')
    ..writeln('          .toList(growable: false),')
    ..writeln('    );')
    ..writeln('  }')
    ..writeln()
    ..writeln('  Future<void> close() async {')
    ..writeln('    await _client.close();')
    ..writeln('  }')
    ..writeln('}')
    ..writeln();

  for (final declaration in options.additionalDeclarations) {
    buffer
      ..writeln(declaration)
      ..writeln();
  }

  _writeGeneratedRuntimeMetadata(buffer, effectiveSchema);

  for (final definition in effectiveSchema.enums) {
    _writeEnumClass(buffer, definition);
  }

  _writeScalarUpdateOperationHelpers(buffer);

  for (final model in effectiveSchema.models) {
    _writeModelClass(buffer, effectiveSchema, model);
  }

  for (final model in effectiveSchema.models) {
    _writeDelegate(buffer, effectiveSchema, model);
    _writeWhereInput(buffer, effectiveSchema, model);
    _writeWhereUniqueInput(buffer, effectiveSchema, model);
    _writeOrderByInput(buffer, effectiveSchema, model);
    _writeScalarFieldEnum(buffer, effectiveSchema, model);
    _writeAggregateInputClasses(buffer, effectiveSchema, model);
    _writeAggregateResultClasses(buffer, effectiveSchema, model);
    _writeGroupBySupportClasses(buffer, effectiveSchema, model);
    _writeInclude(buffer, effectiveSchema, model);
    _writeSelect(buffer, effectiveSchema, model);
    _writeCreateInput(buffer, effectiveSchema, model);
    _writeUpdateInput(buffer, effectiveSchema, model);
    _writeUpsertManyInput(buffer, model);
    _writeCreateWithoutInputs(buffer, effectiveSchema, model);
    _writeConnectOrCreateInputs(buffer, effectiveSchema, model);
    _writeNestedCreateInputs(buffer, effectiveSchema, model);
    _writeNestedUpdateInputs(buffer, effectiveSchema, model);
  }

  buffer
    ..writeln('DateTime? _asDateTime(Object? value) {')
    ..writeln('  if (value is DateTime) {')
    ..writeln('    return value;')
    ..writeln('  }')
    ..writeln('  if (value is String) {')
    ..writeln('    return DateTime.tryParse(value);')
    ..writeln('  }')
    ..writeln('  return null;')
    ..writeln('}')
    ..writeln()
    ..writeln('double? _asDouble(Object? value) {')
    ..writeln('  if (value is num) {')
    ..writeln('    return value.toDouble();')
    ..writeln('  }')
    ..writeln('  return null;')
    ..writeln('}')
    ..writeln()
    ..writeln('List<int>? _asBytes(Object? value) {')
    ..writeln('  if (value is List<int>) {')
    ..writeln('    return value;')
    ..writeln('  }')
    ..writeln('  if (value is List<Object?>) {')
    ..writeln('    return value.whereType<int>().toList(growable: false);')
    ..writeln('  }')
    ..writeln('  return null;')
    ..writeln('}')
    ..writeln()
    ..writeln('BigInt? _asBigInt(Object? value) {')
    ..writeln('  if (value is BigInt) {')
    ..writeln('    return value;')
    ..writeln('  }')
    ..writeln('  if (value is int) {')
    ..writeln('    return BigInt.from(value);')
    ..writeln('  }')
    ..writeln('  if (value is String) {')
    ..writeln('    return BigInt.tryParse(value);')
    ..writeln('  }')
    ..writeln('  return null;')
    ..writeln('}')
    ..writeln()
    ..writeln('String? _enumName(Object? value) {')
    ..writeln('  if (value is Enum) {')
    ..writeln('    return value.name;')
    ..writeln('  }')
    ..writeln('  return null;')
    ..writeln('}')
    ..writeln()
    ..writeln('class _Undefined {')
    ..writeln('  const _Undefined();')
    ..writeln('}')
    ..writeln()
    ..writeln('const Object _undefined = _Undefined();')
    ..writeln()
    ..writeln('bool _deepEquals(Object? left, Object? right) {')
    ..writeln('  if (identical(left, right)) {')
    ..writeln('    return true;')
    ..writeln('  }')
    ..writeln('  if (left is List<Object?> && right is List<Object?>) {')
    ..writeln('    if (left.length != right.length) {')
    ..writeln('      return false;')
    ..writeln('    }')
    ..writeln('    for (var index = 0; index < left.length; index++) {')
    ..writeln('      if (!_deepEquals(left[index], right[index])) {')
    ..writeln('        return false;')
    ..writeln('      }')
    ..writeln('    }')
    ..writeln('    return true;')
    ..writeln('  }')
    ..writeln(
      '  if (left is Map<Object?, Object?> && right is Map<Object?, Object?>) {',
    )
    ..writeln('    if (left.length != right.length) {')
    ..writeln('      return false;')
    ..writeln('    }')
    ..writeln('    for (final entry in left.entries) {')
    ..writeln('      if (!right.containsKey(entry.key)) {')
    ..writeln('        return false;')
    ..writeln('      }')
    ..writeln('      if (!_deepEquals(entry.value, right[entry.key])) {')
    ..writeln('        return false;')
    ..writeln('      }')
    ..writeln('    }')
    ..writeln('    return true;')
    ..writeln('  }')
    ..writeln('  return left == right;')
    ..writeln('}')
    ..writeln()
    ..writeln('int _deepHash(Object? value) {')
    ..writeln('  if (value is List<Object?>) {')
    ..writeln('    return Object.hashAll(value.map(_deepHash));')
    ..writeln('  }')
    ..writeln('  if (value is Map<Object?, Object?>) {')
    ..writeln('    final entries = value.entries')
    ..writeln(
      '        .map((entry) => Object.hash(_deepHash(entry.key), _deepHash(entry.value)))',
    )
    ..writeln('        .toList(growable: false)')
    ..writeln('      ..sort();')
    ..writeln('    return Object.hashAll(entries);')
    ..writeln('  }')
    ..writeln('  return value.hashCode;')
    ..writeln('}')
    ..writeln()
    ..writeln('Object? _jsonEncodable(Object? value) {')
    ..writeln(
      '  if (value == null || value is String || value is num || value is bool) {',
    )
    ..writeln('    return value;')
    ..writeln('  }')
    ..writeln('  if (value is DateTime) {')
    ..writeln('    return value.toIso8601String();')
    ..writeln('  }')
    ..writeln('  if (value is BigInt) {')
    ..writeln('    return value.toString();')
    ..writeln('  }')
    ..writeln('  if (value is Enum) {')
    ..writeln('    return value.name;')
    ..writeln('  }')
    ..writeln('  if (value is List<Object?>) {')
    ..writeln('    return value.map(_jsonEncodable).toList(growable: false);')
    ..writeln('  }')
    ..writeln('  if (value is Map<Object?, Object?>) {')
    ..writeln('    final json = <String, Object?>{};')
    ..writeln('    for (final entry in value.entries) {')
    ..writeln(
      '      json[entry.key.toString()] = _jsonEncodable(entry.value);',
    )
    ..writeln('    }')
    ..writeln('    return Map<String, Object?>.unmodifiable(json);')
    ..writeln('  }')
    ..writeln('  return value;')
    ..writeln('}')
    ..writeln()
    ..writeln('Object? _requireRecordValue(')
    ..writeln('  Map<String, Object?> record,')
    ..writeln('  String field,')
    ..writeln('  String context,')
    ..writeln(') {')
    ..writeln('  final value = record[field];')
    ..writeln('  if (value == null) {')
    ..writeln('    throw StateError(')
    ..writeln("      'Missing required key \"\$field\" for \$context.',")
    ..writeln('    );')
    ..writeln('  }')
    ..writeln('  return value;')
    ..writeln('}')
    ..writeln()
    ..writeln('bool _isSkippableDuplicateError(Object error) {')
    ..writeln('  final code = _errorCode(error);')
    ..writeln("  if (code == '23505') {")
    ..writeln('    return true;')
    ..writeln('  }')
    ..writeln('  final normalized = error.toString().toLowerCase();')
    ..writeln(
      "  return normalized.contains('duplicate key value violates unique constraint') ||",
    )
    ..writeln("      normalized.contains('unique constraint failed') ||")
    ..writeln("      normalized.contains('unique violation');")
    ..writeln('}')
    ..writeln()
    ..writeln('String? _errorCode(Object error) {')
    ..writeln('  try {')
    ..writeln('    final dynamicError = error as dynamic;')
    ..writeln('    final code = dynamicError.code;')
    ..writeln('    return code is String ? code : null;')
    ..writeln('  } catch (_) {')
    ..writeln('    return null;')
    ..writeln('  }')
    ..writeln('}')
    ..writeln();

  return buffer.toString();
}