From 93cb0c254caeff91ad400063745a313d79dd3107 Mon Sep 17 00:00:00 2001 From: mathru Date: Mon, 10 Apr 2023 23:39:21 +0900 Subject: [PATCH] feat: Added the ability to create mirror data in document and collection. --- packages/masamune/pubspec.lock | 72 +++--- .../lib/src/collection_model_path.dart | 43 +++- .../lib/src/document_model_path.dart | 39 ++- packages/masamune_annotation/pubspec.lock | 9 +- .../generator/collection_model_generator.dart | 14 +- .../generator/document_model_generator.dart | 13 +- .../lib/model/collection_model_class.dart | 238 +++++++++++++++++- .../lib/model/document_model_class.dart | 110 +++++++- .../lib/model/model_class.dart | 232 +++++++++++++++++ packages/masamune_builder/pubspec.lock | 40 +-- 10 files changed, 736 insertions(+), 74 deletions(-) diff --git a/packages/masamune/pubspec.lock b/packages/masamune/pubspec.lock index c6524fc24..7376fdaf0 100644 --- a/packages/masamune/pubspec.lock +++ b/packages/masamune/pubspec.lock @@ -228,106 +228,106 @@ packages: dependency: "direct main" description: name: katana - sha256: f4e68a522ae23b317132990e9a3dc2d4cb0fd7155de081daf2cc52427d8b6c46 + sha256: d2994914d6a1ec082f05cb753138e0a729370479dc7b47f51d4d6db4027e4087 url: "https://pub.dev" source: hosted - version: "1.0.11" + version: "1.0.12" katana_auth: dependency: "direct main" description: name: katana_auth - sha256: c3263eccead402d5e064175490f53409722f2eb15e0756c6311db061b21b98ad + sha256: "3aaf5c4d5489b0df1cb9eef6657ffcdaddf23e816e2943b20405f14e2dbdec78" url: "https://pub.dev" source: hosted - version: "1.2.13" + version: "1.2.14" katana_form: dependency: "direct main" description: name: katana_form - sha256: "0f45da76efbe15beadde6cc62805852fa74c8d6a7616bf9c0f5e37828603ac48" + sha256: ffdf9a8a11e99b364f49d2c16eccab6e73b463a8adb4ed9853085a661f75573d url: "https://pub.dev" source: hosted - version: "1.4.6" + version: "1.4.7" katana_functions: dependency: "direct main" description: name: katana_functions - sha256: "2cfbcfe7fab517d5ec033f53fffb41f7b2c026e5a54135000340ea195172a76b" + sha256: "584efdee2963bc196dc3ff8e4c1cfbca85855e870e877a8f662bbc9d3eb604ad" url: "https://pub.dev" source: hosted - version: "1.1.21" + version: "1.1.22" katana_indicator: dependency: "direct main" description: name: katana_indicator - sha256: c350f64d6c3a3ff7fe2f37bf8062ecbbf1a499617bbe8889c6e11c6be834da9e + sha256: d0862ad8e527405371c929fab29f49c77ed82454e3a8a593035aeb3b8a07950f url: "https://pub.dev" source: hosted - version: "1.1.17" + version: "1.1.18" katana_listenables: dependency: "direct main" description: name: katana_listenables - sha256: caa1d6b6a7c48405dddfd79a884f7a49ac138ca9129e43b719ad4fb5ab9ca705 + sha256: c4bcfb799c19cb04b2b8a7f19b3da7c946cf1367eb88615bac027bd704154c28 url: "https://pub.dev" source: hosted - version: "1.2.22" + version: "1.2.24" katana_listenables_annotation: dependency: transitive description: name: katana_listenables_annotation - sha256: accdb333507b32231e546f66fb75f2ce9079a489223659abed91f6958f07e591 + sha256: "3d172e8f956ef5c79c7a106fa83f1774684f03b391411c5f671a9b687ec3f500" url: "https://pub.dev" source: hosted - version: "1.2.20" + version: "1.2.22" katana_localization: dependency: "direct main" description: name: katana_localization - sha256: "11e30dc3053bd5256835c8046a4d86f85c6c3b8617cc011200f5aecd847d1a9c" + sha256: "071ace913689f25d1895cf39cdd446bdc2433167ee056762785d27269505d008" url: "https://pub.dev" source: hosted - version: "1.1.34" + version: "1.1.36" katana_localization_annotation: dependency: transitive description: name: katana_localization_annotation - sha256: b3afffa74dafc7323276438b6e25b3941a057fdb70ed78ebee0aaf5afab691c3 + sha256: c16b04f75347af835ec2be72778110e946bb1e0d497c87221aad5c4472c269b6 url: "https://pub.dev" source: hosted - version: "1.1.32" + version: "1.1.34" katana_logger: dependency: "direct main" description: name: katana_logger - sha256: "58bb47b1063437852188e82faf9dbba87303723bf0381b42ebdde07c10337c9d" + sha256: "07b3c31a7c6994a87c40c916994955bd9bcaeda0d5e7ffc2ecb8f0726552ad10" url: "https://pub.dev" source: hosted - version: "1.2.9" + version: "1.2.10" katana_model: dependency: "direct main" description: name: katana_model - sha256: "5450db03f04faa15de181a113e0f6e262cede7a613b0af36cc9c48fc01374be7" + sha256: d951f1f612f66fd9913f36961b0fccb81e14bc7030b69f1c43ca6825b5f5a207 url: "https://pub.dev" source: hosted - version: "1.7.7" + version: "1.7.8" katana_prefs: dependency: "direct main" description: name: katana_prefs - sha256: d15205118d2f137d10b4ba02dca1b8a19b48f688740fa09869e9f70c0e7e724b + sha256: "6ef657160fe8febbf83018f933f621738edf47eb79337056d713c3db38700056" url: "https://pub.dev" source: hosted - version: "1.1.22" + version: "1.1.24" katana_prefs_annotation: dependency: transitive description: name: katana_prefs_annotation - sha256: b9395203ca6cfd3467092d8fd8b9fa09f9faeada63cf0f3767693231c68ad040 + sha256: "93c5f5b9aaadccb84d8ff3e4fb836f603eeee8ac7c334924aafc52937a2ed598" url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.18" katana_router: dependency: "direct main" description: @@ -356,10 +356,10 @@ packages: dependency: "direct main" description: name: katana_shorten - sha256: ddf0e5373dea182cfb1f12ee5911eac82ac8ebb7208d74353c73148039b50a59 + sha256: "1d7a835932fcf96fa4ae4b4c77d2195ed1ffcb373ecedc3ecd4310880a091c2c" url: "https://pub.dev" source: hosted - version: "1.2.14" + version: "1.2.15" katana_storage: dependency: "direct main" description: @@ -372,26 +372,26 @@ packages: dependency: "direct main" description: name: katana_theme - sha256: eb70fa22fb5d5cff9e88f3aa7697cc7fe4db1c8010c6c71699b39ecbd6d0a5ee + sha256: "2879a1e22c2bd29d7ec7c59fca7676cb8cb8847cf5c0ce16be29afe4a5421e3c" url: "https://pub.dev" source: hosted - version: "1.4.8" + version: "1.4.10" katana_theme_annotation: dependency: transitive description: name: katana_theme_annotation - sha256: "750452a745f08c8556b5d9c9461559a5b349e3add644014df8f2d7991f469831" + sha256: "74c9fe0cd0e4ac66fbdc4b3116edb7d6a91938485c413f7b1f8d71f58ceb6402" url: "https://pub.dev" source: hosted - version: "1.4.7" + version: "1.4.9" katana_ui: dependency: "direct main" description: name: katana_ui - sha256: f55cf92b58061c22f5d7e090a2a52b7c1d56dfd3e600fccf11c8471a9ba77a25 + sha256: "96746f98cadc39b6908426b36eb87eaae1c97ac2ff2221859f4611f88ba56f78" url: "https://pub.dev" source: hosted - version: "1.2.10" + version: "1.2.11" lints: dependency: transitive description: @@ -412,10 +412,10 @@ packages: dependency: "direct main" description: name: masamune_annotation - sha256: "1cb5fc9fc07a7bdc1f7b328229c667b1cf60fabb3c74d47c542fe0b52c9dc2f0" + sha256: cf830df46a8adae31ff7bc13eb635e602103dd9e2ffc2ae4b995bea6d1576bd6 url: "https://pub.dev" source: hosted - version: "1.4.10" + version: "1.4.11" matcher: dependency: transitive description: diff --git a/packages/masamune_annotation/lib/src/collection_model_path.dart b/packages/masamune_annotation/lib/src/collection_model_path.dart index 60c3ffa6d..271bf7f3e 100644 --- a/packages/masamune_annotation/lib/src/collection_model_path.dart +++ b/packages/masamune_annotation/lib/src/collection_model_path.dart @@ -2,8 +2,6 @@ part of masamune_annotation; /// Annotation to create a collection model. /// -/// Documentation is also created together. -/// /// Specify the path for the collection in [path]. /// /// Use with `freezed`, etc. @@ -12,9 +10,15 @@ part of masamune_annotation; /// /// You can define a query to retrieve the collection model in `static const collection = _$(class name)CollectionQuery()`. /// -/// コレクションモデルを作成するためのアノテーション。 +/// You can duplicate the same data to another path by defining a path in [mirror]. +/// +/// Each data can be retrieved with `document.mirror` or `collection.mirror` and can be `loaded` or `saved` in the same way. +/// +/// In addition, by using `saveSync` and `deleteSync`, data can be saved and deleted synchronously. +/// +/// It can be used to achieve relationships in NoSQL databases with follow/follow implementations. /// -/// ドキュメントも一緒に作成されます。 +/// コレクションモデルを作成するためのアノテーション。 /// /// [path]にコレクション用のパスを指定します。 /// @@ -24,6 +28,14 @@ part of masamune_annotation; /// /// `static const collection = _$(クラス名)CollectionQuery()`にコレクションモデルを取得するためのクエリを定義できます。 /// +/// [mirror]にパスを定義すると別のパスに同じデータを複製することができます。 +/// +/// それぞれのデータは`document.mirror`や`collection.mirror`で取得でき、同じように`load`や`save`ができるようになります。 +/// +/// さらに`saveSync`や`deleteSync`を利用することで、同期的にデータの保存や削除が行なえます。 +/// +/// フォロー・フォロワーの実装でNoSQLデータベースにおけるリレーションを実現するために利用することが可能です。 +/// /// ```dart /// @freezed /// @formValue @@ -56,6 +68,14 @@ class CollectionModelPath { /// /// You can define a query to retrieve the collection model in `static const collection = _$(class name)CollectionQuery()`. /// + /// You can duplicate the same data to another path by defining a path in [mirror]. + /// + /// Each data can be retrieved with `document.mirror` or `collection.mirror` and can be `loaded` or `saved` in the same way. + /// + /// In addition, by using `saveSync` and `deleteSync`, data can be saved and deleted synchronously. + /// + /// It can be used to achieve relationships in NoSQL databases with follow/follow implementations. + /// /// コレクションモデルを作成するためのアノテーション。 /// /// [path]にコレクション用のパスを指定します。 @@ -66,6 +86,14 @@ class CollectionModelPath { /// /// `static const collection = _$(クラス名)CollectionQuery()`にコレクションモデルを取得するためのクエリを定義できます。 /// + /// [mirror]にパスを定義すると別のパスに同じデータを複製することができます。 + /// + /// それぞれのデータは`document.mirror`や`collection.mirror`で取得でき、同じように`load`や`save`ができるようになります。 + /// + /// さらに`saveSync`や`deleteSync`を利用することで、同期的にデータの保存や削除が行なえます。 + /// + /// フォロー・フォロワーの実装でNoSQLデータベースにおけるリレーションを実現するために利用することが可能です。 + /// /// ```dart /// @freezed /// @formValue @@ -87,10 +115,15 @@ class CollectionModelPath { /// ``` /// /// * see https://pub.dev/packages/freezed - const CollectionModelPath(this.path); + const CollectionModelPath(this.path, {this.mirror}); /// Path for collection. /// /// コレクション用のパス。 final String path; + + /// Path for mirror collection. + /// + /// ミラーコレクション用のパス。 + final String? mirror; } diff --git a/packages/masamune_annotation/lib/src/document_model_path.dart b/packages/masamune_annotation/lib/src/document_model_path.dart index ce27be6e1..a004c6548 100644 --- a/packages/masamune_annotation/lib/src/document_model_path.dart +++ b/packages/masamune_annotation/lib/src/document_model_path.dart @@ -8,6 +8,14 @@ part of masamune_annotation; /// /// You can define a query to get the document model in `static const document = _$(class name)DocumentQuery()`. /// +/// You can duplicate the same data to another path by defining a path in [mirror]. +/// +/// Each data can be retrieved with `document.mirror` and can be `loaded` and `saved` in the same way. +/// +/// In addition, by using `saveSync` and `deleteSync`, data can be saved and deleted synchronously. +/// +/// It can be used to achieve relationships in NoSQL databases with follow/follow implementations. +/// /// ドキュメントモデルを作成するためのアノテーション。 /// /// [path]にドキュメント用のパスを指定します。 @@ -16,6 +24,14 @@ part of masamune_annotation; /// /// `static const document = _$(クラス名)DocumentQuery()`にドキュメントモデルを取得するためのクエリを定義できます。 /// +/// [mirror]にパスを定義すると別のパスに同じデータを複製することができます。 +/// +/// それぞれのデータは`document.mirror`で取得でき、同じように`load`や`save`ができるようになります。 +/// +/// さらに`saveSync`や`deleteSync`を利用することで、同期的にデータの保存や削除が行なえます。 +/// +/// フォロー・フォロワーの実装でNoSQLデータベースにおけるリレーションを実現するために利用することが可能です。 +/// /// ```dart /// @freezed /// @formValue @@ -44,6 +60,14 @@ class DocumentModelPath { /// /// You can define a query to get the document model in `static const document = _$(class name)DocumentQuery()`. /// + /// You can duplicate the same data to another path by defining a path in [mirror]. + /// + /// Each data can be retrieved with `document.mirror` and can be `loaded` and `saved` in the same way. + /// + /// In addition, by using `saveSync` and `deleteSync`, data can be saved and deleted synchronously. + /// + /// It can be used to achieve relationships in NoSQL databases with follow/follow implementations. + /// /// ドキュメントモデルを作成するためのアノテーション。 /// /// [path]にドキュメント用のパスを指定します。 @@ -52,6 +76,14 @@ class DocumentModelPath { /// /// `static const document = _$(クラス名)DocumentQuery()`にドキュメントモデルを取得するためのクエリを定義できます。 /// + /// [mirror]にパスを定義すると別のパスに同じデータを複製することができます。 + /// + /// それぞれのデータは`document.mirror`で取得でき、同じように`load`や`save`ができるようになります。 + /// + /// さらに`saveSync`や`deleteSync`を利用することで、同期的にデータの保存や削除が行なえます。 + /// + /// フォロー・フォロワーの実装でNoSQLデータベースにおけるリレーションを実現するために利用することが可能です。 + /// /// ```dart /// @freezed /// @formValue @@ -71,10 +103,15 @@ class DocumentModelPath { /// ``` /// /// * see https://pub.dev/packages/freezed - const DocumentModelPath(this.path); + const DocumentModelPath(this.path, this.mirror); /// Path for documentation. /// /// ドキュメント用のパス。 final String path; + + /// Path for mirror documents. + /// + /// ミラードキュメント用のパス。 + final String? mirror; } diff --git a/packages/masamune_annotation/pubspec.lock b/packages/masamune_annotation/pubspec.lock index 8a20cf66b..e68443229 100644 --- a/packages/masamune_annotation/pubspec.lock +++ b/packages/masamune_annotation/pubspec.lock @@ -188,10 +188,11 @@ packages: katana: dependency: "direct main" description: - path: "../katana" - relative: true - source: path - version: "1.0.10" + name: katana + sha256: d2994914d6a1ec082f05cb753138e0a729370479dc7b47f51d4d6db4027e4087 + url: "https://pub.dev" + source: hosted + version: "1.0.12" lints: dependency: transitive description: diff --git a/packages/masamune_builder/lib/generator/collection_model_generator.dart b/packages/masamune_builder/lib/generator/collection_model_generator.dart index c537901fc..69c51dbe8 100644 --- a/packages/masamune_builder/lib/generator/collection_model_generator.dart +++ b/packages/masamune_builder/lib/generator/collection_model_generator.dart @@ -48,7 +48,11 @@ class CollectionModelGenerator final classValue = ClassValue(element); final pathValue = PathValue(annotation.read("path").stringValue.trimString("/")); + final mirrorPathValue = annotation.read("mirror").isNull + ? null + : PathValue(annotation.read("mirror").stringValue.trimString("/")); final path = pathValue.path; + final mirrorPath = mirrorPathValue?.path; if (path.splitLength() <= 0 || path.splitLength() % 2 != 1) { throw InvalidGenerationSourceError( @@ -57,11 +61,19 @@ class CollectionModelGenerator ); } + if (mirrorPath.isNotEmpty && + (path.splitLength() <= 0 || path.splitLength() % 2 != 1)) { + throw InvalidGenerationSourceError( + "The query mirror path hierarchy must be an odd number: $mirrorPath", + element: element, + ); + } + final generated = Library( (l) => l ..body.addAll( [ - ...collectionModelClass(classValue, pathValue), + ...collectionModelClass(classValue, pathValue, mirrorPathValue), ], ), ); diff --git a/packages/masamune_builder/lib/generator/document_model_generator.dart b/packages/masamune_builder/lib/generator/document_model_generator.dart index 14935ab91..6570698c9 100644 --- a/packages/masamune_builder/lib/generator/document_model_generator.dart +++ b/packages/masamune_builder/lib/generator/document_model_generator.dart @@ -47,7 +47,11 @@ class DocumentModelGenerator extends GeneratorForAnnotation { final classValue = ClassValue(element); final pathValue = PathValue(annotation.read("path").stringValue.trimString("/")); + final mirrorPathValue = annotation.read("mirror").isNull + ? null + : PathValue(annotation.read("mirror").stringValue.trimString("/")); final path = pathValue.path; + final mirrorPath = mirrorPathValue?.path; if (path.splitLength() <= 0 || path.splitLength() % 2 != 0) { throw InvalidGenerationSourceError( @@ -55,12 +59,19 @@ class DocumentModelGenerator extends GeneratorForAnnotation { element: element, ); } + if (mirrorPath.isNotEmpty && + (path.splitLength() <= 0 || path.splitLength() % 2 != 0)) { + throw InvalidGenerationSourceError( + "The query mirror path hierarchy must be an odd number: $mirrorPath", + element: element, + ); + } final generated = Library( (l) => l ..body.addAll( [ - ...documentModelClass(classValue, pathValue), + ...documentModelClass(classValue, pathValue, mirrorPathValue), ], ), ); diff --git a/packages/masamune_builder/lib/model/collection_model_class.dart b/packages/masamune_builder/lib/model/collection_model_class.dart index 07b47badc..80a3927ad 100644 --- a/packages/masamune_builder/lib/model/collection_model_class.dart +++ b/packages/masamune_builder/lib/model/collection_model_class.dart @@ -210,9 +210,10 @@ enum CollectionQueryType { List collectionModelClass( ClassValue model, PathValue path, + PathValue? mirror, ) { return [ - ...modelClass(model, path), + ...modelClass(model, path, mirror), Enum( (e) => e ..name = "${model.name}CollectionKey" @@ -266,7 +267,16 @@ List collectionModelClass( ..body = Code( "return _\$_${model.name}DocumentQuery(DocumentModelQuery(\"${path.path.replaceAllMapped(_pathRegExp, (m) => "\$${m.group(1)?.toCamelCase() ?? ""}")}/\$_id\", adapter: adapter,));", ), - ) + ), + if (mirror != null) + Method( + (m) => m + ..name = "mirror" + ..type = MethodType.getter + ..returns = Reference("_\$${model.name}MirrorDocumentQuery") + ..lambda = true + ..body = Code("_\$${model.name}MirrorDocumentQuery()"), + ), ]), ), Class( @@ -359,7 +369,16 @@ List collectionModelClass( ..body = Code( "return _\$_${model.name}CollectionQuery(CollectionModelQuery(\"${path.path.replaceAllMapped(_pathRegExp, (m) => "\$${m.group(1)?.toCamelCase() ?? ""}")}\", adapter: adapter,));", ), - ) + ), + if (mirror != null) + Method( + (m) => m + ..name = "mirror" + ..type = MethodType.getter + ..returns = Reference("_\$${model.name}MirrorCollectionQuery") + ..lambda = true + ..body = Code("_\$${model.name}MirrorCollectionQuery()"), + ), ]), ), Class( @@ -428,6 +447,217 @@ List collectionModelClass( ); }), ]), - ) + ), + if (mirror != null) ...[ + Class( + (c) => c + ..name = "_\$${model.name}MirrorDocumentQuery" + ..annotations.addAll([const Reference("immutable")]) + ..constructors.addAll([ + Constructor( + (c) => c..constant = true, + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "call" + ..annotations.addAll([const Reference("useResult")]) + ..optionalParameters.addAll([ + ...mirror.parameters.map((param) { + return Parameter( + (p) => p + ..name = param.camelCase + ..named = true + ..required = true + ..type = const Reference("String"), + ); + }), + Parameter( + (p) => p + ..name = "adapter" + ..named = true + ..type = const Reference("ModelAdapter?"), + ), + ]) + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "_id" + ..type = const Reference("Object"), + ), + ]) + ..returns = Reference("_\$_${model.name}MirrorDocumentQuery") + ..body = Code( + "return _\$_${model.name}MirrorDocumentQuery(DocumentModelQuery(\"${mirror.path.replaceAllMapped(_pathRegExp, (m) => "\$${m.group(1)?.toCamelCase() ?? ""}")}/\$_id\", adapter: adapter,));", + ), + ) + ]), + ), + Class( + (c) => c + ..name = "_\$_${model.name}MirrorDocumentQuery" + ..annotations.addAll([const Reference("immutable")]) + ..extend = Reference("ModelQueryBase<\$${model.name}MirrorDocument>") + ..constructors.addAll([ + Constructor( + (c) => c + ..constant = true + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "modelQuery" + ..toThis = true, + ) + ]), + ) + ]) + ..fields.addAll([ + Field( + (f) => f + ..name = "modelQuery" + ..modifier = FieldModifier.final$ + ..type = const Reference("DocumentModelQuery"), + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "call" + ..lambda = true + ..annotations.addAll([const Reference("override")]) + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "ref" + ..type = const Reference("Ref"), + ) + ]) + ..returns = + Reference("\$${model.name}MirrorDocument Function()") + ..body = Code( + "() => \$${model.name}MirrorDocument(modelQuery)", + ), + ), + Method( + (m) => m + ..name = "name" + ..lambda = true + ..type = MethodType.getter + ..annotations.addAll([const Reference("override")]) + ..returns = const Reference("String") + ..body = const Code("modelQuery.toString()"), + ), + ]), + ), + Class( + (c) => c + ..name = "_\$${model.name}MirrorCollectionQuery" + ..annotations.addAll([const Reference("immutable")]) + ..constructors.addAll([ + Constructor( + (c) => c..constant = true, + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "call" + ..annotations.addAll([const Reference("useResult")]) + ..optionalParameters.addAll([ + ...mirror.parameters.map((param) { + return Parameter( + (p) => p + ..name = param.camelCase + ..named = true + ..required = true + ..type = const Reference("String"), + ); + }), + Parameter( + (p) => p + ..name = "adapter" + ..named = true + ..type = const Reference("ModelAdapter?"), + ), + ]) + ..returns = Reference("_\$_${model.name}MirrorCollectionQuery") + ..body = Code( + "return _\$_${model.name}MirrorCollectionQuery(CollectionModelQuery(\"${mirror.path.replaceAllMapped(_pathRegExp, (m) => "\$${m.group(1)?.toCamelCase() ?? ""}")}\", adapter: adapter,));", + ), + ) + ]), + ), + Class( + (c) => c + ..name = "_\$_${model.name}MirrorCollectionQuery" + ..annotations.addAll([const Reference("immutable")]) + ..extend = + Reference("ModelQueryBase<_\$${model.name}MirrorCollection>") + ..constructors.addAll([ + Constructor( + (c) => c + ..constant = true + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "modelQuery" + ..toThis = true, + ) + ]), + ) + ]) + ..fields.addAll([ + Field( + (f) => f + ..name = "modelQuery" + ..modifier = FieldModifier.final$ + ..type = const Reference("CollectionModelQuery"), + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "call" + ..lambda = true + ..annotations.addAll([const Reference("override")]) + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "ref" + ..type = const Reference("Ref"), + ) + ]) + ..returns = + Reference("_\$${model.name}MirrorCollection Function()") + ..body = Code( + "() => _\$${model.name}MirrorCollection(modelQuery)", + ), + ), + Method( + (m) => m + ..name = "name" + ..lambda = true + ..type = MethodType.getter + ..annotations.addAll([const Reference("override")]) + ..returns = const Reference("String") + ..body = const Code("modelQuery.toString()"), + ), + ...CollectionQueryType.values.map((queryType) { + return Method( + (m) => m + ..name = queryType.name + ..returns = + Reference("_\$_${model.name}MirrorCollectionQuery") + ..requiredParameters.addAll( + [...queryType.parameters("${model.name}CollectionKey")]) + ..body = Code( + "return _\$_${model.name}MirrorCollectionQuery(modelQuery.${queryType.methodCode});", + ), + ); + }), + ]), + ), + ], ]; } diff --git a/packages/masamune_builder/lib/model/document_model_class.dart b/packages/masamune_builder/lib/model/document_model_class.dart index d9ab3922f..2700fedd6 100644 --- a/packages/masamune_builder/lib/model/document_model_class.dart +++ b/packages/masamune_builder/lib/model/document_model_class.dart @@ -6,9 +6,10 @@ part of masamune_builder; List documentModelClass( ClassValue model, PathValue path, + PathValue? mirror, ) { return [ - ...modelClass(model, path), + ...modelClass(model, path, mirror), Class( (c) => c ..name = "_\$${model.name}DocumentQuery" @@ -44,7 +45,16 @@ List documentModelClass( ..body = Code( "return _\$_${model.name}DocumentQuery(DocumentModelQuery(\"${path.path.replaceAllMapped(_pathRegExp, (m) => "\$${m.group(1)?.toCamelCase() ?? ""}")}\", adapter: adapter,));", ), - ) + ), + if (mirror != null) + Method( + (m) => m + ..name = "mirror" + ..type = MethodType.getter + ..returns = Reference("_\$${model.name}MirrorDocumentQuery") + ..lambda = true + ..body = Code("_\$${model.name}MirrorDocumentQuery()"), + ), ]), ), Class( @@ -102,5 +112,101 @@ List documentModelClass( ), ]), ), + if (mirror != null) ...[ + Class( + (c) => c + ..name = "_\$${model.name}MirrorDocumentQuery" + ..annotations.addAll([const Reference("immutable")]) + ..constructors.addAll([ + Constructor( + (c) => c..constant = true, + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "call" + ..annotations.addAll([const Reference("useResult")]) + ..optionalParameters.addAll([ + ...mirror.parameters.map((param) { + return Parameter( + (p) => p + ..name = param.camelCase + ..named = true + ..required = true + ..type = const Reference("String"), + ); + }), + Parameter( + (p) => p + ..name = "adapter" + ..named = true + ..type = const Reference("ModelAdapter?"), + ), + ]) + ..returns = Reference("_\$_${model.name}MirrorDocumentQuery") + ..body = Code( + "return _\$_${model.name}MirrorDocumentQuery(DocumentModelQuery(\"${mirror.path.replaceAllMapped(_pathRegExp, (m) => "\$${m.group(1)?.toCamelCase() ?? ""}")}\", adapter: adapter,));", + ), + ) + ]), + ), + Class( + (c) => c + ..name = "_\$_${model.name}MirrorDocumentQuery" + ..annotations.addAll([const Reference("immutable")]) + ..extend = Reference("ModelQueryBase<\$${model.name}MirrorDocument>") + ..constructors.addAll([ + Constructor( + (c) => c + ..constant = true + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "modelQuery" + ..toThis = true, + ) + ]), + ) + ]) + ..fields.addAll([ + Field( + (f) => f + ..name = "modelQuery" + ..modifier = FieldModifier.final$ + ..type = const Reference("DocumentModelQuery"), + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "call" + ..lambda = true + ..annotations.addAll([const Reference("override")]) + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "ref" + ..type = const Reference("Ref"), + ) + ]) + ..returns = + Reference("\$${model.name}MirrorDocument Function()") + ..body = Code( + "() => \$${model.name}MirrorDocument(modelQuery)", + ), + ), + Method( + (m) => m + ..name = "name" + ..lambda = true + ..type = MethodType.getter + ..annotations.addAll([const Reference("override")]) + ..returns = const Reference("String") + ..body = const Code("modelQuery.toString()"), + ), + ]), + ), + ], ]; } diff --git a/packages/masamune_builder/lib/model/model_class.dart b/packages/masamune_builder/lib/model/model_class.dart index ee0834712..825048c6f 100644 --- a/packages/masamune_builder/lib/model/model_class.dart +++ b/packages/masamune_builder/lib/model/model_class.dart @@ -9,6 +9,7 @@ final _regExpRef = RegExp(r"(.+)Ref"); List modelClass( ClassValue model, PathValue path, + PathValue? mirror, ) { final searchable = model.parameters.where((e) => e.isSearchable).toList(); final referenceable = model.parameters.where((e) => e.isReference).toList(); @@ -67,6 +68,43 @@ List modelClass( ]) ..body = const Code("value.toJson()"), ), + if (mirror != null) ...[ + Method( + (m) => m + ..name = "saveSync" + ..returns = const Reference("Future") + ..modifier = MethodModifier.async + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "mirror" + ..type = Reference("\$${model.name}MirrorDocument"), + ), + Parameter( + (p) => p + ..name = "newValue" + ..type = Reference(model.name), + ) + ]) + ..body = const Code( + "final tr = transaction(); await tr.call((ref, doc) async { final mdoc = ref.read(mirror); doc.save(newValue); mdoc.save(newValue); });"), + ), + Method( + (m) => m + ..name = "deleteSync" + ..returns = const Reference("Future") + ..modifier = MethodModifier.async + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "mirror" + ..type = Reference("\$${model.name}MirrorDocument"), + ) + ]) + ..body = const Code( + "final tr = transaction(); await tr.call((ref, doc) async { final mdoc = ref.read(mirror); doc.delete(); mdoc.delete(); });"), + ), + ], if (searchable.isNotEmpty) Method( (m) => m @@ -167,5 +205,199 @@ List modelClass( ), ]), ), + if (mirror != null) ...[ + Class( + (c) => c + ..name = "\$${model.name}MirrorDocument" + ..extend = Reference("DocumentBase<${model.name}>") + ..mixins.addAll([ + Reference("ModelRefMixin<${model.name}>"), + if (searchable.isNotEmpty) + Reference("SearchableDocumentMixin<${model.name}>"), + if (referenceable.isNotEmpty) + Reference("ModelRefLoaderMixin<${model.name}>") + ]) + ..constructors.addAll([ + Constructor( + (c) => c + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "modelQuery" + ..toSuper = true, + ) + ]), + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "fromMap" + ..lambda = true + ..returns = Reference(model.name) + ..annotations.addAll([const Reference("override")]) + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "map" + ..type = const Reference("DynamicMap"), + ) + ]) + ..body = Code("${model.name}.fromJson(map)"), + ), + Method( + (m) => m + ..name = "toMap" + ..lambda = true + ..returns = const Reference("DynamicMap") + ..annotations.addAll([const Reference("override")]) + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "value" + ..type = Reference(model.name), + ) + ]) + ..body = const Code("value.toJson()"), + ), + Method( + (m) => m + ..name = "saveSync" + ..returns = const Reference("Future") + ..modifier = MethodModifier.async + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "mirror" + ..type = Reference("\$${model.name}Document"), + ), + Parameter( + (p) => p + ..name = "newValue" + ..type = Reference(model.name), + ) + ]) + ..body = const Code( + "final tr = transaction(); await tr.call((ref, doc) async { final mdoc = ref.read(mirror); doc.save(newValue); mdoc.save(newValue); });"), + ), + Method( + (m) => m + ..name = "deleteSync" + ..returns = const Reference("Future") + ..modifier = MethodModifier.async + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "mirror" + ..type = Reference("\$${model.name}Document"), + ) + ]) + ..body = const Code( + "final tr = transaction(); await tr.call((ref, doc) async { final mdoc = ref.read(mirror); doc.delete(); mdoc.delete(); });"), + ), + if (searchable.isNotEmpty) + Method( + (m) => m + ..name = "buildSearchText" + ..lambda = true + ..returns = const Reference("String") + ..annotations.addAll([const Reference("override")]) + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "value" + ..type = Reference(model.name), + ) + ]) + ..body = Code( + searchable.map((e) { + if (e.type.toString().endsWith("?")) { + return "(value.${e.name}?.toString() ?? \"\")"; + } else { + return "value.${e.name}.toString()"; + } + }).join(" + "), + ), + ), + if (referenceable.isNotEmpty) + Method( + (m) => m + ..name = "builder" + ..lambda = true + ..type = MethodType.getter + ..returns = + Reference("List>") + ..annotations.addAll([const Reference("override")]) + ..body = Code("[${referenceable.map((e) { + if (e.type.toString().endsWith("Ref")) { + final match = _regExpRef.firstMatch(e.type.toString()); + if (match == null) { + throw Exception( + "@refParam can only be given to ModelRef / ModelRefBase? / XXXRef types. \r\n\r\n${e.type} ${e.name}", + ); + } + final doc = "\$${match.group(1)}Document"; + return "ModelRefBuilder( modelRef: (value) => value.${e.name}, document: (modelQuery) => $doc(modelQuery), value: (value, doc) => value.copyWith( ${e.name}: doc ),)"; + } else { + if (!e.type.toString().endsWith("?")) { + throw Exception( + "ModelRefBase must be nullable. \r\n\r\n${e.type} ${e.name}", + ); + } + final match = + _regExpModelRef.firstMatch(e.type.toString()); + if (match == null) { + throw Exception( + "@refParam can only be given to ModelRef / ModelRefBase? / XXXRef types. \r\n\r\n${e.type} ${e.name}", + ); + } + final doc = "\$${match.group(2)}Document"; + return "ModelRefBuilder( modelRef: (value) => value.${e.name}, document: (modelQuery) => $doc(modelQuery), value: (value, doc) => value.copyWith( ${e.name}: doc ),)"; + } + }).join(",")}]"), + ), + ]), + ), + Class( + (c) => c + ..name = "_\$${model.name}MirrorCollection" + ..extend = Reference("CollectionBase<\$${model.name}MirrorDocument>") + ..mixins.addAll([ + if (searchable.isNotEmpty) + Reference( + "SearchableCollectionMixin<\$${model.name}MirrorDocument>") + ]) + ..constructors.addAll([ + Constructor( + (c) => c + ..requiredParameters.addAll([ + Parameter( + (p) => p + ..name = "modelQuery" + ..toSuper = true, + ) + ]), + ) + ]) + ..methods.addAll([ + Method( + (m) => m + ..name = "create" + ..annotations.addAll([const Reference("override")]) + ..lambda = true + ..returns = Reference("\$${model.name}MirrorDocument") + ..optionalParameters.addAll([ + Parameter( + (p) => p + ..name = "id" + ..type = const Reference("String?"), + ) + ]) + ..body = Code( + "\$${model.name}MirrorDocument(modelQuery.create(id))"), + ), + ]), + ), + ], ]; } diff --git a/packages/masamune_builder/pubspec.lock b/packages/masamune_builder/pubspec.lock index 4e0488d52..1adf24ee6 100644 --- a/packages/masamune_builder/pubspec.lock +++ b/packages/masamune_builder/pubspec.lock @@ -261,58 +261,58 @@ packages: dependency: "direct main" description: name: katana - sha256: f4e68a522ae23b317132990e9a3dc2d4cb0fd7155de081daf2cc52427d8b6c46 + sha256: d2994914d6a1ec082f05cb753138e0a729370479dc7b47f51d4d6db4027e4087 url: "https://pub.dev" source: hosted - version: "1.0.11" + version: "1.0.12" katana_listenables_annotation: dependency: "direct main" description: name: katana_listenables_annotation - sha256: accdb333507b32231e546f66fb75f2ce9079a489223659abed91f6958f07e591 + sha256: "3d172e8f956ef5c79c7a106fa83f1774684f03b391411c5f671a9b687ec3f500" url: "https://pub.dev" source: hosted - version: "1.2.20" + version: "1.2.22" katana_listenables_builder: dependency: "direct main" description: name: katana_listenables_builder - sha256: "5de2a86bc5e2cff3f1e492c2ba8824d250fc6617568a2ff51a3efb64db4ec12a" + sha256: "08c39521122e128a4cd592c33b41aae2950842dc3709c23e507e8010b15bff04" url: "https://pub.dev" source: hosted - version: "1.2.21" + version: "1.2.23" katana_localization_annotation: dependency: "direct main" description: name: katana_localization_annotation - sha256: b3afffa74dafc7323276438b6e25b3941a057fdb70ed78ebee0aaf5afab691c3 + sha256: c16b04f75347af835ec2be72778110e946bb1e0d497c87221aad5c4472c269b6 url: "https://pub.dev" source: hosted - version: "1.1.32" + version: "1.1.34" katana_localization_builder: dependency: "direct main" description: name: katana_localization_builder - sha256: "6b2e30acf464e50377039b6450efade9dddb2502879063cf9d0ba50f7761980b" + sha256: b09fb1ee6cef13872b10779d61c2f8e82bd789853c95735ba970b6a572aa1773 url: "https://pub.dev" source: hosted - version: "1.1.34" + version: "1.1.36" katana_prefs_annotation: dependency: "direct main" description: name: katana_prefs_annotation - sha256: b9395203ca6cfd3467092d8fd8b9fa09f9faeada63cf0f3767693231c68ad040 + sha256: "93c5f5b9aaadccb84d8ff3e4fb836f603eeee8ac7c334924aafc52937a2ed598" url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.18" katana_prefs_builder: dependency: "direct main" description: name: katana_prefs_builder - sha256: fb8ab802029f0ce03953f97ace8bb32f613b6eb45c15cf0d42a6fc51b0e0da6e + sha256: "088de08331e7ff2259d897dcb768298550336942c25ba056a9498c943cf48467" url: "https://pub.dev" source: hosted - version: "1.1.25" + version: "1.1.27" katana_router_annotation: dependency: "direct main" description: @@ -333,18 +333,18 @@ packages: dependency: "direct main" description: name: katana_theme_annotation - sha256: "750452a745f08c8556b5d9c9461559a5b349e3add644014df8f2d7991f469831" + sha256: "74c9fe0cd0e4ac66fbdc4b3116edb7d6a91938485c413f7b1f8d71f58ceb6402" url: "https://pub.dev" source: hosted - version: "1.4.7" + version: "1.4.9" katana_theme_builder: dependency: "direct main" description: name: katana_theme_builder - sha256: bc1898bc3e20954b9c3b28c4809c183c02a24a985605e60ba72fb0de97569787 + sha256: f2490dcaf161415027432accb618ce0092d6bd333f23eb2aec3308da3b157836 url: "https://pub.dev" source: hosted - version: "1.4.8" + version: "1.4.10" lints: dependency: transitive description: @@ -365,10 +365,10 @@ packages: dependency: "direct main" description: name: masamune_annotation - sha256: "1cb5fc9fc07a7bdc1f7b328229c667b1cf60fabb3c74d47c542fe0b52c9dc2f0" + sha256: cf830df46a8adae31ff7bc13eb635e602103dd9e2ffc2ae4b995bea6d1576bd6 url: "https://pub.dev" source: hosted - version: "1.4.10" + version: "1.4.11" matcher: dependency: transitive description: