Skip to content

Commit dcd60c5

Browse files
committed
expose the programmatic migration api
1 parent 31f011c commit dcd60c5

16 files changed

Lines changed: 554 additions & 193 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- Require dart sdk `>=3.7.0`.
44
- Update analyzer to `^8.1.0`, build to `^4.0.0` and source_gen to `^4.0.0`.
55
- Disable linting and formatting for generated files.
6+
- Expose the programmatic migration api through the `'package:stormberry/migrate.dart'` import.
67

78
# 0.16.0
89

build.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ builders:
1919
build_extensions: { ".dart": [ ".schema.json" ] }
2020
auto_apply: dependents
2121
build_to: cache
22+
database_schema:
23+
import: "package:stormberry/builder.dart"
24+
builder_factories: [ "buildDatabaseSchema" ]
25+
build_extensions: { "$lib$": [ "database.schema.dart" ] }
26+
required_inputs: [ ".schema.json" ]
27+
auto_apply: none # Needs to be enabled manually
28+
build_to: source
2229

2330
targets:
2431
$default:

doc/migration.md

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
Stormberry comes with a database migration tool, to create or update the schema of your database.
1+
Stormberry comes with a database migration system, to create or update the schema of your database.
22

3-
To use this run the following command from the root folder of your project.
3+
You can either call the migration tool from your terminal, or use the programmatic migration API.
4+
5+
## Migration CLI
6+
7+
To use the migration CLI run the following command from the root folder of your project:
48

59
```
610
dart run stormberry migrate
@@ -38,4 +42,47 @@ The tool supported the following options:
3842

3943
---
4044

41-
*🎉 Congrats, you followed the tour until the end. Now you know everything about this package.*
45+
## Migration API
46+
47+
To migrate your database from your own code, first enable the `database_schema` builder like this:
48+
49+
```yaml
50+
// build.yaml
51+
targets:
52+
$default:
53+
builders:
54+
stormberry|database_schema:
55+
enabled: true
56+
```
57+
58+
This will generate a `lib/database.schema.dart` file next time you run code generation, containing a global `DatabaseSchema schema` variable of your current schema.
59+
60+
To migrate your database to this schema, do the following:
61+
62+
```dart
63+
// Import the 'migrate.dart' library.
64+
import 'package:stormberry/migrate.dart';
65+
66+
Future<void> migrate() async {
67+
// 1. Connect to your database
68+
final Database db = ...
69+
70+
// 2. Compute the schema diff between your live database schema and the generated target `schema`.
71+
final diff = await schema.computeDiff(db);
72+
73+
// 3. (Optional) Print the schema diff to stdout.
74+
diff.printToConsole();
75+
76+
try {
77+
// Always use a transaction to not break your database!!
78+
await db.runTx((session) async {
79+
80+
// 4. Apply the necessary patches to migrate to the target schema.
81+
await diff.patch(session);
82+
});
83+
print('Migration succeeded');
84+
} catch (e) {
85+
print('Migration failed. All changes reverted.');
86+
}
87+
}
88+
```

example/build.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
targets:
2+
$default:
3+
builders:
4+
stormberry|database_schema:
5+
enabled: true

example/lib/database.schema.dart

Lines changed: 208 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/lib/main.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import 'package:stormberry/migrate.dart';
12
import 'package:stormberry/stormberry.dart';
23

4+
import 'database.schema.dart';
35
import 'models/account.dart';
46
import 'models/address.dart';
57
import 'models/company.dart';
@@ -16,6 +18,8 @@ Future<void> main() async {
1618

1719
db.debugPrint = true;
1820

21+
await migrate(db);
22+
1923
await db.companies.deleteOne('abc');
2024

2125
await db.companies.insertOne(CompanyInsertRequest(id: 'abc', name: 'Minga', addresses: []));
@@ -47,3 +51,18 @@ Future<void> main() async {
4751

4852
await db.close();
4953
}
54+
55+
Future<void> migrate(Database db) async {
56+
final diff = await schema.computeDiff(db);
57+
58+
diff.printToConsole();
59+
60+
try {
61+
await db.runTx((session) async {
62+
await diff.patch(session);
63+
});
64+
print('Migration succeeded');
65+
} catch (e) {
66+
print('Migration failed: $e');
67+
}
68+
}

lib/builder.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:build/build.dart';
22

3+
import 'src/builder/builders/database_schema_builder.dart';
34
import 'src/builder/builders/json_builder.dart';
45
import 'src/builder/builders/schema_builder.dart';
56
import 'src/builder/builders/analyzing_builder.dart';
@@ -11,3 +12,5 @@ Builder analyzeSchema(BuilderOptions options) => AnalyzingBuilder(options);
1112
Builder buildSchema(BuilderOptions options) => SchemaBuilder(options);
1213

1314
Builder buildRunner(BuilderOptions options) => JsonBuilder(options);
15+
16+
Builder buildDatabaseSchema(BuilderOptions options) => DatabaseSchemaBuilder();

lib/migrate.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// A strongly-typed postgres ORM to provide easy bindings between your dart classes and postgres database.
2+
/// It supports all kinds of relations without any complex configuration.
3+
library;
4+
5+
export 'src/cli/migration/schema.dart';
6+
export 'src/cli/migration/differentiator.dart';
7+
export 'src/cli/migration/patcher.dart';
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'dart:convert';
2+
3+
import 'package:build/build.dart';
4+
import 'package:glob/glob.dart';
5+
6+
class DatabaseSchemaBuilder implements Builder {
7+
DatabaseSchemaBuilder();
8+
9+
@override
10+
Future<void> build(BuildStep buildStep) async {
11+
var allSchemas = await buildStep
12+
.findAssets(Glob('lib/**.schema.json'))
13+
.asyncMap((id) => buildStep.readAsString(id))
14+
.map((c) => jsonDecode(c))
15+
.toList();
16+
17+
var fullSchema = <String, dynamic>{};
18+
for (var schema in allSchemas) {
19+
fullSchema.addAll(schema as Map<String, dynamic>);
20+
}
21+
22+
final output = '// GENERATED CODE - DO NOT MODIFY BY HAND\n\n'
23+
'// ignore_for_file: type=lint\n'
24+
'// dart format off\n\n'
25+
'import \'package:stormberry/migrate.dart\';\n\n'
26+
'final DatabaseSchema schema = DatabaseSchema.fromMap(${const JsonEncoder.withIndent(' ').convert(fullSchema)});\n';
27+
28+
await buildStep.writeAsString(
29+
AssetId(buildStep.inputId.package, 'lib/database.schema.dart'),
30+
output,
31+
);
32+
}
33+
34+
@override
35+
Map<String, List<String>> get buildExtensions => {
36+
r'$lib$': ['database.schema.dart']
37+
};
38+
}

0 commit comments

Comments
 (0)