{"id":15986887,"url":"https://github.com/miniconnect/holodb","last_synced_at":"2025-03-17T15:32:32.372Z","repository":{"id":63250982,"uuid":"311783128","full_name":"miniconnect/holodb","owner":"miniconnect","description":"Relational database - seemingly filled with random data","archived":false,"fork":false,"pushed_at":"2024-09-08T00:24:30.000Z","size":845,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-15T03:07:01.159Z","etag":null,"topics":["database","faker","java","mock","random-data-generation","rdbms","sql","testing","testing-tool"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/miniconnect.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-10T20:58:51.000Z","updated_at":"2024-09-08T00:24:34.000Z","dependencies_parsed_at":"2023-11-13T00:25:22.470Z","dependency_job_id":"e5c141c4-9c9f-4094-8fc9-4675207caa8d","html_url":"https://github.com/miniconnect/holodb","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miniconnect%2Fholodb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miniconnect%2Fholodb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miniconnect%2Fholodb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miniconnect%2Fholodb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/miniconnect","download_url":"https://codeload.github.com/miniconnect/holodb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221696212,"owners_count":16865376,"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":["database","faker","java","mock","random-data-generation","rdbms","sql","testing","testing-tool"],"created_at":"2024-10-08T03:05:36.905Z","updated_at":"2025-03-17T15:32:32.365Z","avatar_url":"https://github.com/miniconnect.png","language":"Java","readme":"# HoloDB - the on-the-fly relational database\n\nNo data generation.\nNo storage costs.\nNo migrations.\nStart from zero and immediately work with\nrealistic, arbitrarily large, fully queryable datasets.\nSimply describe your data as a declarative configuration,\nand it's there in a flash.\n\nHoloDB is a full-featured relational database engine\nwith a completely virtual starting dataset.\nField values and query results are calculated on the fly,\nbased on the given configuration in a highly efficient way.\nFurther modifications are stored in diff layers,\nmaking the database effectively writable.\n\n\n**What is all this for?**\n\n- **prototyping**: include the database in your stack, even in live mode\n- **demonstration**: showcase your app with realistic data, without materializing it\n- **integration testing**: add a full-featured relational database to your pipeline\n- **mocking**: put a functional database into the stack, even based on ORM entities\n- **feeding**: use it as a data source to populate a traditional database\n- **teaching**: provide a dummy database for your students\n\n**How to try it out quickly?**\n\nYou can use HoloDB in many ways,\ne.g. in embedded mode or as a server, from a program,\nfrom an ORM system, or interactively with a REPL.\nBut the easiest way to try it out is using Docker.\n\nYou can download a ready-made configuration file from the example projects:\n\n```bash\ncurl -o /tmp/config.yaml https://raw.githubusercontent.com/miniconnect/general-docs/refs/heads/main/examples/holodb-standalone/config.yaml\n```\n\nThen, just load the configuration into a HoloDB container:\n\n```bash\ndocker run --rm -p 3430:3430 -v /tmp/config.yaml:/app/config.yaml miniconnect/holodb\n```\n\nThen use the `micl` command from [miniconnect-client](https://github.com/miniconnect/miniconnect-client) to run queries in a REPL:\n\n```\n$ micl\n\nWelcome in miniConnect SQL REPL! - localhost:3430\n\nSQL \u003e SHOW SCHEMAS\n\n  Query was successfully executed!\n\n  ┌─────────┐\n  │ Schemas │\n  ├─────────┤\n  │ economy │\n  └─────────┘\n\nSQL \u003e USE economy\n\n  Query was successfully executed!\n\nSQL \u003e SHOW TABLES;\n\n  Query was successfully executed!\n\n  ┌───────────────────┐\n  │ Tables_in_economy │\n  ├───────────────────┤\n  │ companies         │\n  │ employees         │\n  │ sales             │\n  └───────────────────┘\n\nSQL \u003e SELECT * FROM companies;\n\n  Query was successfully executed!\n\n  ┌────┬──────────────────────┬──────────────┬─────────────────┐\n  │ id │ name                 │ headquarters │ contact_phone   │\n  ├────┼──────────────────────┼──────────────┼─────────────────┤\n  │  1 │ Fav Fruits Inc.      │ Stockholm    │ [NULL]          │\n  │  2 │ Fru-fru Sales Inc.   │ Tel Aviv     │ +1 143-339-0981 │\n  │  3 │ Fructose Palace Inc. │ Baku         │ +1 295-272-4854 │\n  │  4 │ Vega Veterans Inc.   │ New York     │ +1 413-876-4936 │\n  │  5 │ Goods of Nature Inc. │ Paris        │ [NULL]          │\n  └────┴──────────────────────┴──────────────┴─────────────────┘\n\nSQL \u003e exit\n\nBye-bye!\n```\n\nVisit the [SQL guide](https://github.com/miniconnect/minibase/blob/master/SQL.md)\nto learn more about the SQL features supported by the default query engine.\nAlternatively, you can try the experimental integration with the\n[Apache Calcite](https://github.com/miniconnect/calcite-integration) query planner.\n\nYou can connect to HoloDB directly via the MiniConnect API.\nFor more information,\nsee [MiniConnect API](https://github.com/miniconnect/miniconnect?tab=readme-ov-file#getting-started-with-the-api).\n\nAlso, you can use a MiniConnect server or even an existing MiniConnect `Session` via JDBC.\nFor more information,\nsee [MiniConnect JDBC compatibility](https://github.com/miniconnect/miniconnect#jdbc-compatibility).\n\n## Configuration\n\nIn `config.yaml` you can specify the structure of your data (schemas, tables, columns, data, etc.):\n\n```yaml\nseed: 98765\nschemas:\n  - name: my_schema\n    tables:\n      - name: my_table\n        writeable: true\n        size: 150\n        columns:\n          - name: id\n            mode: COUNTER\n          - name: name\n            values: ['Some name', 'Other name', 'Some other']\n```\n\nYou can generate a JSON schema for this configuration data structure\nby executing the `config:generateSchema` gradle task inside the holodb gradle project.\nThen the generated schema file will be found here:\n\n```\nprojects/config/build/schemas/holodb-config.schema.json\n```\n\nOn the **top level** these keys are supported:\n\n| Key | Type | Description |\n| --- | ---- | ----------- |\n| `seed` | `LargeInteger` | global random seed (global default: `0`) |\n| `schemas` | `List` | list of schemas (see below) |\n\nThe `seed` option sets a random seed with which you can vary the content of the database.\n\nFor each **schema**, these subkeys are supported:\n\n| Key | Type | Description |\n| --- | ---- | ----------- |\n| `name` | `String` | name of the database schema |\n| `tables` | `List` | list of tables in this schema, see below (global default: none) |\n\nFor each **table**, these subkeys are supported:\n\n| Key | Type | Description |\n| --- | ---- | ----------- |\n| `name` | `String` | name of the database table |\n| `writeable` | `boolean` | writeable or not (global default: `false`) |\n| `size` | `LargeInteger` | number of records in this table (global default: `50`) |\n| `columns` | `List` | list of columns in this table, see below (global default: none) |\n\nIf `writeable` option is set to true, then an additional layer\nwill be added over the read-only table,\nwhich accepts and stores insertions, updates, and deletions,\nand it gives the effect that the table is writeable.\n\nFor each **column**, these subkeys are supported:\n\n| Key | Type | Description |\n| --- | ---- | ----------- |\n| `name` | `String` | name of the table column |\n| `type` | `String` (`Class\u003c?\u003e`) | java class name of column type |\n| `mode` | `String` | filling mode: `DEFAULT`, `COUNTER`, `FIXED`, or `ENUM` (global default: `DEFAULT`) |\n| `nullCount` | `LargeInteger` | count of null values (global default: `0`) |\n| `values` | `Object[]` | explicit list of possible values |\n| `valuesResource` | `String` | name of a java resource which contains the values line by line |\n| `valuesBundle` | `String` | short name of a bundled value resource, otherwise similar to `valuesResource` (see below) |\n| `valuesRange` | `LargeInteger[]` | start and end value of a numeric value range |\n| `valuesPattern` | `String` | regex pattern for values (reverse indexed) |\n| `valuesDynamicPattern` | `String` | regex pattern processed by [Generex](https://github.com/mifmif/Generex) (not reverse indexed) |\n| `valuesForeignColumn` | `String[]` | use value set of a foreign `COUNTER` column |\n| `distributionQuality` | `String` | distribution quality: `LOW`, `MEDIUM`, or `HIGH` (global default: `MEDIUM`) |\n| `shuffleQuality` | `String` | shuffle quality: `NOOP`, `VERY_LOW`, `LOW`, `MEDIUM`, `HIGH`, or `VERY_HIGH` (global default: `MEDIUM`) |\n| `sourceFactory` | `String` | java class name of source factory (must implement `hu.webarticum.holodb.spi.config.SourceFactory`) |\n| `sourceFactoryData` | *any* | data will be passed to the source factory |\n| `defaultValue` | *any* | default insert value for the column |\n\nIn most cases, `type` can be omitted.\nIf the configuration loader cannot guess the type, the startup aborts with an error.\nHowever, the type can always be overridden (e. g. numbers can be generated using a regular expression).\n\nThe meaning of `mode` values:\n\n| Mode | Description |\n| ---- | ----------- |\n| `DEFAULT` | randomly distributed, non-unique values, indexed (except in case of `valuesDynamicPattern` used) |\n| `COUNTER` | fill with increasing whole numbers starting from `1`, unique, indexed (good choice for ID columns) |\n| `FIXED` | values will not be shuffled, the count of values must be equal to the table size, non-indexed |\n| `ENUM` | similar to `DEFAULT`, but with different proper rules for equality check, sort order and insertion/update |\n\nIn the case of writable tables, if other than the `ENUM` mode is used,\nusers can also put values ​​different from the initial ones.\n\nIf `nullCount` is specified (even if `0`), then the column will be nullable.\nOmit `nullCount` to make the column `NOT NULL`.\nIn case of custom `sourceFactory`, the column will be `NOT NULL` only iff\nthe source is an `IndexedSource` and has at least one null value.\n\nFor specifying the possible values in the column, one of\n`values`, `valuesResource`, `valuesRange`, `valuesPattern`,  `valuesDynamicPattern` and `valuesForeignColumn`\ncan be used.\nCurrently, for a `FIXED` column, only `values` is supported.\n\nIn the case of `COUNTER` mode, values will be ignored and should be omitted.\nThe type of a `COUNTER` column is always `java.math.LargeInteger`.\n\nIf used, the value of `valuesForeignColumn` must be an array of lengths 1, 2, or 3.\nThe one-element version contains a column name in the same table.\nThe two-element version contains a \\[*\\\u003ctable\\\u003e*, *\\\u003ccolumn\\\u003e*\\] pair in the same schema.\nThe three-element version contains the \\[*\\\u003cschema\\\u003e*, *\\\u003ctable\\\u003e*, *\\\u003ccolumn\\\u003e*\\] triplet.\n\nThere are several possible values for `valuesBundle`:\n\n| Bundle name | Description |\n| ----------- | ----------- |\n| `cities` | 100 major world cities |\n| `colors` | 147 color names (from CSS3) |\n| `countries` | 197 country names |\n| `female-forenames` | 100 frequent English female forenames |\n| `forenames` | 100 frequent English forenames (50 female, 50 male) |\n| `fruits` | 26 of the best selling fruits |\n| `log-levels` | 6 standard log levels (from log4j) |\n| `lorem` | 49 lower-case words of the *Lorem ipsum* text |\n| `male-forenames` | 100 frequent English male forenames |\n| `months` | the 12 month names |\n| `surnames` | 100 frequent English surnames |\n| `weekdays` | the names of the 7 days of the week |\n\nYou can set default values for schemas, tables, and columns at any higher level in the configuration tree.\nAny value set at a lower lever will override any value set at a higher level (and, of course, the global default).\n\n| Key | Available in |\n| --- | ------------ |\n| `schemaDefaults` | root |\n| `tableDefaults` | root, `schemas.*` |\n| `columnDefaults` | root, `schemas.*`, `schemas.*.tables.*` |\n\nFor example:\n\n```yaml\ntableDefaults:\n  writeable: false\n  size: 120\ncolumnDefaults:\n  shuffleQuality: NOOP\nschemas:\n  - name: schema_1\n    tables:\n      # ...\nschemas:\n  - name: schema_2\n    tableDefaults:\n      writeable: true\n    tables:\n      # ...\n```\n\nUsing this config all table with no explicit `size` will have the size 120,\nall table with no explicit `writeable` will read-only in `schema_1`, and writeable in `schema_2`.\nAlso, data shuffling is disabled by default.\n\n\n## Load values from resource\n\nYou can use custom predefined value sets too.\nTo do this, create a file with one value on each line.\nMake this file available to the java classloader.\nIf you use docker, the easiest way to do this is to copy the file into the `/app/resources` directory:\n\n```dockerfile\nFROM miniconnect/holodb:latest\n\nCOPY config.yaml /app/config.yaml\nCOPY my-car-brands.txt /app/resources/my-car-brands.txt\n```\n\nYou can use a predefined value set resource with the `valuesResource` key in `config.yaml`:\n\n```yaml\n          # ...\n          - name: car_brand\n            valuesResource: 'my-car-brands.txt'\n```\n\nIf you don't already have a value list, you can retrieve existing data from several sources,\nfor example [WikiData](https://www.wikidata.org/),\n[JSONPlaceholder](https://jsonplaceholder.typicode.com/)\nor [Kaggle](https://www.kaggle.com/).\n\nHere is an example, where we get data from WikiData, process it with `jq`, then save it to the docker image.\nTo safely achieve this, we use a builder image:\n\n```dockerfile\nFROM dwdraju/alpine-curl-jq:latest AS builder\nRUN curl --get \\\n  --data-urlencode 'query=SELECT ?lemma WHERE \\\n    { ?lexemeId dct:language wd:Q1860; wikibase:lemma ?lemma. ?lexemeId wikibase:lexicalCategory wd:Q9788 } \\\n    ORDER BY ?lemma' \\\n  'https://query.wikidata.org/bigdata/namespace/wdq/sparql' \\\n  -H 'Accept: application/json' \\\n  | jq -r '.results.bindings[].lemma.value' \\\n  \u003e en-letters.txt\n\nFROM miniconnect/holodb:latest\nCOPY config.yaml /app/config.yaml\nCOPY --from=builder /en-letters.txt /app/resources/en-letters.txt\n```\n\n\n## Generate from an existing database\n\nYou can find an experimental python script in the `tools` directory\nthat creates a HoloDB configuration from an existing MySQL database.\n\nHere is an example of how you can use it:\n\n```bash\npython3 mysql_scanner.py -u your_user -p your_password -d your_database -w\n```\n\nUse the `-h` or `--help` option for more details.\n\n\n## Embedded mode via JDBC\n\nYou can use HoloDB as an embedded database.\n\nTo achieve this, first add the required dependency:\n\n```gradle\nimplementation \"hu.webarticum.holodb:embedded:${holodbVersion}\"\n```\n\nSet the JDBC connection URL, specifying a resource:\n\n```\njdbc:holodb:embedded:resource://config.yaml\n```\n\nOr any file on the file system:\n\n```\njdbc:holodb:embedded:file:///path/to/config.yaml\n```\n\nOr with selecting a specific schema:\n\n```\njdbc:holodb:embedded:resource://config.yaml?schema=university\n```\n\n(Note: Number of slashes does matter.)\n\nUse the `hu.webarticum.holodb.embedded.HoloEmbeddedDriver` driver class if its explicit setting is mandatory.\n\n\n## Client-server mode via JDBC\n\nTo achieve this, first add the required dependency:\n\n```gradle\nimplementation \"hu.webarticum.miniconnect:jdbc:${miniConnectVersion}\"\n```\n\nSet the JDBC connection URL, specifying a resource:\n\n```\njdbc:miniconnect://localhost:3430\n```\n\nOr with selecting a specific schema:\n\n```\njdbc:miniconnect://localhost:3430/university\n```\n\nIn this case, use the `hu.webarticum.miniconnect.jdbc.MiniJdbcDriver` driver class if necessary.\n\n\n## Mock JPA entities\n\nTo use the annotations below, set the `jpa-annotations` subproject as a dependency:\n\n```gradle\nimplementation \"hu.webarticum.holodb:jpa-annotations:${holodbVersion}\"\n```\n\nIf you want to use the service providers (e. g. `SourceFactory`), include the `spi` subproject too:\n\n```gradle\nimplementation \"hu.webarticum.holodb:spi:${holodbVersion}\"\n```\n\nActually running it requires the `jpa` subproject instead of the `jpa-annotations`:\n\n```gradle\nimplementation \"hu.webarticum.holodb:jpa:${holodbVersion}\"\n```\n\nThe `jpa` subproject has several dependencies (while `jpa-annotations` is near pure).\nIf you only use it for tests, define it as a test-only dependency.\n\nSet this JDBC connection URL to use HoloDB as the database backend:\n\n```\njdbc:holodb:jpa://\n```\n\n(Optionally, the schema can also be specified, e.g. `jdbc:holodb:jpa:///my_schema_name`.)\n\nAt the moment, schema construction is not fully automatic, it's necessary to explicitly pass the metamodel.\nFor example in Micronaut:\n\n```java\n@Singleton\npublic class HoloInit {\n    \n    private final EntityManager entityManager;\n    \n    public HoloInit(EntityManager entityManager) {\n        this.entityManager = entityManager;\n    }\n    \n    @EventListener\n    @Transactional\n    public void onStartup(StartupEvent startupEvent) {\n        JpaMetamodelDriver.setMetamodel(entityManager.getMetamodel());\n    }\n    \n}\n```\n\nThe solution should be similarly simple for Spring or other frameworks.\n\nNow, all of your entities will be backed by HoloDB tables with automatic configuration.\nTo fine-tune this configuration, you can use some annotation on the entity classes.\n\n| Annotation | Target | Description |\n| ---------- | ------ | ----------- |\n| `@HoloTable` | class | Overrides table parameters (schema, name, writeable, size) |\n| `@HoloColumn` | field, method | Overrides column parameters |\n| `@HoloIgnore` | class, field, method | Ignores an entity or attribute |\n| `@HoloVirtualColumn` | class | Defines an additional column for the entity (multiple occurrences allowed) |\n\n`@HoloColumn` and `@HoloVirtualColumn` accepts all the columns configurations\n(for `@HoloVirtualColumn` `name` and `type` are mandatory).\n\nSome numeric settings have two variants, one for usual and one for large values:\n\n| Annotation | Usual field | Large field |\n| ---------- | ----------- | ----------- |\n| `@HoloTable` | `size` (`long`) | `largeSize` (`String`) |\n| `@HoloColumn` | `nullCount` (`long`) | `largeNullCount` (`String`) |\n| `@HoloColumn` | `valuesRange` (`long[]`) | `largeValuesRange` (`String[]`) |\n| `@HoloVirtualColumn` | `nullCount` (`long`) | `largeNullCount` (`String`) |\n| `@HoloVirtualColumn` | `valuesRange` (`long[]`) | `largeValuesRange` (`String[]`) |\n\nSome settings accepts custom data:\n\n| Annotation | Annotation field | Type | Config field |\n| ---------- | ---------------- | ---- | ------------ |\n| `@HoloColumn` | `sourceFactoryData` | `@HoloValue` | `sourceFactoryData` |\n| `@HoloColumn` | `sourceFactoryDataMap` | `@HoloValue[]` | `sourceFactoryData` |\n| `@HoloColumn` | `defaultValue` | `@HoloValue` | `defaultValue` |\n| `@HoloVirtualColumn` | `sourceFactoryData` | `@HoloValue` | `sourceFactoryData` |\n| `@HoloVirtualColumn` | `sourceFactoryDataMap` | `@HoloValue[]` | `sourceFactoryData` |\n| `@HoloVirtualColumn` | `defaultValue` | `@HoloValue` | `defaultValue` |\n\nFields ending with the 'Map' suffix accepts an array of `@HoloValue`s,\nyou can use `@HoloValue.key` to set map entry key for each.\n\nExample:\n\n```java\n@Entity\n@Table(name = \"companies\")\n@HoloTable(size = 25)\n@HoloVirtualColumn(name = \"extracol\", type = Integer.class, valuesRange = {10, 20})\npublic class Company {\n    \n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    \n    @Column(name = \"birth_country\", nullable = false)\n    @HoloColumn(valuesBundle = \"countries\")\n    private String country;\n    \n    // ...\n    \n}\n```\n\n\n## How does HoloDB work?\n\nHoloDB is a flexible virtual relational database engine written in Java.\n\nLike other relational database engines,\nHoloDB is a collection of tools built on top of a query engine\nlayered over a structured data access API.\nBut in the case of HoloDB, this API does not access a real pre-populated data storage,\nbut dynamically computes data on-the-fly directly from your configuration.\nHowever, unlike simplistic SQL mocking techniques,\nmultiple queries yield realistic, mutually consistent results, computed dynamically yet reproducibly.\n\nTypically, this computation is done on a column-by-column basis.\nThe column then refers to an ordered, searchable base set of values.\nThis is then distributed over the size of the table controlled by distribution\nand null-management strategies preserving order and searchability.\nFinally, a shuffling layer applies an invertible permutation,\nleveraging concepts borrowed from cryptography\nto efficiently distribute values without precomputing them.\n\n![Default method of providing column data](img/column-layers.svg)\n\nThe base value set for a column is expected to be ordered and searchable.\nSuch a value set can be as simple as a numerical range\nor as sophisticated as the huge space of strings matching to a complex regular expression.\n\nThe simplest but yet efficient distribution strategy is linear interpolation.\nHowever a more fine-tuned distribution can be parameterized\nwith value frequency and some level of pseudo-randomness.\nYou can also explicitly configure the amount of null values mixed in.\n\nThe shuffling layer ensures realistic randomness through a pair of functions:\na permutation and its inverse.\nHigh-quality implementations are typically based on Feistel cipher\nand independently scalable hash functions.\nHowever, simpler and more performant implementations such as linear congruential methods often suffice.\nExploring the trade-off between seemingly strong randomization and efficiency\nis one of the project's intriguing areas.\n\nWritable tables utilize a diff layer,\ntransparently tracking inserts, updates, and deletions separate from the immutable virtual baseline.\nConcurrent modifications are managed by a lightweight transaction management layer,\nideal for short-lived writable datasets.\n\nThe on-the-fly computations rely heavily on arithmetic-centric operations\nrather than data storage and retrieval.\nFor numeric efficiency, HoloDB introduces specialized types and algorithms, most of which can be used standalone too.\nFor example, LargeInteger is an arbitrarily large numeric data type\nsomewhat inspired by similar double-nature implementations\nsuch as SafeLong from the Spire library, BigInt from the Scala standard library, and others.\nCompared to these, LargeInteger is more efficient in case of\nfrequent operations on smaller numbers.\n\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminiconnect%2Fholodb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fminiconnect%2Fholodb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminiconnect%2Fholodb/lists"}