keg_generator 0.1.0
keg_generator: ^0.1.0 copied to clipboard
sql helper generator for simplified local database access.
keg is small and handy barrel for beer
keg_generator is code generator package to simplify access to local SQLite file.
Generated code uses sqflite to access SQLite file. Tested on sqflite version 2.4.2.
Is this kind of Object-Relational Mapping? I'm not sure about it.
Following is sample code to define simple database.
import 'package:keg_annotation/keg_annotation.dart';
import 'package:sqflite/sqflite.dart';
part 'sole_table_test.g.dart';
@table
class User {
int id;
String name;
User(this.name, [this.id = 0]);
Map<String, Object?> toSqlMap() => _$UserHelper.toSqlMap(this);
factory User.fromSqlMap(Map<String, Object?> map) =>
_$UserHelper.fromSqlMap(map);
}
@KegDatabase(tables: [User])
class AppDatabase extends _$AppDatabase {
@override
Future<String> getPathToOpen() async {
return 'app.db';
}
}
Class User is annotated with @table and referenced to define user table.
Class AppDatabase is annotated with @KegDatabase
and used to define SQLite file.
getPathToOpen is used to determine SQLite file path.
Classes _$UserHelper and _$AppDatabase are generated by keg_generator.
And CRUD operations are,
final appdb = AppDatabase();
await appdb.open();
User user1 = User('John');
print(user1.id); // 0
await appdb.registerUser(user1);
print(user1.id); // 1
List<User> result = await appdb.queryUser(
where: "${appdb.userHelper.column.name} = ?",
whereArgs: ['John'],
);
await appdb.deleteUserByIds([user1]);
await appdb.close();
First you need to call open to open SQLite file.
If you want to create in memory database, call openInMemory instead.
registerUser is used for insert or update user record to table.
If id of the user is 0, record is inserted,
and id is assigned to value by AUTOINCREMENT data type of SQLite.
If id is not 0, REPLACE statement is used,
so record is updated if record with the id exists,
or inserted if not exist.
queryUser is to query users, and returns list of User.
queryUser parameters are similar to
sqflite query.
appdb.userHelper is instance of _$UserHelper class generated by keg_generator, and it holds table name and column names.
class _$UserHelper {
final String tableName = '"user"';
final column = (id: '"id"', name: '"name"');
...
}
These names can be used on query or delete methods.
Table name and column names are converted to snake_case. For Example,
@table
class ItemInfo {
int id;
...
bool isActive;
...
}
class _$ItemInfoHelper {
final String tableName = '"item_info"';
final column = (
id: '"id"',
...
isActive: '"is_active"',
);
...
}
deleteUserByIds is to delete user records by list of User
Full list of generated methods are described here.
Table of Contents #
Getting started #
Packages you need is, keg_annotation, keg_generator(this package), build_runner, and sqflite (or sqflite_common_ffi).
> flutter pub add keg_annotation dev:keg_generator, dev:build_runner
> flutter pub add sqflite
After create dart file like sample on top,
following command will generate .g.dart file.
> dart run build_runner build
Or if you want to generate automatically every time when code is fixed,
> dart run build_runner watch
Example #
Flutter example project is in github repository.
Limitations #
Table classes must have integer id field and it's SQLite type is INTEGER PRIMARY KEY AUTOINCREMENT.
Field types of Table classes are limited as follows,
| Field type | SQLite Data Type |
|---|---|
| int | INTEGER |
| double | REAL |
| bool | INTEGER |
| String | TEXT |
| DateTime | INTEGER |
| enum | TEXT |
Optional(nullable) type is not supported, except table relationships.
List and any collection types are not supported, except table relationships.
On migration, only followings are allowd.
- Add field on table class
- Add table class
Features #
One to Many Relationship #
Following is sample code to define one to many relationship, that one Category to many Items.
@table
class Category {
int id;
String name;
@BackLink(to: "category", order: "name", descendant: true)
List<Item> itemList;
Category(this.name, {this.id = 0, this.itemList = const []});
Map<String, Object?> toSqlMap() => _$CategoryHelper.toSqlMap(this);
factory Category.fromSqlMap(Map<String, Object?> map) =>
_$CategoryHelper.fromSqlMap(map);
}
@table
class Item {
int id;
String name;
Category? category;
Item(this.name, {this.category, this.id = 0});
Map<String, Object?> toSqlMap() => _$ItemHelper.toSqlMap(this);
factory Item.fromSqlMap(Map<String, Object?> map) =>
_$ItemHelper.fromSqlMap(map);
}
@KegDatabase(tables: [Category, Item])
class AppDatabase extends _$AppDatabase {
@override
Future<String> getPathToOpen() async {
return 'one2many.db';
}
@override
Future<void> onConfigure(Database db) async {
await db.execute('PRAGMA foreign_keys = ON');
}
}
If you want foreign key constraints, set on onCofigure method.
Note that Item has Category field and it must be optional.
And category must be optional parameter on Item constructor.
(In order to support back link and avoid infinite recursive call between link and back link)
Back link can be defined if you require, with @BackLink annotation.
to parameter of BackLink is field name of Item
that to wchich link this back link is.
order parameter to define order of item lists by field name of Item.
It is optional and default order is by id.
If descendant parameter is true, ordered by descendant order.
It is optional and default is ascendant order.
Note back link is to acquire by query methods.
registerCategory do nothing on itemList.
Sample code for CRUD operations are,
final cat = Category('pen');
await appdb.registerCategory(cat);
final item1 = Item('ballpoint pen', category: cat);
await appdb.registerItem(item1);
final batch4 = appdb.batch();
batch4.queryCategory();
batch4.queryItem();
final result4 = await batch4.commit();
await appdb.deleteItemByIds([item1]);
await appdb.deleteCategoryByIds([cat]);
Many to Many Relationship #
Following is sample code to define many to many relationship.
To define many to many, middle table is required.
@table
class Order {
int id;
String user;
@ManyToMany(middle: OrderToItem, self: 'order', target: 'item',
order: 'name', descendant: true)
List<Item> itemList = [];
@ManyToMany(middle: OrderToItem, self: 'order', target: 'item',
order: 'name', descendant: true)
List<Item> itemList2 = [];
Order(this.user, {this.id = 0});
Map<String, Object?> toSqlMap() => _$OrderHelper.toSqlMap(this);
factory Order.fromSqlMap(Map<String, Object?> map) =>
_$OrderHelper.fromSqlMap(map);
}
@table
class Item {
int id;
String name;
@BackLink(to: 'itemList')
List <Order> orderList = [];
Item(this.name, {this.id = 0});
Map<String, Object?> toSqlMap() => _$ItemHelper.toSqlMap(this);
factory Item.fromSqlMap(Map<String, Object?> map) =>
_$ItemHelper.fromSqlMap(map);
}
@table
class OrderToItem {
int id;
Order? order;
String field;
Item? item;
OrderToItem({required this.field, this.order, this.item, this.id = 0});
Map<String, Object?> toSqlMap() => _$OrderToItemHelper.toSqlMap(this);
factory OrderToItem.fromSqlMap(Map<String, Object?> map) =>
_$OrderToItemHelper.fromSqlMap(map);
}
@KegDatabase(tables: [Order, Item, OrderToItem])
class AppDatabase extends _$AppDatabase {
@override
Future<String> getPathToOpen() async {
// Implement your logic to get the database path
return 'many2many.db';
}
@override
Future<void> onConfigure(Database db) async {
await db.execute('PRAGMA foreign_keys = ON');
//return super.onConfigure(db);
}
}
Middle table class OrderToItem has field named field,
value of which is field name of Order. (itemList or itemList2 in this sample)
BackLink annotation is same as one to many relationship.
On ManyToMany annotation, middle parameter of is table class in middle.
self parameter is field name in middle table class which holds source instance.
target parameter is field name in middle table class which holds target instance.
Back link is for query methods, so registerItem do nothing on orderList.
On the other hand, registerOrder records Order-Item relationship into SQLite file.
Acquire by query method on many to many relation and back link is one way.
If you acquire Order by queryOrder, Order.itemList is acquired, but
Order.itemList[n].orderList is always empty.
And if you acquire Item by queryItem, Item.orderList is acquired, but
Item.orderList[n].itemList is always empty.
Otherwise cause infinite recursive loop.
Migration #
Migration supported is only add field to table class or add table class to database.
Following is sample code for migration.
Before migtation:
@table
class ItemInfo {
int id;
@unique
String name;
ItemInfo(
this.name, {
this.id = 0,
});
Map<String, Object?> toSqlMap() => _$ItemInfoHelper.toSqlMap(this);
factory ItemInfo.fromSqlMap(Map<String, Object?> map) =>
_$ItemInfoHelper.fromSqlMap(map);
}
@KegDatabase(tables: [ItemInfo], schemaVersion: 1)
class AppDatabase extends _$AppDatabase {
@override
Future<String> getPathToOpen() async {
// Implement your logic to get the database path
return 'migration1.db';
}
}
After migration:
@table
class User {
int id;
String name;
User(this.name, [this.id = 0]);
Map<String, Object?> toSqlMap() => _$UserHelper.toSqlMap(this);
factory User.fromSqlMap(Map<String, Object?> map) =>
_$UserHelper.fromSqlMap(map);
}
enum Color { red, green, blue }
@table()
class ItemInfo {
int id;
@unique
String name;
int stock;
@index
Color color;
double weight;
bool isActive;
DateTime created = DateTime.now();
ItemInfo(
this.name, {
required this.weight,
required this.color,
this.stock = 0,
this.isActive = true,
this.id = 0,
});
Map<String, Object?> toSqlMap() => _$ItemInfoHelper.toSqlMap(this);
factory ItemInfo.fromSqlMap(Map<String, Object?> map) =>
_$ItemInfoHelper.fromSqlMap(map);
}
@KegDatabase(tables: [User, ItemInfo], schemaVersion: 2)
class AppDatabase extends _$AppDatabase {
@override
Future<String> getPathToOpen() async {
// Implement your logic to get the database path
return 'migration1.db';
}
}
This sample adds fields in ItemInfo class, and add User class.
You also need to update @KegDatabase to increment schemaVersion.
To generate migration code,
keg_generator needs to know which field is added to table class.
keg_generator refers to previouly generated .g.dart file to find fields.
class _$ItemInfoHelper {
...
static final v1ColumnList = ['id', 'name', 'weight'];
...
}
But when you need to delete generated code and re-generate from scratch,
migration information will be lost.
To avoid the situation, you cann copy vnColumnList to table class,
then keg_generator refers it.
@table
class ItemInfo {
static final v1ColumnList = ['id', 'name', 'weight'];
int id;
String name;
int stock;
Color color;
double weight;
bool isActive;
DateTime created = DateTime.now();
ItemInfo(
this.name, {
required this.weight,
required this.color,
this.stock = 0,
this.isActive = true,
this.id = 0,
});
Map<String, Object?> toSqlMap() => _$ItemInfoHelper.toSqlMap(this);
factory ItemInfo.fromSqlMap(Map<String, Object?> map) =>
_$ItemInfoHelper.fromSqlMap(map);
}
Transaction and Batch #
Transaction is supported as same as sqflite.
final user1 = User('Mike');
final user2 = User('Jack');
await appdb.transaction((txn) async {
await txn.registerUser(user1);
await txn.registerUser(user2);
});
Batch is also same.
final batch1 = appdb.batch();
batch1.queryUser();
final result1 = await batch1.commit();
Note when noResult parameter of batch commit is true,
registerUser throws StateError when id is 0.
I added onCommit fuction parameter on batch methods.
final user1 = User('Mike');
final batch1 = appdb.batch();
batch1.registerUser(user1, (noResult, object) async {
expect(noResult, null);
expect(object, isA<int>());
expect(user1.id, object);
return object;
});
batch1.queryUser(onCommit: (noResult, object) async {
expect(noResult, null);
expect(object, isA<List<User>>());
final list = object as List<User>;
expect(list.length, 1);
expect(list[0].name, user1.name);
return object;
});
await batch1.commit();
onCommit fuction is called after batch commit.
First parameter noResult of onCommit function is
as same as noResult parameter of batch commit.
Second parameter object is corresponding return value of batch commit.
Index #
SQLite index is to improve performance on query function.
@table
class User {
int id;
@unique
String name;
@Index(unique: false, descendant: true)
DateTime updated = DateTime.now();
@index
String contact;
User(this.name, {this.contact = '', this.id = 0});
Map<String, Object?> toSqlMap() => _$UserHelper.toSqlMap(this);
factory User.fromSqlMap(Map<String, Object?> map) =>
_$UserHelper.fromSqlMap(map);
}
This sample makes 3 indexes, user_name_idx, user_updated_idx and user_contact_idx.
Normal index by @index annotation, unique index by @unique annotation.
If descendant index required, use @Index annotation with descendant to true.
Ignore annotation #
If you want to add fields in table class that do not want to store in SQLite,
use @ignore annotation.
@table
class User {
int id;
String name;
@ignore
String sir;
User(this.name, {this.sir = '', this.id = 0});
Map<String, Object?> toSqlMap() => _$UserHelper.toSqlMap(this);
factory User.fromSqlMap(Map<String, Object?> map) =>
_$UserHelper.fromSqlMap(map);
}
On this sample id and name is stored to SQLite but sir is not.
Use with json_serializable and freezed annotation. #
It is able to use json_serializable and freezed annotation on table class if you want.
Sample with json_serializable.
@table
@JsonSerializable()
class Category {
int id;
String name;
@BackLink(to: "category", order: "name")
@JsonKey(fromJson: _itemListFromJson, toJson: _itemListToJson)
final List<Item> itemList;
Category(this.name, {this.id = 0, this.itemList = const []});
Map<String, Object?> toSqlMap() => _$CategoryHelper.toSqlMap(this);
factory Category.fromSqlMap(Map<String, Object?> map) =>
_$CategoryHelper.fromSqlMap(map);
factory Category.fromJson(Map<String, dynamic> json)
=> _$CategoryFromJson(json);
Map<String, dynamic> toJson() => _$CategoryToJson(this);
static List<Item> _itemListFromJson(List<dynamic> json) {
return json.map((e) => Item.fromJson(e as Map<String, dynamic>)).toList();
}
static List<dynamic> _itemListToJson(List<Item> itemList) {
return itemList.map((e) => e.toJson()).toList();
}
}
Sample withjson_serializable and freezed.
@table
@freezed
@JsonSerializable()
class Category with _$Category {
@override
final int id;
@override
final String name;
@override
@BackLink(to: "category", order: "name")
@JsonKey(fromJson: _itemListFromJson, toJson: _itemListToJson)
final List<Item> itemList;
const Category(this.name, {this.id = 0, this.itemList = const []});
//const Category(this.name, {this.id = 0});
Map<String, Object?> toSqlMap() => _$CategoryHelper.toSqlMap(this);
factory Category.fromSqlMap(Map<String, Object?> map) =>
_$CategoryHelper.fromSqlMap(map);
factory Category.fromJson(Map<String, dynamic> json)
=> _$CategoryFromJson(json);
Map<String, dynamic> toJson() => _$CategoryToJson(this);
static List<Item> _itemListFromJson(List<dynamic> json) {
return json.map((e) => Item.fromJson(e as Map<String, dynamic>)).toList();
}
static List<dynamic> _itemListToJson(List<Item> itemList) {
return itemList.map((e) => e.toJson()).toList();
}
}
When use with freezed, it is not able to set id automatically.
You must set id manually.
var item = Item('ballpoint pen', category: cat);
final itemId = await appdb.registerItem(item);
item = item.copyWith(id: itemId);
Generated Codes Detail #
Following is sample code to define simple database.
import 'package:keg_annotation/keg_annotation.dart';
import 'package:sqflite/sqflite.dart';
part 'sole_table_test.g.dart';
@table
class User {
int id;
String name;
User(this.name, [this.id = 0]);
Map<String, Object?> toSqlMap() => _$UserHelper.toSqlMap(this);
factory User.fromSqlMap(Map<String, Object?> map) =>
_$UserHelper.fromSqlMap(map);
}
@KegDatabase(tables: [User])
class AppDatabase extends _$AppDatabase {
@override
Future<String> getPathToOpen() async {
return 'app.db';
}
}
With sample code above, classes generated are,
- _$AppDatabase
- _$AppDatabaseExecutor
- _$AppDatabaseTransactionWrapper
- _$AppDatabaseBatchWrapper
- _$UserHelper
where AppDatabase and User is classes user defined.
_$AppDatabase #
This is main class you use.
This class is generated per database class.
abstract class _$AppDatabase implements _$AppDatabaseExecutor
Fields
late Database database
Object of sqflite Database class.
late final _$TableHelper tableHelper
Instance of table helper class.
CRUD methods
For each table classes, follwing methods are defined.
Future<int> registerTable(Table item)
Insert or update record to SQLite table.
Always update every columns in record.
If id is 0, record is inserted.
If id is not 0, REPLACE statement is used.
If record with the id exists in the table, is updated.
If record with the id does not exist, is inserted.
Future<List<Table>> queryTable({String? where, List<Object?>? whereArgs, String? orderBy, int? limit, int? offset, List<({String table, String column})> dropKeys = const [],})
Query records from table and returns list of table class objects.
Most parameters are same as sqflite query.
dropKeys parameter is mainly for internal use.
If you have linked tables and do not need link information,
you can improve performance of query.
final dropKey = (
table: appdb.itemHelper.tableName,
column: appdb.itemHelper.column.category,
);
final result = await appdb.queryItem(dropKeys: [dropKey]);
If column specified is mandatory parameter of constructor, Exception occurs.
Future<Table?> getTable(int id, [ List<({String table, String column})> dropKeys = const [],])
Acquire table class instance by id.
If not exist, returns null.
Future<int> deleteTable({String? where, List<Object?>? whereArgs})
Delete records from table.
Returns number of records deleted.
Future<int> deleteTableByIds(List<Table> itemsList)
Delete records by instance of table class. Returns number of records deleted.
database methods
int get schemaVersion
Acquire schema version specified in @KegDatabase annotation.
Future<String> getPathToOpen()
Acquire path of SQLite file.
If you want to use default directory, return value can be relative path.
Future<void> open()
Open or create SQLite file.
Future<void> openInMemory()
Open or create SQLite file in memory.
Future<void> close()
Close SQLite file.
Future<void> onConfigure(Database db)
Call back function on configure database. Called on open.
Future<void> onCreate(Database db, int version)
Call back function on create database.
Future<void> onUpgrade(Database db, int oldVersion, int newVersion)
Call back function on upgrade database.
Future<void> onDowngrade(Database db, int oldVersion, int newVersion)
Call back function on downgrade database.
keg_generator does not support downgrade, so UnimplementedError occurs
if downgrade happens.
Future<void> onOpen(Database db)
Call back function on open database.
Other methods
These are mainly pass through methods to sqflite Database class.
Future<T> transaction<T>(Future<T> Function(_$AppDatabaseTransactionWrapper txn) action, {bool? exclusive,})
Start transaction.
Future<T> readTransaction<T>(Future<T> Function(_$AppDatabaseTransactionWrapper txn) action,)
Start read transaction.
_$AppDatabaseBatchWrapper batch()
Start batch.
String get path
Actual full path of SQLite file.
bool get isOpen
Database is open or not.
Future<void> execute(String sql, [List<Object?>? arguments])
Execute SQLite statement.
Future<int> rawInsert(String sql, [List<Object?>? arguments])
Insert.
Future<int> insert(String table, Map<String, Object?> values, { String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm,})
Insert.
Future<List<Map<String, Object?>>> query(String table, {bool? distinct, List<String>? columns, String? where, List<Object?>? whereArgs, String? groupBy, String? having, String? orderBy, int? limit, int? offset,})
Query records and return list of map.
Future<List<Map<String, Object?>>> rawQuery(String sql, [List<Object?>? arguments,])
Query records and return list of map.
Future<QueryCursor> queryCursor(String table, {bool? distinct, List<String>? columns, String? where, List<Object?>? whereArgs, String? groupBy, String? having, String? orderBy, int? limit, int? offset, int? bufferSize,})
Query records and return list of map.
Future<QueryCursor> rawQueryCursor(String sql, List<Object?>? arguments, {int? bufferSize, })
Query records and return list of map.
Future<int> update(String table, Map<String, Object?> values, { String? where, List<Object?>? whereArgs, ConflictAlgorithm? conflictAlgorithm,})
Update.
Future<int> rawUpdate(String sql, [List<Object?>? arguments])
Update.
Future<int> delete(String table, {String? where, List<Object?>? whereArgs})
Delete
Future<int> rawDelete(String sql, [List<Object?>? arguments])
Delete
_$TableHelper #
This helper class is generated per each table class.
class _$TableHelper
Fields
final String tableName
table name
final String tableName = '"user"';
column
Dart record type which holds names of column of table.
final column = (id: '"id"', name: '"name"');
final Map<String, String> columnTypes
Column data types.
Key is name of column, and value is SQLite data type.
final columnTypes = {
'id': 'INTEGER PRIMARY KEY AUTOINCREMENT',
'name': 'TEXT NOT NULL DEFAULT \'\'',
};
final List<String> columnList
List of column names.
final columnList = ['id', 'name'];
static final List<String> v1ColumnList
List of column names by each version. (v1ColumnList, v2ColumnList...)
static final v1ColumnList = ['id', 'name'];
final Map<int, List<String>> columnListByVersion
Map of column list by each version
final columnListByVersion = {1: v1ColumnList, 2: v2ColumnList};
_$AppDatabase appdb;
Link to AppDatabase
CRUD Methods
Future<int> register(Table item, _$AppDatabaseExecutor db)
Actual implementation of AppDatabase registerTable.
void registerBatch(Table item, _$AppDatabaseBatchWrapper batch)
Actual implementation of AppDatabase registerTable on batch.
Future<List<Table>> query(_$AppDatabaseExecutor db, {String? where, List<Object?>? whereArgs, String? orderBy, int? limit, int? offset, List<({String table, String column})> dropKeys = const [],})
Actual implementation of AppDatabase queryTable.
void queryBatch(_$AppDatabaseBatchWrapper batch, {String? where, List<Object?>? whereArgs, String? orderBy, int? limit, int? offset, List<({String table, String column})> dropKeys = const [],})
Actual implementation of AppDatabase queryTable on batch.
Future<Table?> get(int id, _$AppDatabaseExecutor db, [List<({String table, String column})> dropKeys = const [],])
Actual implementation of AppDatabase getTable.
void getBatch(int id, _$AppDatabaseBatchWrapper batch, [List<({String table, String column})> dropKeys = const [],])
Actual implementation of AppDatabase getTable on batch.
Future<int> delete(_$AppDatabaseExecutor db, {String? where, List<Object?>? whereArgs,})
Actual implementation of AppDatabase deleteTable.
Future<void> deleteBatch(_$AppDatabaseBatchWrapper batch, {String? where, List<Object?>? whereArgs,})
Actual implementation of AppDatabase deleteTable on batch.
Future<int> deleteByIds(_$AppDatabaseExecutor db, List<Table> itemList)
Actual implementation of AppDatabase deleteTableByIds.
void deleteByIdsBatch(_$AppDatabaseBatchWrapper batch, List<Table> itemList)
Actual implementation of AppDatabase deleteTableByIds on batch.
Database Methods
Future<void> onCreate(int version, { DatabaseExecutor? db, Batch? batch,})
Call back function on create table.
Future<void> onUpgrade(int oldVersion, int newVersion, { DatabaseExecutor? db, Batch? batch,})
Call back function on upgrade table.
Map Methods
static Map<String, Object?> toSqlMap(Table item)
Convert table class instance to Map.
static Table fromSqlMap(Map<String, Object?> map)
Convert Map to table class instance.
Other Methods
Future<List<Map<String, Object?>>> convertReferences(List<Map<String, Object?>> mapList, _$AppDatabaseExecutor db, List<({String table, String column})> dropKeys,)
Called from query method.
If this table is linked to other, additional query is executed here.
About dropKeys see query description in _$AppDatabase.
List<User> mapToObject(List<Map<String, Object?>> mapList)
Called from query method.
Finally covert from Map to table class instance including linked tables.
bool compareManyToMany(Table item1, Set<int> set2)
Called from register method if many to many relation exists.
Compare if list of ids are equal or not.
Future<void> registerManyToMany(Order item, _$AppDatabaseExecutor executor,)
Called from register method if many to many relation exists.
Register records of in the middle table.
_$AppDatabaseExecutor #
abstract class _$AppDatabaseExecutor extends DatabaseExecutor
Interface class for _$AppDatabase and transaction. Extends sqflite DatabaseExecutor
_$AppDatabaseTransactionWrapper #
class _$AppDatabaseTransactionWrapper implements _$AppDatabaseExecutor
Wrapper class for sqflite Transaction
_$AppDatabaseBatchWrapper #
class _$AppDatabaseBatchWrapper implements Batch
Wrapper class for sqflite Batch
Additional information #
If you need to see full code of these samples check keg_tenerator test codes.
If you have any problems and questions, feel free to post issues to github.
Pull requests are also welcome.