{"id":13409276,"url":"https://github.com/capturetechnologies/stored","last_synced_at":"2025-03-14T14:31:09.052Z","repository":{"id":50907195,"uuid":"133202556","full_name":"capturetechnologies/stored","owner":"capturetechnologies","description":"STORED - Document oriented layer and ORM for FoundationDB","archived":false,"fork":false,"pushed_at":"2021-07-19T17:11:45.000Z","size":4733,"stargazers_count":26,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-07-31T20:35:56.692Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","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/capturetechnologies.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":"2018-05-13T03:31:23.000Z","updated_at":"2024-02-20T14:32:09.000Z","dependencies_parsed_at":"2022-09-03T17:30:26.747Z","dependency_job_id":null,"html_url":"https://github.com/capturetechnologies/stored","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capturetechnologies%2Fstored","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capturetechnologies%2Fstored/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capturetechnologies%2Fstored/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capturetechnologies%2Fstored/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/capturetechnologies","download_url":"https://codeload.github.com/capturetechnologies/stored/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243593338,"owners_count":20316171,"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":[],"created_at":"2024-07-30T20:00:59.506Z","updated_at":"2025-03-14T14:31:08.044Z","avatar_url":"https://github.com/capturetechnologies.png","language":"Go","readme":"# FoundationDB layer\nIn development. Use with care, schema are about to change in the future.\n\n## Init the database\nBefore you start STORED document, a layer must be inited.\nThis way you will describe all the objects and indexes. Init part should be executed before\nthe actual part of your application. Example:\n```Go\nvar dbUser, dbChat *stored.Object\nvar dbUserChat *stored.Relation\nfunc init() { // init database\n  cluster := stored.Connect(\"./fdb.cluster\")\n  err := cluster.Err()\n  if err != nil {\n    fmt.Println(\"DB Problem\", err)\n    // no \"return\" needed, if db will wake up, client library will catch it up\n  }\n  db := cluster.Directory(\"test\")\n  user := db.Object(\"user\", User{})\n  chat := db.Object(\"chat\", Chat{})\n  dbUserChat = user.N2N(chat)\n  dbUser = user.Done() // finish object\n  dbChat = chat.Done()\n}\n```\nIt is required to create a variable for each database object. This may seems like unnecessary\ncode but this approach allowes you to have much more control over your source code. For\nexample it makes easy to find all usage of specific object in your codebase.\n\nAll the steps will be described below:\n#### Connect to DB\n```Go\ncluster := stored.Connect(\"./fdb.cluster\")\n```\n\n#### Create directory\nAll objects should be stored in a directory. Using directories you are able to separate different logical parts of application.\n```Go\ndb := cluster.Directory(\"test\")\n```\n\n#### Define a stored document\nSTORED could use any struct defined in your app as a schema, all you need is to add annotations, like ```stored:\"online,mutable\"```\n**mutual** tag means this field will be changed often, so should not be packed.\n```Go\ntype dbUser struct {\n  ID    int64  `stored:\"id\"`\n  Name  string `stored:\"name\"`\n  Login string `stored:\"login\"`\n  Online bool `stored:\"online,mutable\"`\n}\n```\nList of options available:\n- **mutable** indicates that field should kept separately if it going to be changed frequently *(not implemented yet)*\n\n#### Objects initialization\nObjects is a main workhorse of stored FoundationDB layer.\nYou should init objects for all the objects in your application at the initialization part of application.\n```Go\nuser := db.Object(\"user\", User{}) // User could be any struct in your project\n```\n\n#### Primary keys\nAlternative to setting primary in struct define annotation is setting it directly.\n```Go\nuser.Primary(\"id\")\n```\nPrimary index could be multiple, for example:\n```Go\nuser.Primary(\"chat_id\", \"message_id\")\n```\nIn this case the combination of values will be the primary key. Fields order should not change.\n\n#### IDDate\nThis is the best way to generate new indexes for objects with fields like int64.\nIDDate is most suitable way to generate unique identifiers in most cases. IDDate will generate\nint64 identifier based on current timestamp plus some random seed. This way ID could serve double purpose\nstoring ID plus storing timestamp of adding the object.\nSince int64 is not precise enough to completely illuminate collisions, if field is in primary index at the\nmoment of Add STORED will check that no other key with such ID presented.\n\n```Go\nuser.IDDate(\"id\") // field id should be int64 or uint64\n```\n\n#### IDRandom\nYou should use IDRandom when you do not want your ID to unveil timestamp of object creation.\nSince int64 is not precise enough to completely illuminate collisions, if field is in primary index at the\nmoment of Add STORED will check that no other key with such ID presented.\n\n```Go\nuser.IDRandom(\"id\") // field id should be int64 or uint64\n```\n\n#### AutoIncrement\nAutoincrement is an easy way to provide automatically incremented values to an field.\nAt the moment of each Add new object will be written with incremented counter, 1,2,3 etc..\n\nBut autoincrement should be used with care, since incrementing of a counter creates collision\nof transactions you should not use this options when there are more than 100 Adds per second\n\nAny key could be setup as autoincremented.\n```Go\nuser.AutoIncrement(\"id\")\n```\nthis way the value of this field will be set automaticly if **Add** `dbUser.Add(\u0026user)` method triggered.\n\n#### Indexes\n**Unique** creates unique index. You could fetch document directly using this index.\n*Add* and *Set* methods would fail if other item with same unique index presented.\n```Go\nuser.Unique(\"login\")\n```\n**Index** creates regular index. Could be many rows with this index. You are able to fetch first row or list of rows.\n```Go\nuser.Index(\"login\")\n```\n\n#### Relations\n**N2N** is the most usefull type of relations between database objects. N2N represents *many* to *many* type of connection.\n```Go\ndbUserChat := user.N2N(chat)\n```\nIn this example **dbUserChat** represents relation when any user has unlimited amount of connected chats and any chat has\nunlimited amount of connected users. Also it is available to set any data value to each connection (user to chat and chat to user)\n\n## Working with data\nIf database is successfully inited and schema is set up, you are ok to work with defined database objects.\nMake sure that init section is triggered once and before any work with database.\n\n#### Write data to key\nThis way stored will write user object in set of keys each for each field with `stored:\"some_key\"` type annotation\n```Go\nuser := User{1, \"John\", \"john\"}\nerr := dbUser.Set(user).Err()\n```\nIf you have **autoincrement** option at your primary field you are able to Add new rows\n```Go\nuser := User{0, \"John\", \"john\"}\nerr := dbUser.Add(\u0026user).Err() // after this user.ID will be 1\n```\n\n#### Get data by primary ID\nYou could use method Get to fetch any object from stored by primary key\n```Go\nuser := User{1}\nerr := dbUser.Get(\u0026user).Err()\n```\nAlso you could perform multiget. This way a lot of items will be requested simultaneously\n```Go\nusers := []*User{\u0026User{1},\u0026User{2},\u0026User{3},\u0026User{4}}\nerr := dbUser.MultiGet(users).Err()\n```\n\n#### Get data by index\n```Go\nuser := User{}\nerr := dbUser.GetBy(\"login\", \"john\").Scan(\u0026user)\n```\n\n#### Add new connection using relation\nBefore using the connection you should create new relation at #init section\n```Go\ndbUserChat.Set(user, chat)\n```\n*user* and *chat* objects should contain primary index values\nThere are cases when inside the relation you want to store some data, for example say:\n* in user-chat connection you want to store last *message_id* user read\n* in chat-user connection you want to store user join date in this chat\n```Go\ndbUserChat.ClientData(\"last_message\") // last_message is the field at the client (Chat) object\ndbUserChat.HostData(\"join_date\") // last_message is the field at the client (Chat) object\n```\nData object could be any type, even the struct.\nBut complicated struct object could got\n\n#### Get list of objects using Relation\nSay you have **N2N** relation between users and chats.\n* **GetClients** allow you to fetch all objects using host of this relation\n```Go\nchats := []Chat{}\nerr = dbUserChat.GetClients(user, nil, 100).ScanAll(\u0026chats)\n```\n* **GetHosts** allow you to fetch all objects using client of this relation\n```Go\nusers := []User{}\nerr = dbUserChat.GetHosts(chat, nil, 100).ScanAll(\u0026users)\n```\n\n## Testing\nStored has set of unit tests, you can easily run to check that everything set up properly.\nUse this simple code snippet to run tests on your database.\n```Go\ndbDriver := stored.Connect(\"./fdb.cluster\")\nstored.TestsRun(dbDriver)\n```\n\n# TODO\n- [x] Indexes\n- [x] AutoIncrement\n- [x] Multiple primary\n- [ ] Store schema inside FoundationDB\n- [ ] Schema migration (delete each item with old schema and set with new one)\n","funding_links":[],"categories":["Layers"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcapturetechnologies%2Fstored","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcapturetechnologies%2Fstored","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcapturetechnologies%2Fstored/lists"}