{"id":13433526,"url":"https://github.com/dosyago/sirdb","last_synced_at":"2025-04-05T03:09:21.171Z","repository":{"id":43090273,"uuid":"234091935","full_name":"dosyago/sirdb","owner":"dosyago","description":":man: a simple, git diffable JSON database on yer filesystem. By the power of NodeJS","archived":false,"fork":false,"pushed_at":"2024-04-25T15:28:25.000Z","size":206,"stargazers_count":573,"open_issues_count":3,"forks_count":14,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-24T11:56:57.062Z","etag":null,"topics":["card","database","database-files","dbase","dbf","filesystem","git-diff","gron","human-readable","json","json-diffable","json-files","recutils","sir","sirdb","tinydb"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dosyago.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-15T13:56:10.000Z","updated_at":"2025-01-03T21:53:06.000Z","dependencies_parsed_at":"2024-05-04T19:15:20.618Z","dependency_job_id":"2a30fc83-0cea-4584-af3d-217ca645badd","html_url":"https://github.com/dosyago/sirdb","commit_stats":null,"previous_names":["c9fe/sirdb","dosyago/sirdb","crislin2046/stubdb","00000o1/sirdb"],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosyago%2Fsirdb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosyago%2Fsirdb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosyago%2Fsirdb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dosyago%2Fsirdb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dosyago","download_url":"https://codeload.github.com/dosyago/sirdb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246126685,"owners_count":20727594,"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":["card","database","database-files","dbase","dbf","filesystem","git-diff","gron","human-readable","json","json-diffable","json-files","recutils","sir","sirdb","tinydb"],"created_at":"2024-07-31T02:01:28.287Z","updated_at":"2025-03-29T02:04:32.503Z","avatar_url":"https://github.com/dosyago.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","json"],"sub_categories":[],"readme":"\n\u003cp id=logo align=center\u003e\u003cimg width=555 src=https://github.com/c9fe/sirdb/raw/master/.readme-assets/sir-logo-rainbow.svg\u003e\u003c/p\u003e\n\n# :man: [Sir](https://github.com/c9fe/sirdb) ![npm downloads](https://img.shields.io/npm/dt/stubdb) ![version](https://img.shields.io/npm/v/sirdb?label=version) [![visitors+++](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fc9fe%2Fsirdb\u0026count_bg=%2379C83D\u0026title_bg=%23555555\u0026icon=\u0026icon_color=%23E7E7E7\u0026title=%28today%2Ftotal%29%20visitors%20since%20Nov%207%202020\u0026edge_flat=false)](https://hits.seeyoufarm.com) \n\n**Sir.DB** -- A simple database on the file system.\n\nJSON files organised into subdirectories for each table.\n\nLike **SirDB**? You'll probably **love [ServeData](https://github.com/c9fe/servedata)!** **ServeData** is a powerful yet simple server for SirDB with baked-in schemas, users, groups, permissions, authentication, authorization and payments.\n\n\u003cspan id=toc\u003e\u003c/span\u003e\n------------------------------------------------\n- [overview](#overview)\n- [features](#features)\n  * [roadmap](#roadmap)\n- [get](#get)\n- [add git](#add-git--make-diffable)\n- [repository guide](#repository-guide)\n- [api](#api)\n  * [config](#configroot-path)\n  * [dropTable](#droptablename-string)\n  * [getTable](#gettablename-string-table)\n  * [getIndexedTable](#getindexedtablename-string-indexed_properties-string-indexedtable)\n  * [\u0026lt;Table\u0026gt;.put](#tableputkey-string-value-any-greenlights-function--function--evaluator)\n  * [\u0026lt;Table\u0026gt;.get](#tablegetkey-string-greenlights-function--function--evaluator)\n  * [\u0026lt;IndexedTable\u0026gt;.getAllMatchingKeysFromIndex](#indexedtablegetallmatchingkeysfromindexprop-string-value-any-string)\n  * [\u0026lt;IndexedTable\u0026gt;.getAllMatchingRecordsFromIndex](#indexedtablegetallmatchingrecordsfromindexprop-string-value-any-any)\n  * [\u0026lt;Table\u0026gt;.getAll](#tablegetall-any)\n  * [\u0026lt;PageableTable\u0026gt;.getPage](#pageabletablegetpagecursor-string-count-int-any)\n- [examples](#examples)\n- [related projects](#related-projects)\n- [contributing](#contributing)\n- [examples of database files and diffs](#example-of-database-files-and-diffs)\n- [notices](#notices)\n--------------------------------------\n# overview\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nWith this library you can make databases that are:\n- human-readable\n- git-diffable, and therefore versionable, and\n- text-based. Everything is a JSON file, including the database meta information.\n\n**Sir.DB**:\n- is written in literate, tested JavaScript, \n- is permissively licensed, and\n- is around 500 lines of code and 6.6Kb gzipped.\n\n# features\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n- Human readable\n- Store any JSON-able object\n- Index on any property (for nested objects, only index top-level properties)\n- Auto ID or custom ID\n- Diffable by git \n- All records, indexes and table information are JSON files\n- 1 file per record, 1 file per unique index value, 1 file per table info\n- 1 sub-directory per table, 1 sub-directory (nested inside table) per indexed property\n\nAll in all this makes the database easy to understand and inspect. As well as making the code easy to read and maintain.\n\n## roadmap\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n- \u0026lt;PageableTable\u0026gt;.getPage\n- Transactions\n- ACID guarantee (as long as accessed by single node thread)\n- Can expand ACID to multi-threaded access with a request queue.\n\n# get\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n```console\nnpm i --save sirdb\n```\n\n# add git, make diffable.\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nJust `cd` into your DB root path (the `root` path given to `config()`) and type `git init`. Or do it at any ancestor directory.\n\n# repository guide\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nThis repository has around 500 lines of code and 2 dependencies ([esm](https://www.npmjs.com/package/esm) and [discohash](https://github.com/c9fe/discohash)), and is organized as follows:\n\n- [api.js](https://github.com/c9fe/sirdb/tree/master/api.js): the main file (config, dropTable, getTable and getIndexedTable)\n- [table.js](https://github.com/c9fe/sirdb/tree/master/table.js): Table and IndexedTable classes\n- [stats/](https://github.com/c9fe/sirdb/tree/master/stats): see the source-code stats\n\n# api\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nThere's only a couple of handful of calls in this api: `config`, `dropTable`, `getIndexedTable`, `getTable`, `put`, `get`, `getAllMatchingKeysFromIndex` and `getAllMatchingRecordsFromIndex`\n\n## config({root: path})\n\nConfigure the root directory of your database.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## dropTable(name: string)\n\nDeletes a table.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## getTable(name: string): Table \n\nCreates a table (if it doesn't exist) and returns it.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## getIndexedTable(name: string, indexed_properties: string[]): IndexedTable\n\nCreates a table (if it doesn't exist) that has indexes for the given properties, and returns it.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## \u0026lt;Table\u0026gt;.put(key: string, value: any, greenlights?: function | function[] | Evaluator)\n\nAdds (or updates) an item to the table by key.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## \u0026lt;Table\u0026gt;.get(key: string, greenlights?: function | function[] | Evaluator)\n\nGets an item from the table by key.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## \u0026lt;IndexedTable\u0026gt;.getAllMatchingKeysFromIndex(prop: string, value: any): string[]\n\nGets all item keys from the table that have a property `prop` that matches `value`, if that property is indexed.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## \u0026lt;IndexedTable\u0026gt;.getAllMatchingRecordsFromIndex(prop: string, value: any): any[]\n\nGets all items from the table that have a property `prop` that matches `value`, if that property is indexed.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## \u0026lt;Table\u0026gt;.getAll(): any[]\n\nGets all items from the table.\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n## \u0026lt;PageableTable\u0026gt;.getPage(cursor: string, count?: int): any[]\n\nGet `count` (default 10) items from the table, starting at the first item after `cursor`. *Note: not yet implemented.*\n\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n# examples\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nSee below for how the [api calls](#api) are used:\n\n```javascript\nimport path from 'path';\nimport assert from 'assert';\nimport fs from 'fs';\n\nimport {getTable, getIndexedTable, dropTable, config} from './api.js';\n\ntestGetTable();\ntestGetIndexedTable();\ntestDropTable();\ntestPut();\ntestIndexedPut();\ntestGet();\ntestGetAll();\n\nfunction testGetTable() {\n  const root = path.resolve(__dirname, \"test-get-table\");\n  config({root});\n\n  dropTable(\"users\");\n  dropTable(\"subscriptions\");\n\n  getTable(\"users\");\n  getTable(\"subscriptions\");\n\n  assert.strictEqual(fs.existsSync(path.resolve(root, \"users\", \"tableInfo.json\")), true);\n  assert.strictEqual(fs.existsSync(path.resolve(root, \"subscriptions\", \"tableInfo.json\")), true);\n\n  fs.rmdirSync(root, {recursive:true});\n}\n\nfunction testGetIndexedTable() {\n  const root = path.resolve(__dirname, \"test-get-indexed-table\");\n  config({root});\n\n  dropTable(\"users\");\n  dropTable(\"subscriptions\");\n\n  getIndexedTable(\"users\", [\"username\", \"email\"]);\n  getIndexedTable(\"subscriptions\", [\"email\"]);\n\n  try {\n    assert.strictEqual(fs.existsSync(path.resolve(root, \"users\", \"tableInfo.json\")), true);\n    assert.strictEqual(fs.existsSync(path.resolve(root, \"subscriptions\", \"tableInfo.json\")), true);\n\n    assert.strictEqual(fs.existsSync(path.resolve(root, \"users\", \"_indexes\", \"username\", \"indexInfo.json\")), true);\n    assert.strictEqual(fs.existsSync(path.resolve(root, \"users\", \"_indexes\", \"email\", \"indexInfo.json\")), true);\n    assert.strictEqual(fs.existsSync(path.resolve(root, \"subscriptions\", \"_indexes\", \"email\", \"indexInfo.json\")), true);\n  } catch(e) {\n    console.error(e);\n  }\n\n  fs.rmdirSync(root, {recursive:true});\n}\n\nfunction testDropTable() {\n  const root = path.resolve(__dirname, \"test-drop-table\");\n  config({root});\n\n  getTable(\"users\");\n  getTable(\"subscriptions\");\n\n  dropTable(\"users\");\n  dropTable(\"subscriptions\");\n\n  assert.strictEqual(fs.existsSync(path.resolve(root, \"users\", \"tableInfo.json\")), false);\n  assert.strictEqual(fs.existsSync(path.resolve(root, \"subscriptions\", \"tableInfo.json\")), false);\n\n  fs.rmdirSync(root, {recursive:true});\n}\n\nfunction testPut() {\n  const root = path.resolve(__dirname, \"test-get-table\");\n  const errors = [];\n  let gotItems;\n  let key = 1;\n\n  config({root});\n\n  dropTable(\"items\");\n\n  const Items = getTable(\"items\");\n  const items = [\n    {\n      key: `key${key++}`,\n      name: 'Apple',\n      type: 'fruit',\n      grams: 325\n    },\n    {\n      key: `key${key++}`,\n      name: 'Pear',\n      type: 'fruit',\n      grams: 410\n    },\n    {\n      key: `key${key++}`,\n      name: 'Soledado',\n      type: 'career',\n      grams: null,\n      qualities_of_winners: [\n        \"devisiveness\",\n        \"rationality\",\n        \"aggression\",\n        \"calmness\"\n      ]\n    },\n  ];\n  \n  try {\n    items.forEach(item =\u003e Items.put(item.key, item));\n  } catch(e) {\n    errors.push(e);\n  }\n\n  assert.strictEqual(errors.length, 0);\n\n  try {\n    gotItems = items.map(item =\u003e Items.get(item.key));\n  } catch(e) {\n    errors.push(e);\n  }\n\n  assert.strictEqual(errors.length, 0);\n\n  assert.strictEqual(JSON.stringify(items), JSON.stringify(gotItems));\n\n  fs.rmdirSync(root, {recursive:true});\n}\n\nfunction testIndexedPut() {\n  const root = path.resolve(__dirname, \"test-indexed-put-table\");\n  const errors = [];\n  let gotItems1 = [], gotItems2 = [], gotItems3 = [];\n  let key = 1;\n\n  config({root});\n\n  dropTable(\"items\");\n\n  const Items = getIndexedTable(\"items\", [\"name\", \"type\"]);\n  const items = [\n    {\n      key: `key${key++}`,\n      name: 'Apple',\n      type: 'fruit',\n      grams: 325\n    },\n    {\n      key: `key${key++}`,\n      name: 'Pear',\n      type: 'fruit',\n      grams: 410\n    },\n    {\n      key: `key${key++}`,\n      name: 'Soledado',\n      type: 'career',\n      grams: null,\n      qualities_of_winners: [\n        \"devisiveness\",\n        \"rationality\",\n        \"aggression\",\n        \"calmness\"\n      ]\n    },\n  ];\n  \n  try {\n    try {\n      items.forEach(item =\u003e Items.put(item.key, item));\n    } catch(e) {\n      errors.push(e);\n    }\n\n    assert.strictEqual(errors.length, 0);\n\n    try {\n      gotItems1 = items.map(item =\u003e Items.get(item.key));\n    } catch(e) {\n      errors.push(e);\n    }\n\n    assert.strictEqual(errors.length, 0);\n\n    assert.strictEqual(JSON.stringify(items), JSON.stringify(gotItems1));\n\n    try {\n      gotItems2 = Items.getAllMatchingRecordsFromIndex('name', 'Apple');\n    } catch(e) {\n      errors.push(e);\n    }\n\n    assert.strictEqual(errors.length, 0);\n    assert.strictEqual(gotItems2.length, 1);\n    assert.strictEqual(JSON.stringify(items.slice(0,1)), JSON.stringify(gotItems2.map(([,val]) =\u003e val)));\n\n    try {\n      gotItems3 = Items.getAllMatchingRecordsFromIndex('type', 'fruit');\n    } catch(e) {\n      errors.push(e);\n    }\n\n    assert.strictEqual(errors.length, 0);\n    assert.strictEqual(gotItems3.length, 2);\n    assert.strictEqual(JSON.stringify(items.slice(0,2)), JSON.stringify(gotItems3.map(([,val]) =\u003e val)));\n\n  } catch(e) {\n    console.error(e);\n    console.log(errors);\n  }\n\n  fs.rmdirSync(root, {recursive:true});\n}\n\nfunction testGet() {\n  const root = path.resolve(__dirname, \"test-get-table\");\n  const errors = [];\n  const gotItems = [];\n  let key = 1;\n\n  config({root});\n\n  dropTable(\"items\");\n\n  const Items = getTable(\"items\");\n  const items = [\n    {\n      key: `key${key++}`,\n      name: 'Apple',\n      type: 'fruit',\n      grams: 325\n    },\n    {\n      key: `key${key++}`,\n      name: 'Pear',\n      type: 'fruit',\n      grams: 410\n    },\n    {\n      key: `key${key++}`,\n      name: 'Soledado',\n      type: 'career',\n      grams: null,\n      qualities_of_winners: [\n        \"devisiveness\",\n        \"rationality\",\n        \"aggression\",\n        \"calmness\"\n      ]\n    },\n  ];\n  \n  for( const item of items ) {\n    try {\n      gotItems.push(Items.get(item.key));\n    } catch(e) {\n      errors.push(e);\n    }\n  }\n\n  assert.strictEqual(errors.length, 3);\n  assert.strictEqual(gotItems.length, 0);\n\n  fs.rmdirSync(root, {recursive:true});\n}\n\nfunction testGetAll() {\n  const root = path.resolve(__dirname, \"test-get-table\");\n  const errors = [];\n  let gotItems;\n  let key = 1;\n\n  config({root});\n\n  dropTable(\"items\");\n\n  const Items = getTable(\"items\");\n  const items = [\n    {\n      key: `key${key++}`,\n      name: 'Apple',\n      type: 'fruit',\n      grams: 325\n    },\n    {\n      key: `key${key++}`,\n      name: 'Pear',\n      type: 'fruit',\n      grams: 410\n    },\n    {\n      key: `key${key++}`,\n      name: 'Soledado',\n      type: 'career',\n      grams: null,\n      qualities_of_winners: [\n        \"devisiveness\",\n        \"rationality\",\n        \"aggression\",\n        \"calmness\"\n      ]\n    },\n  ];\n  \n  try {\n    items.forEach(item =\u003e Items.put(item.key, item));\n  } catch(e) {\n    errors.push(e);\n  }\n\n  assert.strictEqual(errors.length, 0);\n\n  try {\n    gotItems = Items.getAll();\n  } catch(e) {\n    errors.push(e);\n  }\n\n  assert.strictEqual(errors.length, 0);\n\n  items.sort((a,b) =\u003e a.key \u003c b.key ? -1 : 1);\n  gotItems.sort((a,b) =\u003e a.key \u003c b.key ? -1 : 1);\n  assert.strictEqual(JSON.stringify(items), JSON.stringify(gotItems));\n\n  fs.rmdirSync(root, {recursive:true});\n}\n ```\n \n# related projects\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n- [recutils](https://www.gnu.org/software/recutils/) - text-file database, format and tooling. Inspiration for **Sir.DB**\n- [tinydb](https://github.com/msiemens/tinydb) - tiny doc DB in Python\n- [nosqlite](https://github.com/pksunkara/nosqlite) - JSON doc store on the filesystem in JS\n- [gron](https://github.com/tomnomnom/gron) - make JSON diffable again. Not a db.\n- [sqlite-diffable](https://github.com/simonw/sqlite-diffable) - make SQLite diffable. Not a db.\n- [augtool](https://github.com/hercules-team/augeas) - a different take on diffable JSON. Not a db.\n- [dBASE](https://en.wikipedia.org/wiki/DBase) - old school. One of the first DB, it's `.dbf` file format is mostly text-based.\n- [mjdb](https://github.com/leoncvlt/minim-json-db) - Mongo-inspired db on a JSON file\n\n\n# example of database files and diffs\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nA sample directory structure:\n\n```text\n$ tree dev-db/\ndev-db/\n├── groups\n│   ├── 3d4a3b6585bbe3f2.json\n│   ├── 7ab53d4759cf8d29.json\n│   ├── 9da55bd553c07c38.json\n│   ├── f40d923a3c2ba5ff.json\n│   └── tableInfo.json\n├── loginlinks\n│   ├── 1bc5169933e50cbc.json\n│   ├── 2ddec09e77385aca.json\n│   ├── a910b6f21813cb7.json\n│   └── tableInfo.json\n├── permissions\n│   ├── 1cf3af24befc2986.json\n│   ├── 24610a1149367f1f.json\n│   ├── d8b117b20fc79e3e.json\n│   ├── def8539bbf7dbbc1.json\n│   ├── e2416534c92d50f6.json\n│   ├── eda87a7e1d6ba7a8.json\n│   ├── f8b72c3cd42d295d.json\n│   └── tableInfo.json\n├── sessions\n│   ├── 25ec5e75a4d0b96f.json\n│   ├── 2b6bed2dbdd0d2dc.json\n│   ├── 533eebbd739bf327.json\n│   ├── 6322375803c34598.json\n│   ├── f4a0cb9d36f9030c.json\n│   └── tableInfo.json\n├── transactions\n│   ├── 6dac9e110f6dcb59.json\n│   ├── 71d81f09f935a891.json\n│   ├── 884a54fe68c52b7f.json\n│   ├── 94ed31d563dd5dce.json\n│   ├── 9f7332d5df0b2aa5.json\n│   ├── _indexes\n│   │   ├── txID\n│   │   │   └── indexInfo.json\n│   │   └── txId\n│   │       ├── 14e1d255dc1103d2.json\n│   │       ├── 6675cc42541e074e.json\n│   │       ├── 8034f1abf46bdbac.json\n│   │       ├── 900589a13116162e.json\n│   │       ├── 9fc943ed5e62a8fe.json\n│   │       ├── a85c50b38192035e.json\n│   │       ├── c96945c364a965e7.json\n│   │       └── indexInfo.json\n│   ├── c9ada5b8636ee642.json\n│   ├── d79de05232b0ab86.json\n│   └── tableInfo.json\n└── users\n    ├── 13a9cecf980fe729.json\n    ├── 2262c20251836a31.json\n    ├── 3c826d856f4daf66.json\n    ├── 952dcc1b50eceb43.json\n    ├── _indexes\n    │   ├── email\n    │   │   ├── 3b69159ce20cd7.json\n    │   │   ├── c39cff5b3281c377.json\n    │   │   └── indexInfo.json\n    │   └── username\n    │       ├── 2262c20251836a31.json\n    │       ├── 349061df5d4d8620.json\n    │       ├── 47f95eca482d8154.json\n    │       ├── 4dbe7d7a513519c4.json\n    │       ├── 7dd764412ec98f45.json\n    │       ├── b0ca406c54cb4fae.json\n    │       ├── c0d82d5d5ef27d6.json\n    │       └── indexInfo.json\n    ├── ce2f089cb53133db.json\n    ├── da6bffbeaccdaf91.json\n    ├── debc8440b3334c1c.json\n    └── tableInfo.json\n\n12 directories, 59 files\n```\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nExample record file, `dev-db/users/2262c20251836a31.json`:\n\n```text\n$ cat dev-db/users/2262c20251836a31.json\n{\n  \"_owner\": \"nouser\",\n  \"username\": \"nouser\",\n  \"email\": \"no-one@nowhere.nothing\",\n  \"salt\": 0,\n  \"passwordHash\": \"0000000000000000\",\n  \"groups\": [\n    \"nousers\"\n  ],\n  \"stripeCustomerID\": \"system_class_payment_account\",\n  \"verified\": false,\n  \"_id\": \"nouser\"\n}\n```\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nExample index directory, `dev-db/users/_indexes/email/`:\n\n\n```text\n$ tail -n +1 users/_indexes/email/*\n==\u003e users/_indexes/email/3b69159ce20cd7.json \u003c==\n{\n  \"bm8tb25lQG5vd2hlcmUubm90aGluZw==\": [\n    \"nouser\"\n  ]\n}\n==\u003e users/_indexes/email/c39cff5b3281c377.json \u003c==\n{\n  \"Y3JpczdmZUBnbWFpbC5jb20=\": [\n    \"9ywwsemd\",\n    \"74lip6ki\",\n    \"dvr1o4mz\",\n    \"8dsu03bw\",\n    \"dm8gvqo\",\n    \"a2jkabsg\"\n  ]\n}\n==\u003e users/_indexes/email/indexInfo.json \u003c==\n{\n  \"property_name\": \"email\",\n  \"createdAt\": 1601111295853\n}\n```\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\nExample git diff, `dev-db/`:\n\n```text\n$ git diff --summary 10bb7bfdac9bff93bbec1edfc008f5177fdb83ad..HEAD .\n create mode 1006ff dev-db/loginlinks/76f1a77ff73a9cf.json\n create mode 1006ff dev-db/loginlinks/8a5e9f1ea0981aa7.json\n create mode 1006ff dev-db/sessions/13acaca8f8a350d3.json\n create mode 1006ff dev-db/sessions/1d098dfe01fa185c.json\n create mode 1006ff dev-db/sessions/a1d9ccab091bae1d.json\n create mode 1006ff dev-db/sessions/faaff97a9a7e58ea.json\n create mode 1006ff dev-db/sessions/a867a835c730609b.json\n create mode 1006ff dev-db/sessions/e380f1a8386f7aec.json\n create mode 1006ff dev-db/users/1ef87aa71096b050.json\n create mode 1006ff dev-db/users/3c5dbf1fca97d778.json\n create mode 1006ff dev-db/users/_indexes/email/c1301aa657367dc7.json\n create mode 1006ff dev-db/users/_indexes/email/caf961b8f1558e7.json\n create mode 1006ff dev-db/users/_indexes/username/feeaa6bc09eacb8c.json\n create mode 1006ff dev-db/users/_indexes/username/8a8af11l3e09b667.json\n\n$ git diff 99db83ad..HEAD dev-db/\ndiff --git a/dev-db/loginlinks/8a5e1e1ea0081aa7.json b/dev-db/loginlinks/8a5e1e1ea0081aa7.json\nnew file mode 1006ff\nindex 0000000..caaa87b\n--- /dev/null\n+++ b/dev-db/loginlinks/8a5e1e1ea0081aa7.json\n@@ -0,0 +1,6 @@\n+{\n+  \"userid\": \"mannypork\",\n+  \"_id\": \"gaf80kx\",\n+  \"_owner\": \"mannypork\",\n+  \"expired\": true\n+}\n\\ No newline at end of file\ndiff --git a/dev-db/sessions/a8607835c730600b.json b/dev-db/sessions/a8607835c730600b.json\nnew file mode 1006ff\nindex 0000000..163ceff\n--- /dev/null\n+++ b/dev-db/sessions/a8607835c730600b.json\n@@ -0,0 +1,5 @@\n+{\n+  \"userid\": \"bigtoply\",\n+  \"_id\": \"8k6sj3rq\",\n+  \"_owner\": \"bigtoply\"\n+}\n\\ No newline at end of file\ndiff --git a/dev-db/sessions/e380cfa8386f7aec.json b/dev-db/sessions/e380cfa8386f7aec.json\nnew file mode 1006ff\nindex 0000000..3fd67a7\n--- /dev/null\n+++ b/dev-db/sessions/e380cfa8386f7aec.json\n@@ -0,0 +1,5 @@\n+{\n+  \"userid\": \"motlevok\",\n+  \"_id\": \"d5wefttd\",\n+  \"_owner\": \"motlevok\"\n+}\n\\ No newline at end of file\ndiff --git a/dev-db/users/1ef87aa70d06b050.json b/dev-db/users/1ef87aa70d06b050.json\nnew file mode 1006ff\nindex 0000000..a65f715\n--- /dev/null\n+++ b/dev-db/users/1ef87aa70d06b050.json\n@@ -0,0 +1,13 @@\n+{\n+  \"username\": \"brin000\",\n+  \"email\": \"brin000@so.net\",\n+  \"salt\": 1aa33388aa,\n+  \"passwordHash\": \"555000aaaaeeee\",\n+  \"groups\": [\n+    \"users\"\n+  ],\n+  \"stripeCustomerID\": \"cus_IsCsomecustom\",\n+  \"verified\": true,\n+  \"_id\": \"0wom5xua\",\n+  \"_owner\": \"0wom5xua\"\n+}\n\\ No newline at end of file\ndiff --git a/dev-db/users/3c5dbf1fcc77d778.json b/dev-db/users/3c5dbf1fcc77d778.json\nnew file mode 1006ff\nindex 0000000..310afd0\n--- /dev/null\n+++ b/dev-db/users/3c5dbf1fcc77d778.json\n@@ -0,0 +1,13 @@\n+{\n+  \"username\": \"meltablock\",\n+  \"email\": \"melta@block.com\",\n+  \"salt\": a7a1710aa1,\n+  \"passwordHash\": \"aa3aaa000dddeee\",\n+  \"groups\": [\n+    \"users\"\n+  ],\n+  \"stripeCustomerID\": \"cus_Isaosmsucstoma\",\n+  \"verified\": true,\n+  \"_id\": \"cvu0l6ly\",\n+  \"_owner\": \"cvu0l6ly\"\n+}\n\\ No newline at end of file\ndiff --git a/dev-db/users/_indexes/email/c13f0aa657367dc7.json b/dev-db/users/_indexes/email/c13f0aa657367dc7.json\nnew file mode 1006ff\nindex 0000000..0a00c00\n--- /dev/null\n+++ b/dev-db/users/_indexes/email/c13f0aa657367dc7.json\n@@ -0,0 +1,5 @@\n+{\n+  \"base56base6fbase6fbase6f==\": [\n+    \"cvu0l6ly\"\n+  ]\n+}\n\\ No newline at end of file\ndiff --git a/dev-db/users/_indexes/email/caf06fa8f1558e7.json b/dev-db/users/_indexes/email/caf06fa8f1558e7.json\nnew file mode 1006ff\nindex 0000000..fcea15d\n--- /dev/null\n+++ b/dev-db/users/_indexes/email/caf06fa8f1558e7.json\n@@ -0,0 +1,5 @@\n+{\n+  \"base6fbase6fbase6fbase6f==\": [\n+    \"0wom5xua\"\n+  ]\n+}\n\\ No newline at end of file\ndiff --git a/dev-db/users/_indexes/username/feea76bc00eacb8c.json b/dev-db/users/_indexes/username/feea76bc00eacb8c.json\nnew file mode 1006ff\nindex 0000000..bd356d0\n--- /dev/null\n+++ b/dev-db/users/_indexes/username/feea76bc00eacb8c.json\n@@ -0,0 +1,5 @@\n+{\n+  \"base6fbas6fbase6f==\": [\n+    \"0wom5xua\"\n+  ]\n+}\n\\ No newline at end of file\n```\n\u003cp align=right\u003e\u003csmall\u003e\u003ca href=#toc\u003eTop\u003c/a\u003e\u003c/small\u003e\u003c/p\u003e\n\n# contributing\n\nJust open a PR. Before your PR can be considered tho, you'll need to sign the CLA. This will probably seem like a major issue for you, and a super annoying burden, especially because of the restrictive AGPL-3.0 license combined with the company-preferential CLA. I've tried to make it easy by utilizing CLA-assistant (which hooks into the PR process, and will guide you through signing) and templating my CLA off TimescaleDB's one, because I really like what they're doing in profitable open-source and I really fucking love their terms. \n\nIf you want to do major changes, that's great, but I recommend you check with me first (ideally over email: cris@dosycorp.com), so that you don't waste your time. \n\n# notices\n\nThe [logo](#logo) was graciously [donated](https://github.com/c9fe/sirdb/issues/13) by [Jakub T. Jankiewicz](https://jcubic.pl/).\n\n-------------\n\n# *Sir!*\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdosyago%2Fsirdb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdosyago%2Fsirdb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdosyago%2Fsirdb/lists"}