{"id":15041114,"url":"https://github.com/ulfox/dby","last_synced_at":"2025-08-09T00:21:33.266Z","repository":{"id":44724313,"uuid":"399076464","full_name":"ulfox/dby","owner":"ulfox","description":"Simple Yaml DB","archived":false,"fork":false,"pushed_at":"2021-09-18T17:20:32.000Z","size":213,"stargazers_count":60,"open_issues_count":1,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T01:43:11.910Z","etag":null,"topics":["database","db","go","golang","yaml"],"latest_commit_sha":null,"homepage":"","language":"Go","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/ulfox.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":"2021-08-23T11:25:39.000Z","updated_at":"2025-01-16T23:03:54.000Z","dependencies_parsed_at":"2022-08-30T03:11:15.291Z","dependency_job_id":null,"html_url":"https://github.com/ulfox/dby","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulfox%2Fdby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulfox%2Fdby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulfox%2Fdby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulfox%2Fdby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ulfox","download_url":"https://codeload.github.com/ulfox/dby/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248949698,"owners_count":21188127,"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","db","go","golang","yaml"],"created_at":"2024-09-24T20:45:36.573Z","updated_at":"2025-04-14T19:43:52.991Z","avatar_url":"https://github.com/ulfox.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## DB Yaml\n\nSimple DB using yaml. A project for managing the content of yaml files.\n\nTable of Contents\n=================\n- [DB Yaml](#db-yaml)\n- [Features](#features)\n- [Usage](#usage)\n  * [Write to DB](#write-to-db)\n  * [Query DB](#query-db)\n    + [Get First Key](#get-first-key)\n    + [Search for Keys](#search-for-keys)\n  * [Query Path](#query-path)\n    + [Query Path with Arrays](#query-path-with-arrays)\n      - [Without trailing array](#without-trailing-array)\n      - [With trailing array](#with-trailing-array)\n  * [Delete Key By Path](#delete-key-by-path)\n  * [Document Management](#document-management)\n      + [Add a new doc](#add-a-new-doc)\n      + [Switch Doc](#switch-doc)\n      + [Document names](#document-names)\n        - [Name documents manually](#name-documents-manually)\n        - [Name all documents automatically](#name-all-documents-automatically)\n        - [Switch between docs by name](#switch-between-docs-by-name)\n      + [Import Docs](#import-docs)\n      + [Global Commands](#global-commands)\n        - [Global Upsert](#global-upsert)\n        - [Global Update](#global-update)\n        - [Global GetFirst](#global-getfirst)\n        - [Global FindKeys](#global-findkeys)\n        - [Global GetPath](#global-getpath)\n        - [Global Delete](#global-delete)\n  * [Convert Utils](#convert-utils)\n      + [Get map of strings from interface](#get-map-of-strings-from-interface)\n        - [Get map directly from a GetPath object](#get-map-directly-from-a-getpath-object)\n        - [Get map manually](#get-map-manually)\n      + [Get array of string from interface](#get-array-of-string-from-interface)\n        - [Get array directly from a GetPath object](#get-array-directly-from-a-getpath-object)\n      - [Get array manually](#get-array-manually)\n\n\n## Features\n\nThe module can do\n\n- Create/Load yaml files\n- Update content\n- Get values from keys\n- Query for keys\n- Delete keys\n- Merge content\n\n##  Usage\n\nSimple examples for working with yaml files as db\n\n### Initiate a new stateful DB\n\nCreate a new local DB\n\n```go\npackage main\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/ulfox/dby/db\"\n)\n\nfunc main() {\n\tlogger := logrus.New()\n\n\tstate, err := db.NewStorageFactory(\"local/db.yaml\")\n\tif err != nil {\n\t\tlogger.Fatalf(err.Error())\n\t}\n}\n```\n\nThe code above will create a new yaml file under **local** directory.\n\n### Initiate a new stateless DB\n\n```go\npackage main\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/ulfox/dby/db\"\n)\n\nfunc main() {\n\tlogger := logrus.New()\n\n\tstate, err := db.NewStorageFactory()\n\tif err != nil {\n\t\tlogger.Fatalf(err.Error())\n\t}\n}\n```\n\nInitiating a db without arguments will not create/write/read from a file. All operations\nwill be done in memory and unless the caller saves the data externally, all data will be lose\non termination\n\n\n### Write to DB\n\nInsert a map to the local yaml file.\n\n```go\nerr = state.Upsert(\n\t\"some.path\",\n\tmap[string]string{\n\t\t\"key-1\": \"value-1\",\n\t\t\"key-2\": \"value-2\",\n\t},\n)\n\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\n```\n\n### Query DB\n\n#### Get First Key\n\nGet the value of the first key in the hierarchy (if any)\n\n```go\nval, err := state.GetFirst(\"key-1\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(val)\n```\n\nFor example if we have the following structure\n\n```yaml\nkey-1:\n    key-2:\n        key-3: \"1\"\n    key-3: \"2\"\n```\n\nAnd we query for `key-3`, then we will get back **\"2\"** and not **\"1\"**\nsince `key-3` appears first on a higher layer with a value of **2**\n\n#### Search for keys\n\nGet all they keys (if any). This returns the full path for the key,\nnot the key values. To get the values check the next section **GetPath**\n\n```go\nkeys, err := state.FindKeys(\"key-1\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(keys)\n```\n\nFrom the previous example, this query would have returned\n\n```yaml\n[\"key-1.key-2.key-3\", \"key-1.key-3\"]\n```\n\n### Query Path\n\nGet the value from a given path (if any)\n\nFor example if we have in yaml file the following key-path\n\n```yaml\nkey-1:\n    key-2:\n        key-3: someValue\n```\n\nThen to get someValue, issue\n\n```go\nkeyPath, err := state.GetPath(\"key-1.key-2.key-3\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(keyPath)\n```\n\n#### Query Path with Arrays\n\nWe can also query paths that have arrays.\n\n##### Without trailing array\n\n```yaml\nkey-1:\n    key-2:\n        - key-3: \n            key-4: value-1\n```\n\nTo get the value of `key-4`, issue\n\n```go\nkeyPath, err := state.GetPath(\"key-1.key-2.[0].key-3.key-4\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(keyPath)\n```\n\n##### With trailing array\n\n```yaml\nkey-1:\n    key-2:\n        - value-1\n        - value-2\n        - value-3\n```\n\nTo get the first index of `key-2`, issue\n\n```go\nkeyPath, err := state.GetPath(\"key-1.key-2.[0]\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(keyPath)\n```\n\n### Delete Key By Path\n\nTo delete a single key for a given path, e.g. key-2\nfrom the example above, issue\n\n```go\nerr = state.Delete(\"key-1.key-2\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\n```\n\n\n### Document Management\n\nDBy creates by default an array of documents called library. That is in fact an array of interfaces\n\nWhen initiating DBy, document 0 (index 0) is creatd by default and any action is done to that document, unless we switch to a new one\n\n#### Add a new doc\n\nTo add a new doc, issue\n\n```go\nerr = state.AddDoc()\nif err != nil {\n  logger.Fatal(err)\n}\n\n```\n\n**Note: Adding a new doc also switches the pointer to that doc. Any action will write/read from the new doc by default**\n\n#### Switch Doc\n\nTo switch a different document, we can use **Switch** method that takes as an argument an index\n\nFor example to switch to doc 1 (second doc), issue\n\n```go\nerr = state.Switch(1)\nif err != nil {\n  logger.Fatal(err)\n}\n```\n\n#### Document names\n\nWhen we work with more than 1 document, we may want to set names in order to easily switch between docs\n\nWe have 2 ways to name our documents\n\n- Add a name to each document manually\n- Add a name providing a path that exists in all documents\n\n##### Name documents manually\n\nTo name a document manually, we can use the **SetName** method which takes 2 arguments\n\n- name\n- doc index\n\nFor example to name document with index 0, as myDoc\n\n```go\nerr := state.SetName(\"myDoc\", 0)\nif err != nil {\n  logger.Fatal(err)\n}\n```\n\n##### Name all documents automatically\n\nTo name all documents automatically we need to ensure that the same path exists in all documents.\n\nThe method for updating all documents is called **SetNames** and takes 2 arguments\n\n- Prefix: A path in the documents that will be used for the first name\n- Suffix: A path in the documents that will be used for the last name\n\n**Note: Docs that do not have the paths that are queried will not get a name**\n\nThis method best works with **Kubernetes** manifests, where all docs have a common set of fields. \n\nFor example\n\n```yaml\napiVersion: someApi-0\nkind: someKind-0\nmetadata:\n...\n  name: someName-0\n...\n---\napiVersion: someApi-1\nkind: someKind-1\nmetadata:\n...\n  name: someName-1\n...\n---\n```\n\nFrom above we could give a name for all our documents if we use **kind** + **metadata.name** for the name.\n\n```go\nerr := state.SetNames(\"kind\", \"metadata.name\")\nif err != nil {\n  logger.Fatal(err)\n}\n```\n\n###### List all doc names\n\nTo get the name of all named docs, issue\n\n```go\nfor i, j := range state.ListDocs() {\n  fmt.Println(i, j)\n}\n```\nExample output based on the previous **SetNames** example\n\n```bash\n0 service/listener-svc\n1 poddisruptionbudget/listener-svc\n2 horizontalpodautoscaler/caller-svc\n3 deployment/caller-svc\n4 service/caller-svc\n5 poddisruptionbudget/caller-svc\n6 horizontalpodautoscaler/listener-svc\n7 deployment/listener-svc\n```\n\n##### Switch between docs by name\n\nTo switch to a doc by using the doc's name, issue\n\n```go\nerr = state.SwitchDoc(\"PodDisruptionBudget/caller-svc\")\nif err != nil {\n  logger.Fatal(err)\n}\n```\n\n#### Import Docs\n\nWe can import a set of docs with **ImportDocs** method. For example if we have the following yaml\n\n```yaml\napiVersion: someApi-0\nkind: someKind-0\nmetadata:\n...\n  name: someName-0\n...\n---\napiVersion: someApi-1\nkind: someKind-1\nmetadata:\n...\n  name: someName-1\n...\n---\n```\n\nWe can import it by giving the path of the file\n\n```go\nerr = state.ImportDocs(\"file-name.yaml\")\nif err != nil {\n  logger.Fatal(err)\n}\n```\n\n#### Global Commands\n\nWrappers for working with all documents\n\n##### Global Upsert\n\nWe can use upsert to update or create keys on all documents\n\n```go\nerr = state.UpsertGlobal(\n  \"some.path\",\n  \"v0.3.0\",\n)\nif err != nil {\n  logger.Fatal(err)\n}\n\n```\n\n##### Global Update\n\nGlobal update works as **GlobalUpsert** but it skips documents that\nmiss a path rather than creating the path on those docs.\n\n##### Global GetFirst\n\nTo get the value of the first key in the hierarchy for each document, issue\n\n```go\nvalueOfDocs, err := state.GetFirstGlobal(\"keyName\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(valueOfDocs)\n```\n\nThis returns a `map[int]interface{}` object. The key is the index of each document and it's value\nis the value of the first key in the hierarchy in that document\n\n##### Global FindKeys\n\nTo get all the paths for a given from all documents, issue\n\n```go\nmapOfPaths, err := state.FindKeysGlobal(\"keyName\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(mapOfPaths)\n```\n\nThis returns a `map[int][]string` object. The key is the index of each document and it's value\nis a list of paths that have the queried key\n\n##### Global GetPath\n\nTo get a path that exists in all documents, issue\n\n```go\nvalueOfDocs, err := state.GetPathGlobal(\"key-1.key-2.key-3\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(valueOfDocs)\n```\n\nThis returns a `map[int]interface{}` object. The key is the index of each document and it's value\nis the value of the specific key in that document\n\n##### Global Delete\n\nTo delete a path from all documents, issue\n\n```go\nerr := state.DeleteGlobal(\"key-1.key-2.key-3\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\n```\n\nThe above will delete all the paths that match the queried path from each doc\n\n### Convert Utils\n\nConvert simply automate the need to\nexplicitly do assertion each time we need to access\nan interface object.\n\nLet us assume we have the following YAML structure\n\n```yaml\n\nto:\n  array-1:\n    key-1:\n    - key-2: 2\n    - key-3: 3\n    - key-4: 4\n  array-2:\n  - 1\n  - 2\n  - 3\n  - 4\n  - 5\n  array-3:\n  - key-1: 1\n  - key-2: 2\n\n```\n\n#### Get map of strings from interface\n\nWe can do this in two ways, get object by giving a path and assert the interface to `map[string]string`, or work manually our way to the object\n\n##### Get map directly from a GetPath object\n\nTo get map **key-2: 2**, first get object via GetPath\n\n```go\n\nobj, err := state.GetPath(\"to.array-1.key-1.[0]\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(val)\n\n```\n\nNext, assert **obj** as `map[string]string`\n\n```go\n\nassertData := db.NewConvertFactory()\n\nassertData.Input(val)\nif assertData.GetError() != nil {\n\tlogger.Fatal(assertData.GetError())\n}\nvMap, err := assertData.GetMap()\nif err != nil {\n\tlogger.Fatal(err)\n}\nlogger.Info(vMap[\"key-2\"])\n\n```\n\n##### Get map manually\n\nWe can get the map manually by using only **Convert** operations\n\n```go\n\nassertData := db.NewConvertFactory()\n\nassertData.Input(state.Data).\n\tKey(\"to\").\n\tKey(\"array-1\").\n\tKey(\"key-1\").Index(0)\nif assertData.GetError() != nil {\n\tlogger.Fatal(assertData.GetError())\n}\nvMap, err := assertData.GetMap()\nif err != nil {\n\tlogger.Fatal(err)\n}\nlogger.Info(vMap[\"key-2\"])\n\n```\n\n#### Get array of string from interface\n\nAgain here we can do it two ways as with the map example\n\n##### Get array directly from a GetPath object\n\nTo get **array-2** as **[]string**, first get object via GetPath\n\n```go\n\nobj, err = state.GetPath(\"to.array-2\")\nif err != nil {\n\tlogger.Fatalf(err.Error())\n}\nlogger.Info(obj)\n\n```\n\nNext, assert **obj** as `[]string`\n\n```go\n\nassertData := db.NewConvertFactory()\n\nassertData.Input(obj)\nif assertData.GetError() != nil {\n\tlogger.Fatal(assertData.GetError())\n}\nvArray, err := assertData.GetArray()\nif err != nil {\n\tlogger.Fatal(err)\n}\nlogger.Info(vArray)\n\n```\n\n##### Get array manually\n\nWe can get the array manually by using only **Convert** operations\n\n```go\n\nassertData.Input(state.Data).\n\tKey(\"to\").\n\tKey(\"array-2\")\nif assertData.GetError() != nil {\n\tlogger.Fatal(assertData.GetError())\n}\nvArray, err := assertData.GetArray()\nif err != nil {\n\tlogger.Fatal(err)\n}\nlogger.Info(vArray)\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulfox%2Fdby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fulfox%2Fdby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulfox%2Fdby/lists"}