{"id":15797144,"url":"https://github.com/juhaku/juhakudb","last_synced_at":"2025-03-31T19:43:33.162Z","repository":{"id":57733749,"uuid":"84716424","full_name":"juhaku/juhakudb","owner":"juhaku","description":"Spring DATA like Android ORM Library for SQLite dabaseses","archived":false,"fork":false,"pushed_at":"2018-03-17T10:13:09.000Z","size":33444,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-12T00:23:49.121Z","etag":null,"topics":["andoird","android-database","android-databinding","android-orm","annotations","criteria-api","database-management","filter-api","javax-persistence-annotation","juhakudb","manage-databases","orm","spring-data","sqlite-database"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juhaku.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-12T10:10:31.000Z","updated_at":"2023-05-03T18:55:22.000Z","dependencies_parsed_at":"2022-08-24T11:20:20.690Z","dependency_job_id":null,"html_url":"https://github.com/juhaku/juhakudb","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juhaku%2Fjuhakudb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juhaku%2Fjuhakudb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juhaku%2Fjuhakudb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juhaku%2Fjuhakudb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juhaku","download_url":"https://codeload.github.com/juhaku/juhakudb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246531984,"owners_count":20792735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["andoird","android-database","android-databinding","android-orm","annotations","criteria-api","database-management","filter-api","javax-persistence-annotation","juhakudb","manage-databases","orm","spring-data","sqlite-database"],"created_at":"2024-10-05T00:04:21.340Z","updated_at":"2025-03-31T19:43:33.130Z","avatar_url":"https://github.com/juhaku.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JuhakuDB current release: 2.1.3\nSpring DATA like Android ORM Library for SQLite dabaseses\n\n## Introduction\nJuhakuDb is created to provide advanced database management with simplicity in mind as well. This library implements \nSpring DATA and Hibernate like API for database management. Key features are annotation based ORM handling as well \nas filter based criteria API supporting normal SQL as well in case of necessarity. \n\nThis library supports automatic database creation, schema updates as well as rollbacks. With easy to use and fast to \nconfigure API it gives you levarage for managing databases in Android devices. \n\nWith JuhakuDb managing SQLite database is fast and simply and it is quick to adapt on using the library. \nLibrary also provides annotated repositories with basic CRUD functionalities.\n\n### ORM handling\nAnnotation based ORM handling works with same javax persistence annotations as Hibernate would work. \nIf Hibernate is something you are already familiar with this should not be a too much to take. \nCurrently supported javax persistence annotations are ManyToOne, ManyToMany, OneToOne, OneToMany, Entity, Id, \nTable, Column, Index, UniqueConstraint and Transient.\n\nAnnotated classes will be mapped accordingly and database tables will be automatically created with \nrelations by the annotations.\n\n### Filter based criteria API\nFilter based criteria API is fairly similar to Spring DATA's representation of criteria API but in \ncomparison this is a lot simpler and easier to use. It provides easy way to create join queries from \nmultiple tables with easy to use filter chain syntax. This syntax allows you to create complex queries \nwithout writing single line of sql.\n\n## Installation\n\nCurrently available from central repository.\n\n### Maven\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.juhaku\u003c/groupId\u003e\n    \u003cartifactId\u003ejuhaku-db\u003c/artifactId\u003e\n    \u003cversion\u003e2.1.3\u003c/version\u003e\n    \u003ctype\u003eaar\u003c/type\u003e\n\u003c/dependency\u003e\n```\n\n### Gradle\n\n```java\n    compile ('io.github.juhaku:juhaku-db:2.1.3@aar') {\n        transitive = true\n    }\n```\n\nIt is crusial to set transitive to true so dependant javax persistence annotations will be loaded as well. \nIf not added you need to manually add the annotation dependency.\n\n## Usage\n* Database manager\n* Annotations\n* Repositories\n* Filter criteria API\n\n### Database manager\nWith following snippet you can create database manager. This is the core of the JuhakuDb which provides you \nconfiguration possibilities for database. Initializing of this class will automatically create your database \nby the given configurations. This class should be initialized in super class or in application level and \nnever should be initialized more \nthan once in same runtime context for avoiding unwanted behaviour.\n```java\nDatabaseManager dbManager = new DatabaseManager(context, new DatabaseConfigurationAdapter() {\n    @Override\n    public void configure(DatabaseConfiguration configuration) {\n        configuration.getBuilder().setBasePackages(\"db.juhaku.dbdemo.model\", \"db.juhaku.dbdemo.bean\")\n                .setVersion(1) // Updating this version will cause database to update.\n                .setName(\"dbtest.db\")\n                .setMode(SchemaCreationMode.UPDATE)\n                .setAllowRollback(false)\n                .setRollbackHistorySize(5) // Number of back-ups allowed to take from database schema.\n                .setEnableAutoInject(true) // This will enable annotation based repository injection.\n                .setBaseRepositoryClass(CustomCoreRepository.class); // Custom base class for repositories.\n    }\n});\n```\nContext here is a Android Context. Other attributes are quite self explanatory. They are documented so \nreading javadocs will give you more knowledge on them as well.\n\n### Annotations\nCurrently available javax persistence annotation.\n\n|Annotation| Supported attributes| Description| \n|------------------------------|---| --- |\n|Table| name, indexes, uniqueConstraints | Define table name for class. Not required as name can be resolved from class itself. Indexes as well as unique constraints contain list of constraints for the table.|\n|Index| name, columnList | Defines index for given column list in table. Name is name of the index constraint.|\n|UniqueConstraint| name, columnNames | Defines unique constraint index for given list of columns in table. Name is name of the constraint.|\n|Column|name | Define column name for class attribute, can only be added to non-relation field.|\n|Entity|-- | Marks class as entity of database.|\n|Id|-- | Define primary key field for database table, id is auto generated per table (way of the Android). Only numeric value can be id currently. Id annotated column will map to \"_id\" column in database. This is default to Android.|\n|ManyToMany|fetch | Defines many to many relation. Fetch attribute can have value FetchType.LAZY or FetchType.EAGER. Default: FetchType.LAZY.|\n|ManyToOne| fetch | Defines many to one relation. Fetch attribute can have value FetchType.LAZY or FetchType.EAGER. Default: FetchType.EAGER.|\n|OneToMany| fetch | Defines one to many relation. Fetch attribute can have value FetchType.LAZY or FetchType.EAGER. Default: FetchType.LAZY.|\n|OneToOne| fetch | Defines one to one relation. Fetch attribute can have value FetchType.LAZY or FetchType.EAGER. Default: FetchType.EAGER.|\n|Transient| -- | Marks class field as transient which will not be saved to database.|\n\nAll operations are cascading and storing will return stored item with populated database id.\nFetch can be either EAGER or LAZY. This is defined by FetchType enum that can be provided as attribute for \nrelation annotations. Lazy will not load relation from database and it need to be manually loaded. EAGER \nwill automatically fetch referenced relation from database along with original item. However using EAGER \nis not recommended behaviour.\n\nCurrently available own annotations.\n\n|Annotation| Supported attributes| Description|\n|------------------------------|---| --- |\n|Repository| value | Marks interface as repository. Value can be implementing class of repository.|\n|Inject| -- | Marks class field as injectable for automatic repository injection. Type of the field must be a repository annotated with Repository annotation.|\n\n\nAnnotated table examples with minimal annotation configuration.\n```java\n@Entity\npublic class Class {\n\n    @Id\n    private Long id;\n\n    private String name;\n\n    @ManyToOne\n    private Person person;\n\n    @OneToOne\n    private Teacher teacher;\n    \n    // ... getters and setters\n}\n\n@Entity\npublic class Book {\n\n    @Id\n    private Long id;\n\n    private String name;\n\n    @ManyToMany\n    private List\u003cPerson\u003e persons;\n\n    @ManyToMany\n    private List\u003cLibrary\u003e libraries;\n    \n    // ... getters and setters   \n}\n\n@Entity\npublic class Person {\n\n    @Id\n    private Long id;\n\n    private String name;\n\n    private String nickName;\n\n    @ManyToMany\n    private List\u003cBook\u003e books;\n\n    @OneToMany\n    private List\u003cClass\u003e classes;\n    \n    // ... getters and setters   \n}\n```\n\nDatabase table names are resolved from the class name if class has @Entity annotation. Table name can \nalso be resolved from @Table annotation if @Table annotation has name attribute provided. Column names \nare defined by the @Column annotation's name attribute. If name attribute is not defined name will be \nresolved from field's name.\n\nTable annotation can contain along with name information array of indexes and unique constraints needed\nto add to the table. Below is an example of how to configure them as well.\n\nClasses can also define fetch in the relation annotation. All store operations and delete operations \nare cascading by default. Just to make your life easier without calling different repositories for \nsimple delete operation or store operation.\n\nRelations still need to be defined in both sides of tables.\n\nAnnotated class example with more configuration. \n```java\n@Entity\n@Table(name = \"authorities\", indexes = {@Index(name = \"value_idx\", columnList = \"value,name\"), @Index(name = \"level_idx\", columnList = \"level\", unique = true)},\nuniqueConstraints = {@UniqueConstraint(name = \"unique_permission_ctx\", columnNames = {\"permission\"})})\npublic class Authority {\n\n    @Id\n    private Long id;\n\n    @Column(name = \"value\")\n    private String value;\n    \n    @Column(name = \"name\")\n    private String name;\n    \n    @Column(name = \"level\")\n    private Integer level;\n    \n    @Column(name = \"permission\")\n    private String permission;\n\n    @ManyToMany(fetch = FetchType.LAZY)\n    private List\u003cPerson\u003e person;\n    \n    // ... getters and setters\n}\n```\n\n### Repositories\n\nThere are 3 ways to work with repositories since version 1.3.0. All repositories must be annotated \nwith @Repository annotation. If value is not provided the default implementation is being used. If \nvalue is provided inside the @Repository annotation then that is being used as implementing class \nfor the repository interface.\n\nAs shown below currently by writing following snippet is enough to create instance of a repository. \nAbsolutely no implementation is required as long as de default functionality is enough.\n```java\n@Repository\npublic interface PersonRepository extends SimpleRepository\u003cLong, Person\u003e {}\n```\n\n#### Default implementation\n\nBy default there is a implementation for following methods. Javadocs omitted from example.\n```java\npublic interface SimpleRepository\u003cK, T\u003e {\n\n    T store(T object);\n\n    List\u003cT\u003e storeAll(Collection\u003cT\u003e objects);\n\n    int remove(K id);\n\n    int removeAll(Collection\u003cK\u003e ids);\n\n    T findOne(final K id);\n\n    T findOne(Filter\u003cT\u003e filter);\n\n    List\u003cT\u003e findAll();\n\n    List\u003cT\u003e find(Filter\u003cT\u003e filter);\n\n    \u003cE\u003e E find(Query query, ResultTransformer\u003cE\u003e resultTransformer);\n\n}\n```\n\n#### Custom base repository\n\nHowever if default functionality is not enough you are able to create custom base repository as well. \nThis is particularly useful if custom behaviour is required for all the repositories. Following steps \nwill guide you through how to create a custom base repository that is being used as base for all \nrepositories except those with own implementation.\n\nCreate an interface called custom core repo. It could be anything you wish but the important part is \nto extend simple repository. That gives you the default functionalities available in simple repository. \nThere is additional method called exists. This method will be available to all repositories that are using \nthe default repository.\n```java\npublic interface CustomCoreRepo\u003cK, T\u003e extends SimpleRepository\u003cK, T\u003e {\n    boolean exists(K id);\n}\n```\n\nNow create implementation for the custom core repository. We extend the default repository called \nsimple android repository. We implement the new functionality from the new custom core repo. Find \none is function in the default simple android repository and K stands for key which is Id class of \nthe entity and T stands for the entity class itself.\n```java\npublic class CustomCoreRepository\u003cK, T\u003e extends SimpleAndroidRepository\u003cK, T\u003e implements CustomCoreRepo\u003cK, T\u003e {\n\n    public CustomCoreRepository(EntityManager entityManager, Class\u003cT\u003e persistentClass) {\n        super(entityManager, persistentClass);\n    }\n\n    @Override\n    public boolean exists(K id) {\n        Log.d(getClass().getName(), \"checking does item exists\");\n        return findOne(id) != null;\n    }\n}\n```\n\nTo this point we have created custom core repository. In order to use it we just need to do 2 things.\n\nFirst we create a repository that is using the custom core repo we created. As told previously this is \nenough to create a repository instance. But the difference is that we are extending custom core repo \ninstead of simple repository.\n```java\n@Repository\npublic interface PermissionRepository extends CustomCoreRepo\u003cLong, Permission\u003e {}\n```\n\nThen we tell database manager to use the cusom core repository instead of the simple android repository \nas the base of repositories by adding this line to database configuration adapter. Also shown above in \ndatabase manager section.\n```java\n.setBaseRepositoryClass(CustomCoreRepository.class); // Custom base class for repositories.\n```\n\n#### Old school repository\n\nThe third option is little bit old school. This can be useful if totally custom repository is required. \n\nFirst we create a repository interface with @Repository annotation. The main difference is that we provide \nimplementing class as attribute inside @Repository annotation and we do not extend any repository class.\n```java\n@Repository(BookRepositoryImpl.class)\npublic interface BookRepository {\n    Long storeBook(Book book);\n    List\u003cBook\u003e findBooks(Filters filters);\n}\n```\n\nThen we provide the implementation for the repository interface as follows. We implement the book repository \nand extend the default simple android repository.\n```java\npublic class BookRepositoryImpl extends SimpleAndroidRepository\u003cLong, Book\u003e implements BookRepository {\n\n    public BookRepositoryImpl(EntityManager em) {\n        super(em);\n    }\n    \n    @Override\n    public Long storeBook(Book book) {\n        return store(book);\n    }\n    \n    @Override\n    public List\u003cBook\u003e findBooks(Filters filters) {\n        return find(filters);\n    }\n}\n```\n\n#### Get instance of repository in application\n\nIf you have access to database manager you can always get an instance of repository by simple calling \ngetRepository method with the repository interface from database manager to your class.\n```java\nrepository = dbManager.getRepository(BookRepository.class);\n```\n\nMore robust solution is to use @Inject annotation. To enable this feature we need to add following \nconfiguration to database configuration adapter. Following will enable the feature.\n```java\n.setEnableAutoInject(true); // this will enable annotation based repository injection \n```\n\nThen we need to call explisitly following method in the class that the repository injection should be \ninitiated. This method can exists in super class of the class stack as well. It still will go through \nthe class stack and inject all repositories that is marked with @Inject annotation.\n\nThis behaviour is necessary as Android does not have default functionality to provide stack of running \nclasses or to follow opening fragments or other classes. Only opening activities can be retrieved. So we \ncannot execute this process at the background. But good thing is that you can hide this at the top of \nyour activity stack or fragment stack.\n```java\ngetDbManager().lookupRepositories(this); // this will initiate repository lookup injection.\n\n// ... then later in code class stack we can add @Inject annotation to any repository.\n@Inject\nprivate PersonRepository personRepository;\n```\n\n##### The quick quide of annotation based repository injection comes as follows. \n\n1. There should be only one database manager in super level of your Android application.\n2. Call lookupRepositories(this) in Activity or Fragment or inside other object that repositories is wished to be injected and is accessible to DatabaseManager. This method call should appear in super level of your component hierarchy.\n3. Use Inject annotation in any child or parent component where lookupRepositories(this) is already called.\n\n\n### Filter criteria API\n\nSince 2.1.0 the old criteria api is being changed to new predicate builder criteria api. This enables\nyou to write more robust queries as well as SQL predicates without mentioning the size of the code. \nSince 2.1.0 you only need write half as much as previously with more expressive api.\n\nFor example compare statements below. Above one is how it is written currently and below you can \nfind old substitute.\n```java\nbuilder.in(\"this.name\", \"john\", \"kimmo\").not().eq(\"name\", \"kim\");\npredicates.add(Predicate.in(\"this.name\", \"john\", \"kimmo\")).add(Predicate.not(Predicate.eq(\"name\", \"kim\")));\n```\n\nIf joins or predicates are prefixed with \"this\" or left without prefix it will be mapped to the root entity \nof repository.\n```java\nroot.join(\"this.rooms\", JoinMode.LEFT_JOIN)\nbuilder.in(\"this.name\", \"john\", \"kimmo\").not().eq(\"name\", \"kim\");\n```\n\nIn predicate using .id or ._id as column name is mapped as to be equal and both will refer to primary key column. \nIn Android primary key column is mapped to \"_id\" column thus we made this convention for cleaner code.\n```java\nbuilder.eq(\"this.id\", \"1\");\nbuilder.eq(\"this._id\", \"1\");\n```\n\nYou can also create custom queries by writing normal sql and then use result transformer to transform custom result. \n```java\nQuery query = new Query(\"select persons_id, books_id from books_persons\", null);\nreturn find(query, new ResultTransformer\u003cList\u003cString\u003e\u003e() {\n    @Override\n    public List\u003cString\u003e transformResult(List\u003cResultSet\u003e resultSets) {\n        List\u003cString\u003e strings = new ArrayList\u003c\u003e();\n        for (ResultSet res : resultSets) {\n            String val = \"\";\n            for (Result result : res.getResults()) {\n                val = val.concat(result.getColumnName()).concat(\"=\").concat(result.getColumnValue().toString());\n            }\n            strings.add(val);\n        }\n\n        return strings;\n    }\n});\n```\n\nJoins work little different from version 1.2.0 forward. Method join will return new root to the join target. \nE.g. in example below the first join will return root to rooms object and the second will return teacher root \nfrom rooms.\nSo be careful when creating joins.\n\nBelow are couple of more advanced examples.\n```java\nFilters filters = new Filters();\nfilters.add(new Filter\u003cPerson\u003e() {\n    @Override\n    public void filter(Root\u003cPerson\u003e root, PredicateBuilder builder) {\n        root.join(\"this.rooms\", \"r\", JoinMode.LEFT_JOIN).join(\"r.teacher\", \"t\", JoinMode.FULL_JOIN);\n        root.join(\"this.groups\", \"g\", JoinMode.INNER_JOIN);\n\n        builder.in(\"this.name\", \"matti\", \"kimmo\").not().eq(\"name\", \"lauri\");\n\n        Disjunction or = builder.disjunction();\n        or.eq(\"t.name\", \"laura\").eq(\"t.name\", \"minna\");\n\n        builder.conjunction().between(\"t.id\", 1, 3).not().isNull(\"t.name\");\n\n        builder.sort(Order.ASC, \"name\");\n        builder.setPageSize(20).setPage(1);\n    }\n});\n```\n\n```java\nFilters filters = new Filters();\nfilters.add(new Filter\u003cClassRoom\u003e() {\n    @Override\n    public void filter(Root\u003cClassRoom\u003e root, PredicateBuilder builder) {\n        root.join(\"persons\", \"p\", JoinMode.INNER_JOIN);\n        builder.setPage(2).setPageSize(10).sort(Order.DESC, \"name\", \"_id\").sort(Order.ASC, \"p._id\");\n    }\n});\n```\n\nJoins can also be chained to Filters object like this from version 1.2.0 forward. So there is no need to \ncall add method. It can be a personal decision whether to add multiple filters or use just one. The end \nresult is same.\n```java\nFilters filters = new Filters(new Filter\u003cClassRoom\u003e() {\n    @Override\n    public void filter(Root\u003cClassRoom\u003e root, PredicateBuilder builder) {\n        root.join(\"persons\", JoinMode.INNER_JOIN);\n    }\n}, new Filter\u003cClassRoom\u003e() {\n    @Override\n    public void filter(Root\u003cClassRoom\u003e root, PredicateBuilder builder) {\n        root.join(\"this.teacher\", \"t\", JoinMode.LEFT_JOIN);\n\n        builder.ge(\"id\", 1);\n    }\n}, new Filter\u003cClassRoom\u003e() {\n    @Override\n    public void filter(Root\u003cClassRoom\u003e root, PredicateBuilder builder) {\n        builder.eq(\"t.name\", \"tester\");\n    }\n});\n```\n\nBelow is an example of new function since 1.2.0. It is fetch method in root object. It is a same as join \nbut it will fetch the objects from the database along with the original objects. So there is no need to \ncreate additional query to fetch related objects manually nor use the EAGER fetch mode in relation annotations. \n\nAs you might have noted you can specify alias for join and as well as fetch method. This is an optional \nfunctionality and thus no necessary to provide. If alias is not provided it will be generated automatically. \nHowever if you use alias you can refer to object in queries with the alias. So without alias you cannot \nrefer to the table in queries and you cannot add predicates for the table.\n```java\nFilters filters = new Filters(new Filter\u003cPerson\u003e() {\n    @Override\n    public void filter(Root\u003cPerson\u003e root, PredicateBuilder builder) {\n        root.fetch(\"groups\", JoinMode.INNER_JOIN);\n        root.fetch(\"rooms\", \"r\", JoinMode.LEFT_JOIN);\n    }\n}, new Filter\u003cPerson\u003e() {\n    @Override\n    public void filter(Root\u003cPerson\u003e root, PredicateBuilder builder) {\n        builder.eq(\"this.name\", \"tester\");\n    }\n});\n```\n\nYet another invented real case scenario example.\n```java\nList\u003cLibrary\u003e libraries = libraryRepository.findLibraries(new Filters(new Filter() {\n    @Override\n    public void filter(Root root, PredicateBuilder builder) {\n        root.fetch(\"books\", JoinMode.INNER_JOIN).fetch(\"persons\", \"bp\", JoinMode.LEFT_JOIN);\n        root.fetch(\"roles\", JoinMode.LEFT_JOIN).fetch(\"person\", JoinMode.LEFT_JOIN);\n    }\n}, new Filter() {\n    @Override\n    public void filter(Root root, PredicateBuilder builder) {\n        builder.not().isNull(\"this.name\");\n    }\n}));\n```\n\nSince 2.1.0 there is the possibility to create SQL predicates for more advanced queries. Just if \nthe default functionality is not enough. See couple of hypotetical examples below.\n```java\nPermission p1 = permissionRepository.findOne(new Filter\u003cPermission\u003e() {\n    @Override\n    public void filter(Root\u003cPermission\u003e root, PredicateBuilder builder) {\n        builder.sqlPredicate(\"value = lower(?)\", \"PERMISSION 1\");\n    }\n});\n\nPermission p2 = permissionRepository.findOne(new Filter\u003cPermission\u003e() {\n    @Override\n    public void filter(Root\u003cPermission\u003e root, PredicateBuilder builder) {\n        builder.sqlPredicate(\"value = coalesce(?, ?)\", \"permission 2\", \"permission 3\");\n    }\n});\n```\n\nNow begin to use the library that rocks the Android's SQLite database.\n\n## Roadmap\n\n### 3.x release\n\nAdded support for stand-alone SQLite database as well.\n\n# Terms and conditions MIT\nCopyright 2017 juhaku\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuhaku%2Fjuhakudb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuhaku%2Fjuhakudb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuhaku%2Fjuhakudb/lists"}