{"id":13394359,"url":"https://github.com/beakerbrowser/webdb","last_synced_at":"2025-03-13T20:31:28.759Z","repository":{"id":57273830,"uuid":"98339148","full_name":"beakerbrowser/webdb","owner":"beakerbrowser","description":"The Web is your database.","archived":true,"fork":false,"pushed_at":"2018-12-02T19:30:07.000Z","size":474,"stargazers_count":399,"open_issues_count":13,"forks_count":45,"subscribers_count":28,"default_branch":"master","last_synced_at":"2024-05-05T16:22:18.012Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@beaker/webdb","language":"JavaScript","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/beakerbrowser.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-07-25T18:46:27.000Z","updated_at":"2024-04-25T22:37:49.000Z","dependencies_parsed_at":"2022-09-17T10:12:48.605Z","dependency_job_id":null,"html_url":"https://github.com/beakerbrowser/webdb","commit_stats":null,"previous_names":["beakerbrowser/ingestdb","beakerbrowser/injestdb"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beakerbrowser%2Fwebdb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beakerbrowser%2Fwebdb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beakerbrowser%2Fwebdb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beakerbrowser%2Fwebdb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beakerbrowser","download_url":"https://codeload.github.com/beakerbrowser/webdb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242819369,"owners_count":20190442,"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-30T17:01:16.992Z","updated_at":"2025-03-13T20:31:28.360Z","avatar_url":"https://github.com/beakerbrowser.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Using Dat"],"sub_categories":["Hosting \u0026 Dat Management"],"readme":"# WebDB\n\nA database that reads and writes records on dat:// websites. [How it works](#how-it-works)\n\n## Example\n\nInstantiate:\n\n```js\n// in the browser\nconst WebDB = require('@beaker/webdb')\nvar webdb = new WebDB('webdb-example')\n\n// in nodejs\nconst DatArchive = require('node-dat-archive')\nconst WebDB = require('@beaker/webdb')\nvar webdb = new WebDB('./webdb-example', {DatArchive})\n```\n\nDefine your table:\n\n```js\nwebdb.define('people', {\n  // validate required attributes before indexing\n  validate(record) {\n    assert(record.firstName \u0026\u0026 typeof record.firstName === 'string')\n    assert(record.lastName \u0026\u0026 typeof record.lastName === 'string')\n    return true\n  },\n\n  // secondary indexes for fast queries (optional)\n  index: ['lastName', 'lastName+firstName', 'age'],\n\n  // files to index\n  filePattern: [\n    '/person.json',\n    '/people/*.json'\n  ]\n})\n```\n\nThen open the DB:\n\n```js\nawait webdb.open()\n```\n\nNext we add archives to be indexed into the database.\n\n```js\nawait webdb.indexArchive('dat://alice.com')\nawait webdb.indexArchive(['dat://bob.com', 'dat://carla.com'])\n```\n\nNow we can begin querying the database for records.\n\n```js\n// get any person record where lastName === 'Roberts'\nvar mrRoberts = await webdb.people.get('lastName', 'Roberts')\n\n// response attributes:\nconsole.log(mrRoberts.lastName)          // =\u003e 'Roberts'\nconsole.log(mrRoberts)                   // =\u003e {lastName: 'Roberts', ...}\nconsole.log(mrRoberts.getRecordURL())    // =\u003e 'dat://foo.com/bar.json'\nconsole.log(mrRoberts.getRecordOrigin()) // =\u003e 'dat://foo.com'\nconsole.log(mrRoberts.getIndexedAt())    // =\u003e 1511913554723\n\n// get any person record named Bob Roberts\nvar mrRoberts = await webdb.people.get('lastName+firstName', ['Roberts', 'Bob'])\n\n// get all person records with the 'Roberts' lastname\nvar robertsFamily = await webdb.people\n  .where('lastName')\n  .equalsIgnoreCase('roberts')\n  .toArray()\n\n// get all person records with the 'Roberts' lastname\n// and a firstname that starts with 'B'\n// - this uses a compound index\nvar robertsFamilyWithaBName = await webdb.people\n  .where('lastName+firstName')\n  .between(['Roberts', 'B'], ['Roberts', 'B\\uffff'])\n  .toArray()\n\n// get all person records on a given origin\n// - `:origin` is an auto-generated attribute\nvar personsOnBobsSite = await webdb.people\n  .where(':origin')\n  .equals('dat://bob.com')\n  .toArray()\n\n// get the 30 oldest people indexed\nvar oldestPeople = await webdb.people\n  .orderBy('age')\n  .reverse() // oldest first\n  .limit(30)\n  .toArray()\n\n// count the # of young people\nvar oldestPeople = await webdb.people\n  .where('age')\n  .belowOrEqual(18)\n  .count()\n```\n\nWe can also use WebDB to create, modify, and delete records (and their matching files).\n\n```js\n// set the record\nawait webdb.people.put('dat://bob.com/person.json', {\n  firstName: 'Bob',\n  lastName: 'Roberts',\n  age: 31\n})\n\n// update the record if it exists\nawait webdb.people.update('dat://bob.com/person.json', {\n  age: 32\n})\n\n// update or create the record\nawait webdb.people.upsert('dat://bob.com/person.json', {\n  age: 32\n})\n\n// delete the record\nawait webdb.people.delete('dat://bob.com/person.json')\n\n// update the spelling of all Roberts records\nawait webdb.people\n  .where('lastName')\n  .equals('Roberts')\n  .update({lastName: 'Robertos'})\n\n// increment the age of all people under 18\nvar oldestPeople = await webdb.people\n  .where('age')\n  .belowOrEqual(18)\n  .update(record =\u003e {\n    record.age = record.age + 1\n  })\n\n// delete the 30 oldest people\nvar oldestPeople = await webdb.people\n  .orderBy('age')\n  .reverse() // oldest first\n  .limit(30)\n  .delete()\n```\n\n## Table of Contents\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [How to use WebDB](#how-to-use-webdb)\n  - [Table definitions](#table-definitions)\n  - [Indexing sites](#indexing-sites)\n  - [Creating queries](#creating-queries)\n  - [Applying linear-scan filters](#applying-linear-scan-filters)\n  - [Applying query modifiers](#applying-query-modifiers)\n  - [Executing 'read' queries](#executing-read-queries)\n  - [Executing 'write' queries](#executing-write-queries)\n  - [Table helper methods](#table-helper-methods)\n  - [Record methods](#record-methods)\n  - [Handling multiple schemas](#handling-multiple-schemas)\n  - [Preprocessing records](#preprocessing-records)\n  - [Serializing records](#serializing-records)\n  - [Using JSON-Schema to validate](#using-json-schema-to-validate)\n  - [Helper tables](#helper-tables)\n- [Class: WebDB](#class-webdb)\n  - [new WebDB([name, opts])](#new-webdbname-opts)\n  - [WebDB.delete([name])](#webdbdeletename)\n- [Instance: WebDB](#instance-webdb)\n  - [webdb.open()](#webdbopen)\n  - [webdb.close()](#webdbclose)\n  - [webdb.delete()](#webdbdelete)\n  - [webdb.define(name, definition)](#webdbdefinename-definition)\n  - [webdb.indexArchive(url[, opts])](#webdbindexarchiveurl-opts)\n  - [webdb.unindexArchive(url)](#webdbunindexarchiveurl)\n  - [webdb.indexFile(archive, filepath)](#webdbindexfilearchive-filepath)\n  - [webdb.indexFile(url)](#webdbindexfileurl)\n  - [webdb.unindexFile(archive, filepath)](#webdbunindexfilearchive-filepath)\n  - [webdb.unindexFile(url)](#webdbunindexfileurl)\n  - [webdb.listSources()](#webdblistsources)\n  - [webdb.isSource(url)](#webdbissourceurl)\n  - [Event: 'open'](#event-open)\n  - [Event: 'open-failed'](#event-open-failed)\n  - [Event: 'indexes-reset'](#event-indexes-reset)\n  - [Event: 'indexes-updated'](#event-indexes-updated)\n  - [Event: 'source-indexing'](#event-source-indexing)\n  - [Event: 'source-index-progress'](#event-source-index-progress)\n  - [Event: 'source-indexed'](#event-source-indexed)\n  - [Event: 'source-missing'](#event-source-missing)\n  - [Event: 'source-found'](#event-source-found)\n  - [Event: 'source-error'](#event-source-error)\n- [Instance: WebDBTable](#instance-webdbtable)\n  - [table.count()](#tablecount)\n  - [table.delete(url)](#tabledeleteurl)\n  - [table.each(fn)](#tableeachfn)\n  - [table.filter(fn)](#tablefilterfn)\n  - [table.get(url)](#tablegeturl)\n  - [table.get(key, value)](#tablegetkey-value)\n  - [table.isRecordFile(url)](#tableisrecordfileurl)\n  - [table.limit(n)](#tablelimitn)\n  - [table.listRecordFiles(url)](#tablelistrecordfilesurl)\n  - [table.name](#tablename)\n  - [table.offset(n)](#tableoffsetn)\n  - [table.orderBy(key)](#tableorderbykey)\n  - [table.put(url, record)](#tableputurl-record)\n  - [table.query()](#tablequery)\n  - [table.reverse()](#tablereverse)\n  - [table.schema](#tableschema)\n  - [table.toArray()](#tabletoarray)\n  - [table.update(url, updates)](#tableupdateurl-updates)\n  - [table.update(url, fn)](#tableupdateurl-fn)\n  - [table.upsert(url, updates)](#tableupserturl-updates)\n  - [table.upsert(url, fn)](#tableupserturl-fn)\n  - [table.where(key)](#tablewherekey)\n  - [Event: 'put-record'](#event-put-record)\n  - [Event: 'del-record'](#event-del-record)\n- [Instance: WebDBQuery](#instance-webdbquery)\n  - [query.clone()](#queryclone)\n  - [query.count()](#querycount)\n  - [query.delete()](#querydelete)\n  - [query.each(fn)](#queryeachfn)\n  - [query.eachKey(fn)](#queryeachkeyfn)\n  - [query.eachUrl(fn)](#queryeachurlfn)\n  - [query.filter(fn)](#queryfilterfn)\n  - [query.first()](#queryfirst)\n  - [query.keys()](#querykeys)\n  - [query.last()](#querylast)\n  - [query.limit(n)](#querylimitn)\n  - [query.offset(n)](#queryoffsetn)\n  - [query.orderBy(key)](#queryorderbykey)\n  - [query.put(record)](#queryputrecord)\n  - [query.urls()](#queryurls)\n  - [query.reverse()](#queryreverse)\n  - [query.toArray()](#querytoarray)\n  - [query.uniqueKeys()](#queryuniquekeys)\n  - [query.until(fn)](#queryuntilfn)\n  - [query.update(updates)](#queryupdateupdates)\n  - [query.update(fn)](#queryupdatefn)\n  - [query.where(key)](#querywherekey)\n- [Instance: WebDBWhereClause](#instance-webdbwhereclause)\n  - [where.above(value)](#whereabovevalue)\n  - [where.aboveOrEqual(value)](#whereaboveorequalvalue)\n  - [where.anyOf(values)](#whereanyofvalues)\n  - [where.anyOfIgnoreCase(values)](#whereanyofignorecasevalues)\n  - [where.below(value)](#wherebelowvalue)\n  - [where.belowOrEqual(value)](#wherebeloworequalvalue)\n  - [where.between(lowerValue, upperValue[, options])](#wherebetweenlowervalue-uppervalue-options)\n  - [where.equals(value)](#whereequalsvalue)\n  - [where.equalsIgnoreCase(value)](#whereequalsignorecasevalue)\n  - [where.noneOf(values)](#wherenoneofvalues)\n  - [where.notEqual(value)](#wherenotequalvalue)\n  - [where.startsWith(value)](#wherestartswithvalue)\n  - [where.startsWithAnyOf(values)](#wherestartswithanyofvalues)\n  - [where.startsWithAnyOfIgnoreCase(values)](#wherestartswithanyofignorecasevalues)\n  - [where.startsWithIgnoreCase(value)](#wherestartswithignorecasevalue)\n- [How it works](#how-it-works)\n  - [Why not put all records in one file?](#why-not-put-all-records-in-one-file)\n    - [Performance](#performance)\n    - [Linkability](#linkability)\n- [Changelog](#changelog)\n  - [4.1.0](#410)\n  - [4.0.0](#400)\n  - [3.0.0](#300)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## How to use WebDB\n\n### Table definitions\n\nUse the [`define()`](#webdbdefinename-definition) method to define your tables, and then call [`webdb.open()`](#webdbopen) to create them.\n\n### Indexing sites\n\nUse [`indexArchive()`](#webdbindexarchiveurl-opts) and [`unindexArchive()`](#webdbunindexarchiveurl) to control which sites will be indexed.\nIndexed data will persist in the database until `unindexArchive()` is called.\nHowever, `indexArchive()` should always be called on load to get the latest data.\n\nIf you only want to index the current state of a site, and do not want to watch for updates, call `indexArchive()` with the `{watch: false}` option.\n\nYou can index and de-index individual files using [`indexFile()`](#webdbindexfileurl) and [`unindexFile()`](#webdbunindexfileurl).\n\n### Creating queries\n\nQueries are created with a chained function API.\nYou can create a query from the table object using [`.query()`](#tablequery), [`.where()`](#tablewherekey), or [`.orderBy()`](#tableorderbykey).\nThe `where()` method returns an object with [multiple filter functions that you can use](#instance-webdbwhereclause).\n\n```js\nvar myQuery = webdb.query().where('foo').equals('bar')\nvar myQuery = webdb.where('foo').equals('bar') // equivalent\nvar myQuery = webdb.where('foo').startsWith('ba')\nvar myQuery = webdb.where('foo').between('bar', 'baz', {includeLower: true, includeUpper: false})\n```\n\nEach query has a primary key.\nBy default, this is the `url` attribute, but it can be changed using [`.where()`](#querywherekey) or [`.orderBy()`](#queryorderbykey).\nIn this example, the primary key becomes 'foo':\n\n```js\nvar myQuery = webdb.orderBy('foo')\n```\n\nAt this time, the primary key must be one of the indexed attributes.\nThere are 2 indexes created automatically for every record: `url` and `origin`.\nThe other indexes are specified in your table's [`define()`](#webdbdefinename-definition) call using the `index` option.\n\n### Applying linear-scan filters\n\nAfter the primary key index is applied, you can apply additional filters using [filter(fn)](#queryfilterfn) and [until(fn)](#queryuntilfn).\nThese methods are called \"linear scan\" filters because they require each record to be loaded and then filtered out.\n(Until stops when it hits the first `false` response.)\n\n```js\nvar myQuery = webdb.query()\n  .where('foo').equals('bar')\n  .filter(record =\u003e record.beep == 'boop') // additional filter\n```\n\n### Applying query modifiers\n\nYou can apply the following modifiers to your query to alter the output:\n\n  - [limit(n)](#querylimitn)\n  - [offset(n)](#queryoffsetn)\n  - [reverse()](#queryreverse)\n\n### Executing 'read' queries\n\nOnce your query has been defined, you can execute and read the results using one of these methods:\n\n  - [count()](#querycount)\n  - [each(fn)](#queryeachfn)\n  - [eachKey(fn)](#queryeachkeyfn)\n  - [eachUrl(fn)](#queryeachurlfn)\n  - [first()](#queryfirst)\n  - [keys()](#querykeys)\n  - [last()](#querylast)\n  - [urls()](#queryurls)\n  - [toArray()](#querytoarray)\n  - [uniqueKeys()](#queryuniquekeys)\n\n### Executing 'write' queries\n\nOnce your query has been defined, you can execute and *modify* the results using one of these methods:\n\n  - [delete()](#querydelete)\n  - [put(record)](#queryputrecord)\n  - [update(updates)](#queryupdateupdates)\n  - [update(fn)](#queryupdatefn)\n  \nIf you try to modify rows in archives that are not writable, WebDB will throw an error.\n\n### Table helper methods\n\nThe following methods exist on the table object for query reads and writes:\n\n  - [table.delete(url)](#tabledeleteurl)\n  - [table.each(fn)](#tableeachfn)\n  - [table.get(url)](#tablegeturl)\n  - [table.get(key, value)](#tablegetkey-value)\n  - [table.put(url, record)](#tableputurl-record)\n  - [table.toArray()](#tabletoarray)\n  - [table.update(url, updates)](#tableupdateurl-updates)\n  - [table.update(url, fn)](#tableupdateurl-fn)\n  - [table.upsert(url, updates)](#tableupserturl-updates)\n  - [table.upsert(url, fn)](#tableupserturl-fn)\n\n### Record methods\n\n```js\nrecord.getRecordURL()    // =\u003e 'dat://foo.com/bar.json'\nrecord.getRecordOrigin() // =\u003e 'dat://foo.com'\nrecord.getIndexedAt()    // =\u003e 1511913554723\n```\n\nEvery record is emitted in a wrapper object with the following methods:\n\n - `getRecordURL()` The URL of the record.\n - `getRecordOrigin()` The URL of the site the record was found on.\n - `getIndexedAt()` The timestamp of when the record was indexed.\n\nThese attributes can be used in indexes with the following IDs:\n\n - `:url`\n - `:origin`\n - `:indexedAt`\n\nFor instance:\n\n```js\nwebdb.define('things', {\n  // ...\n  index: [\n    ':indexedAt', // ordered by time the record was indexed\n    ':origin+createdAt' // ordered by origin and declared create timestamp (a record attribute)\n  ],\n  // ...\n})\nawait webdb.open()\nwebdb.things.where(':indexedAt').above(Date.now() - ms('1 week'))\nwebdb.things.where(':origin+createdAt').between(['dat://bob.com', 0], ['dat://bob.com', Infinity])\n```\n\n### Handling multiple schemas\n\nSince the Web is a complex place, you'll frequently have to deal with multiple schemas which are slightly different.\nTo deal with this, you can use a definition object to support multiple attribute names under one index.\n\n```js\nwebdb.define('places', {\n  // ...\n\n  index: [\n    // a simple index definition:\n    'name',\n\n    // an object index definition:\n    {name: 'zipCode', def: ['zipCode', 'zip_code']}\n  ]\n\n  // ...\n})\n```\n\nNow, when you run queries on the `'zipCode'` key, you will search against both `'zipCode'` and `'zip_code'`.\nNote, however, that the records emitted from the query will not be changed by WebDB and so they may differ.\n\nFor example:\n\n```js\nwebdb.places.where('zipCode').equals('78705').each(record =\u003e {\n  console.log(record.zipCode) // may be '78705' or undefined\n  console.log(record.zip_code) // may be '78705' or undefined\n})\n```\n\nTo solve this, you can [preprocess records](#preprocessing-records).\n\n### Preprocessing records\n\nSometimes, you need to modify records before they're stored in the database. This can be for a number of reasons:\n\n - Normalization. Small differences in accepted record schemas may need to be merged (see [handling multiple schemas](#handling-multiple-schemas)).\n - Indexing. WebDB's index spec only supports toplevel attributes. If the data is embedded in a sub-object, you'll need to place the data at the top-level.\n - Computed attributes.\n\nFor these cases, you can use the `preprocess(record)` function in the table definition:\n\n```js\nwebdb.define('places', {\n  // ...\n\n  preprocess(record) {\n    // normalize zipCode and zip_code\n    if (record.zip_code) {\n      record.zipCode = record.zip_code\n    }\n\n    // move an attribute to the root object for indexing\n    record.title = record.info.title\n\n    // compute an attribute\n    record.location = `${record.address} ${record.city}, ${record.state} ${record.zipCode}`\n\n    return record\n  }\n\n  // ...\n})\n```\n\nThese attributes will be stored in the WebDB table.\n\n### Serializing records\n\nWhen records are updated by WebDB, they are published to a Dat site as a file.\nSince these files are distributed on the Web, it's wise to avoid adding noise to the record.\n\nTo control the exact record that will be published, you can set the `serialize(record)` function in the table definition:\n\n```js\nwebdb.define('places', {\n  // ...\n\n  serialize(record) {\n    // write the following object to the dat site:\n    return {\n      info: record.info,\n      city: record.city,\n      state: record.state,\n      zipCode: record.zipCode\n    }\n  }\n\n  // ...\n})\n```\n\n### Using JSON-Schema to validate\n\nThe default way to validate records is to provide a validator function.\nIf the function throws an error or returns falsy, the record will not be indexed.\n\nIt can be tedious to write validation functions, so you might want to use JSON-Schema:\n\n```js\nconst Ajv = require('ajv')\nwebdb.define('people', {\n  validate: (new Ajv()).compile({\n    type: 'object',\n    properties: {\n      firstName: {\n        type: 'string'\n      },\n      lastName: {\n        type: 'string'\n      },\n      age: {\n        description: 'Age in years',\n        type: 'integer',\n        minimum: 0\n      }\n    },\n    required: ['firstName', 'lastName']\n  }),\n\n  // ...\n})\n```\n\n### Helper tables\n\nSometimes you need internal storage to help you maintain application state.\nThis may be for interfaces, or data which is private, or for special kinds of indexes.\n\nFor instance, in the [Fritter](https://github.com/beakerbrowser/fritter) app, we needed an index for notifications.\nThis index was conditional: it needed to contain posts which were replies to the user, or likes which were on the user's post.\nFor cases like this, you can use a \"helper table.\"\n\n```js\nwebdb.define('notifications', {\n  helperTable: true,\n  index: ['createdAt'],\n  preprocess (record) {\n    record.createdAt = Date.now()\n  }\n})\n```\n\nWhen the `helperTable` attribute is set to `true` in a table definition, the table will not be used to index Dat archives.\nInstead, it will exist purely in the local data cache, and only contain data which is `.put()` there.\nIn all other respects, it behaves like a normal table.\n\nIn [Fritter](https://github.com/beakerbrowser/fritter), the helper table is used with events to track notifications:\n\n```js\n// track reply notifications\nwebdb.posts.on('put', async ({record, url, origin}) =\u003e {\n  if (origin === userUrl) return // dont index the user's own posts\n  if (isAReplyToUser(record) === false) return // only index replies to the user\n  if (await isNotificationIndexed(url)) return // don't index if already indexed\n  await db.notifications.put(url, {type: 'reply', url})\n})\nwebdb.posts.on('del', async ({url}) =\u003e {\n  if (await isNotificationIndexed(url)) {\n    await db.notifications.delete(url)\n  }\n})\n```\n\n## Class: WebDB\n\n### new WebDB([name, opts])\n\n```js\nvar webdb = new WebDB('mydb')\n```\n\n - `name` String. Defaults to `'webdb'`. If run in the browser, this will be the name of the IndexedDB instance. If run in NodeJS, this will be the path of the LevelDB folder.\n - `opts` Object.\n   - `DatArchive` Constructor. The class constructor for dat archive instances. If in node, you should specify [node-dat-archive](https://npm.im/node-dat-archive).\n\nCreate a new `WebDB` instance.\nThe given `name` will control where the indexes are saved.\nYou can specify different names to run multiple WebDB instances at once.\n\n### WebDB.delete([name])\n\n```js\nawait WebDB.delete('mydb')\n```\n\n - `name` String. Defaults to `'webdb'`. If run in the browser, this will be the name of the IndexedDB instance. If run in NodeJS, this will be the path of the LevelDB folder.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nDeletes the indexes and metadata for the given WebDB.\n\n## Instance: WebDB\n\n### webdb.open()\n\n```js\nawait webdb.open()\n```\n\n - Returns Promise\u0026lt;Object\u0026gt;.\n   - `rebuilds` Array\u0026lt;String\u0026gt;. The tables which were built or rebuilt during setup.\n\nRuns final setup for the WebDB instance.\nThis must be run after [`.define()`](#webdbdefinename-definition) to create the table instances.\n\n### webdb.close()\n\n```js\nawait webdb.close()\n```\n\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nCloses the WebDB instance.\n\n### webdb.delete()\n\n```js\nawait webdb.delete()\n```\n\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nCloses and destroys all indexes in the WebDB instance.\n\nYou can `.delete()` and then `.open()` a WebDB to recreate its indexes.\n\n```js\nawait webdb.delete()\nawait webdb.open()\n```\n\n### webdb.define(name, definition)\n\n - `name` String. The name of the table.\n - `definition` Object.\n   - `index` Array\u0026lt;String or Object\u0026gt;. A list of attributes which should have secondary indexes produced for querying. Each `index` value is a keypath (see https://www.w3.org/TR/IndexedDB/#dfn-key-path) or an object definition (see below).\n   - `filePattern` String or Array\u0026lt;String\u0026gt;. An [anymatch](https://www.npmjs.com/package/anymatch) list of files to index.\n   - `helperTable` Boolean. If true, the table will be used for storing internal data and will not be used to index Dat archives. See [helper tables](#helper-tables)\n   - `validate` Function. A method to accept or reject a file from indexing based on its content. If the method returns falsy or throws an error, the file will not be indexed.\n     - `record` Object.\n     - Returns Boolean.\n   - `preprocess` Function. A method to modify the record after read from the dat site. See [preprocessing records](#preprocessing-records).\n     - `record` Object.\n     - Returns Object.\n   - `serialize` Function. A method to modify the record before write to the dat site. See [serializing records](#serializing-records).\n     - `record` Object.\n     - Returns Object.\n - Returns Void.\n\nCreates a new table on the `webdb` object.\nThe table will be set at `webdb.{name}` and be the `WebDBTable` type.\nThis method must be called before [`open()`](#webdbopen)\n\nIndexed attributes may either be defined as a [keypath string](https://www.w3.org/TR/IndexedDB/#dfn-key-path) or an object definition.\nThe object definition has the following values:\n\n - `name` String. The name of the index.\n - `def` String or Array\u0026lt;String\u0026gt;. The definition of the index.\n\nIf the value of `def` is an array, it supports each definition.\nThis is useful when supporting multiple schemas ([learn more here](#handling-multiple-schemas)).\n\nIn the index definition, you can specify compound indexes with a `+` separator in the keypath.\nYou can also index each value of an array using the `*` sigil at the start of the name.\nSome example index definitions:\n\n```\na simple index           - 'firstName'\nas an object def         - {name: 'firstName', def: 'firstName'}\na compound index         - 'firstName+lastName'\nindex an array's values  - '*favoriteFruits'\nmany keys                - {name: 'firstName', def: ['firstName', 'first_name']}\nmany keys, compound      - {name: 'firstName+lastName', def: ['firstName+lastName', 'first_name+last_name']}\n```\n\nYou can specify which files should be processed into the table using the `filePattern` option.\nIf unspecified, it will default to all json files on the site (`'*.json'`).\n\nExample:\n\n```js\nwebdb.define('people', {\n  validate(record) {\n    assert(record.firstName \u0026\u0026 typeof record.firstName === 'string')\n    assert(record.lastName \u0026\u0026 typeof record.lastName === 'string')\n    return true\n  },\n  index: ['lastName', 'lastName+firstName', 'age'],\n  filePattern: [\n    '/person.json',\n    '/people/*.json'\n  ]\n})\n\nawait webdb.open()\n// the new table will now be defined at webdb.people\n```\n\n### webdb.indexArchive(url[, opts])\n\n```js\nawait webdb.indexArchive('dat://foo.com')\n```\n\n - `url` String or DatArchive or Array\u0026lt;String or DatArchive\u0026gt;. The sites to index.\n - `opts` Object.\n   - `watch` Boolean. Should WebDB watch the archive for changes, and index them immediately? Defaults to true.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nAdd one or more dat:// sites to be indexed.\nThe method will return when the site has been fully indexed.\nThis will add the given archive to the \"sources\" list.\n\n### webdb.unindexArchive(url)\n\n```js\nawait webdb.unindexArchive('dat://foo.com')\n```\n\n - `url` String or DatArchive. The site to deindex.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nRemove a dat:// site from the dataset.\nThe method will return when the site has been fully de-indexed.\nThis will remove the given archive from the \"sources\" list.\n\n### webdb.indexFile(archive, filepath)\n\n```js\nawait webdb.indexFile(fooArchive, '/bar.json')\n```\n\n - `archive` DatArchive. The site containing the file to index.\n - `filepath` String. The path of the file to index.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nAdd a single file to the index.\nThe method will return when the file has been indexed.\n\nThis will not add the file or its archive to the \"sources\" list.\nUnlike `indexArchive`, WebDB will not watch the file after this call.\n\n### webdb.indexFile(url)\n\n```js\nawait webdb.indexFile('dat://foo.com/bar.json')\n```\n\n - `url` String. The url of the file to index.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nAdd a single file to the index.\nThe method will return when the file has been indexed.\n\nThis will not add the file or its archive to the \"sources\" list.\n\n### webdb.unindexFile(archive, filepath)\n\n```js\nawait webdb.unindexFile(fooArchive, '/bar.json')\n```\n\n - `archive` DatArchive. The site containing the file to deindex.\n - `filepath` String. The path of the file to deindex.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nRemove a single file from the dataset.\nThe method will return when the file has been de-indexed.\n\n### webdb.unindexFile(url)\n\n```js\nawait webdb.unindexFile('dat://foo.com')\n```\n\n - `url` String. The url of the file to deindex.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nRemove a single file from the dataset.\nThe method will return when the file has been de-indexed.\n\n### webdb.listSources()\n\n```js\nvar urls = await webdb.listSources()\n```\n\n - Returns Array\u0026lt;String\u0026gt;.\n\nLists the URLs of the dat:// sites which are included in the dataset.\n\n### webdb.isSource(url)\n\n```js\nvar urls = await webdb.isSource('dat://foo.com')\n```\n\n - Returns Boolean.\n\nIs the given dat:// URL included in the dataset?\n\n### Event: 'open'\n\n```js\nwebdb.on('open', () =\u003e {\n  console.log('WebDB is ready for use')\n})\n```\n\nEmitted when the WebDB instance has been opened using [`open()`](#webdbopen).\n\n### Event: 'open-failed'\n\n```js\nwebdb.on('open-failed', (err) =\u003e {\n  console.log('WebDB failed to open', err)\n})\n```\n\n - `error` Error.\n\nEmitted when the WebDB instance fails to open during [`open()`](#webdbopen).\n\n### Event: 'indexes-reset'\n\n```js\nwebdb.on('indexes-reset', () =\u003e {\n  console.log('WebDB detected a change in schemas and reset all indexes')\n})\n```\n\nEmitted when the WebDB instance detects a change in the schemas and has to reindex the dataset.\nAll indexes are cleared and will be reindexed as sources are added.\n\n### Event: 'indexes-updated'\n\n```js\nwebdb.on('indexes-updated', (url, version) =\u003e {\n  console.log('Tables were updated for', url, 'at version', version)\n})\n```\n\n - `url` String. The archive that was updated.\n - `version` Number. The version which was updated to.\n\nEmitted when the WebDB instance has updated the stored data for a archive.\n\n### Event: 'source-indexing'\n\n```js\nwebdb.on('source-indexing', (url, startVersion, targetVersion) =\u003e {\n  console.log('Tables are updating for', url, 'from version', startVersion, 'to', targetVersion)\n})\n```\n\n - `url` String. The archive that was updated.\n - `startVersion` Number. The version which is being indexed from.\n - `targetVersion` Number. The version which is being indexed to.\n\nEmitted when the WebDB instance has started to index the given archive.\n\n### Event: 'source-index-progress'\n\n```js\nwebdb.on('source-index-progress', (url, tick, total) =\u003e {\n  console.log('Update for', url, 'is', Math.round(tick / total * 100), '% complete')\n})\n```\n\n - `url` String. The archive that was updated.\n - `tick` Number. The current update being applied.\n - `total` Number. The total number of updates being applied.\n\nEmitted when an update has been applied during an indexing process.\n\n### Event: 'source-indexed'\n\n```js\nwebdb.on('source-indexed', (url, version) =\u003e {\n  console.log('Tables were updated for', url, 'at version', version)\n})\n```\n\n - `url` String. The archive that was updated.\n - `version` Number. The version which was updated to.\n\nEmitted when the WebDB instance has indexed the given archive.\nThis is similar to `'indexes-updated'`, but it fires every time a source is indexed, whether or not it results in updates to the indexes.\n\n### Event: 'source-missing'\n\n```js\nwebdb.on('source-missing', (url) =\u003e {\n  console.log('WebDB couldnt find', url, '- now searching')\n})\n```\n\nEmitted when a source's data was not locally available or found on the network.\nWhen this occurs, WebDB will continue searching for the data, and emit `'source-found'` on success.\n\n### Event: 'source-found'\n\n```js\nwebdb.on('source-found', (url) =\u003e {\n  console.log('WebDB has found and indexed', url)\n})\n```\n\nEmitted when a source's data was found after originally not being found during indexing.\nThis event will only be emitted after `'source-missing'` is emitted.\n\n### Event: 'source-error'\n\n```js\nwebdb.on('source-error', (url, err) =\u003e {\n  console.log('WebDB failed to index', url, err)\n})\n```\n\nEmitted when a source fails to load.\n\n## Instance: WebDBTable\n\n### table.count()\n\n```js\nvar numRecords = await webdb.mytable.count()\n```\n\n - Returns Promise\u0026lt;Number\u0026gt;.\n\nCount the number of records in the table.\n\n### table.delete(url)\n\n```js\nawait webdb.mytable.delete('dat://foo.com/bar.json')\n```\n\n - Returns Promise\u0026lt;Number\u0026gt;. The number of deleted records (should be 0 or 1).\n\nDelete the record at the given URL.\n\n### table.each(fn)\n\n```js\nawait webdb.mytable.each(record =\u003e {\n  console.log(record)\n})\n```\n\n - `fn` Function.\n   - `record` Object.\n   - Returns Void.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nIterate over all records in the table with the given function.\n\n### table.filter(fn)\n\n```js\nvar records = await webdb.mytable.filter(record =\u003e {\n  return (record.foo == 'bar')\n})\n```\n\n - `fn` Function.\n   - `record` Object.\n   - Returns Boolean.\n - Returns WebDBQuery.\n\nStart a new query and apply the given filter function to the resultset.\n\n### table.get(url)\n\n```js\nvar record = await webdb.mytable.get('dat://foo.com/myrecord.json')\n```\n\n - `url` String. The URL of the record to fetch.\n - Returns Promise\u0026lt;Object\u0026gt;.\n\nGet the record at the given URL.\n\n### table.get(key, value)\n\n```js\nvar record = await webdb.mytable.get('foo', 'bar')\n```\n\n - `key` String. The keyname to search against.\n - `value` Any. The value to match against.\n - Promise\u0026lt;Object\u0026gt;.\n \nGet the record first record to match the given key/value query.\n\n### table.isRecordFile(url)\n\n```js\nvar isRecord = webdb.mytable.isRecordFile('dat://foo.com/myrecord.json')\n```\n\n - `url` String.\n - Returns Boolean.\n\nTells you whether the given URL matches the table's file pattern.\n\n### table.limit(n)\n\n```js\nvar query = webdb.mytable.limit(10)\n```\n\n - `n` Number.\n - Returns WebDBQuery.\n\nCreates a new query with the given limit applied.\n\n### table.listRecordFiles(url)\n\n```js\nvar recordFiles = await webdb.mytable.listRecordFiles('dat://foo.com')\n```\n\n - `url` String.\n - Returns Promise\u0026lt;Array\u0026lt;Object\u0026gt;\u0026gt;. On each object:\n   - `recordUrl` String.\n   - `table` WebDBTable.\n\nLists all files on the given URL which match the table's file pattern.\n\n### table.name\n\n - String.\n\nThe name of the table.\n\n### table.offset(n)\n\n```js\nvar query = webdb.mytable.offset(5)\n```\n\n - `n` Number.\n - Returns WebDBQuery.\n\nCreates a new query with the given offset applied.\n\n### table.orderBy(key)\n\n```js\nvar query = webdb.mytable.orderBy('foo')\n```\n\n - `key` String.\n - Returns WebDBQuery.\n\nCreates a new query ordered by the given key.\n\n### table.put(url, record)\n\n```js\nawait webdb.mytable.put('dat://foo.com/myrecord.json', {foo: 'bar'})\n```\n\n - `url` String.\n - `record` Object.\n - Returns Promise\u0026lt;String\u0026gt;. The URL of the written record.\n\nReplaces or creates the record at the given URL with the `record`.\n\n### table.query()\n\n```js\nvar query = webdb.mytable.query()\n```\n\n - Returns WebDBQuery.\n\nCreates a new query.\n\n### table.reverse()\n\n```js\nvar query = webdb.mytable.reverse()\n```\n\n - Returns WebDBQuery.\n\nCreates a new query with reverse-order applied.\n\n### table.schema\n\n - Object.\n\nThe schema definition for the table.\n\n### table.toArray()\n\n```js\nvar records = await webdb.mytable.toArray()\n```\n\n - Returns Promise\u0026lt;Array\u0026lt;Object\u0026gt;\u0026gt;.\n\nReturns an array of all records in the table.\n\n### table.update(url, updates)\n\n```js\nvar wasUpdated = await webdb.mytable.update('dat://foo.com/myrecord.json', {foo: 'bar'})\n```\n\n - `url` String. The record to update.\n - `updates` Object. The new values to set on the record.\n - Returns Promise\u0026lt;Number\u0026gt;. The number of records updated.\n\nUpdates the target record with the given key values, if it exists.\n\n### table.update(url, fn)\n\n```js\nvar wasUpdated = await webdb.mytable.update('dat://foo.com/myrecord.json', record =\u003e {\n  record.foo = 'bar'\n  return record\n})\n```\n\n - `url` String. The record to update.\n - `fn` Function. A method to modify the record.\n   - `record` Object. The record to modify.\n   - Returns Object.\n - Returns Promise\u0026lt;Number\u0026gt;. The number of records updated.\n\nUpdates the target record with the given function, if it exists.\n\n### table.upsert(url, updates)\n\n```js\nvar didCreateNew = await webdb.mytable.upsert('dat://foo.com/myrecord.json', {foo: 'bar'})\n```\n\n - `url` String. The record to update.\n - `updates` Object. The new values to set on the record.\n - Returns Promise\u0026lt;Number\u0026gt;. The number of records updated.\n\nIf a record exists at the target URL, will update it with the given key values.\nIf a record does not exist, will create the record.\n\n### table.upsert(url, fn)\n\n```js\nvar didCreateNew = await webdb.mytable.upsert('dat://foo.com/myrecord.json', record =\u003e {\n  if (record) {\n    // update\n    record.foo = 'bar'\n    return record\n  }\n  // create\n  return {foo: 'bar'}\n})\n```\n\n - `url` String. The record to update.\n - `fn` Function. A method to modify the record.\n   - `record` Object. The record to modify. Will be falsy if the record does ot previously exist\n   - Returns Object.\n - Returns Promise\u0026lt;Number\u0026gt;. The number of records updated.\n\nUpdates the target record with the given function, if it exists.\nIf a record does not exist, will give a falsy value to the method.\n\n### table.where(key)\n\n```js\nvar whereClause = webdb.mytable.where('foo')\n```\n\n - `key` String.\n - Returns WebDBWhereClause.\n\nCreates a new where-clause using the given key.\n\n### Event: 'put-record'\n\n```js\nwebdb.mytable.on('put-record', ({url, origin, indexedAt, record}) =\u003e {\n  console.log('Table was updated for', url, '(origin:', origin, ') at ', indexedAt)\n  console.log('Record data:', record)\n})\n```\n\n - `url` String. The url of the record that was updated.\n - `origin` String. The url origin of the record that was updated.\n - `indexedAt` Number. The timestamp of the index update.\n - `record` Object. The content of the updated record.\n\nEmitted when the table has updated the stored data for a record.\n\n### Event: 'del-record'\n\n```js\nwebdb.mytable.on('del-record', ({url, origin, indexedAt}) =\u003e {\n  console.log('Table was updated for', url, '(origin:', origin, ') at ', indexedAt)\n})\n```\n\n - `url` String. The url of the record that was deleted.\n - `origin` String. The url origin of the record that was deleted.\n - `indexedAt` Number. The timestamp of the index update.\n\nEmitted when the table has deleted the stored data for a record.\nThis can happen because the record has been deleted, or because a new version of the record fails validation.\n\n## Instance: WebDBQuery\n\n### query.clone()\n\n```js\nvar query = webdb.mytable.query().clone()\n```\n\n - Returns WebDBQuery.\n\nCreates a copy of the query.\n\n### query.count()\n\n```js\nvar numRecords = await webdb.mytable.query().count()\n```\n\n - Returns Promise\u0026lt;Number\u0026gt;. The number of found records.\n\nGives the count of records which match the query.\n\n### query.delete()\n\n```js\nvar numDeleted = await webdb.mytable.query().delete()\n```\n\n - Returns Promise\u0026lt;Number\u0026gt;. The number of deleted records.\n\nDeletes all records which match the query.\n\n### query.each(fn)\n\n```js\nawait webdb.mytable.query().each(record =\u003e {\n  console.log(record)\n})\n```\n\n - `fn` Function.\n   - `record` Object.\n   - Returns Void.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nCalls the given function with all records which match the query.\n\n### query.eachKey(fn)\n\n```js\nawait webdb.mytable.query().eachKey(url =\u003e {\n  console.log('URL =', url)\n})\n```\n\n - `fn` Function.\n   - `key` String.\n   - Returns Void.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nCalls the given function with the value of the query's primary key for each matching record.\n\nThe `key` is determined by the index being used.\nBy default, this is the `url` attribute, but it can be changed by using `where()` or `orderBy()`.\n\nExample:\n\n```js\nawait webdb.mytable.orderBy('age').eachKey(age =\u003e {\n  console.log('Age =', age)\n})\n```\n\n### query.eachUrl(fn)\n\n```js\nawait webdb.mytable.query().eachUrl(url =\u003e {\n  console.log('URL =', url)\n})\n```\n\n - `fn` Function.\n   - `url` String.\n   - Returns Void.\n - Returns Promise\u0026lt;Void\u0026gt;.\n\nCalls the given function with the URL of each matching record.\n\n### query.filter(fn)\n\n```js\nvar query = webdb.mytable.query().filter(record =\u003e {\n  return record.foo == 'bar'\n})\n```\n\n - `fn` Function.\n   - `record` Object.\n   - Returns Boolean.\n - Returns WebDBQuery.\n\nApplies an additional filter on the query.\n\n### query.first()\n\n```js\nvar record = await webdb.mytable.query().first()\n```\n\n - Returns Promise\u0026lt;Object\u0026gt;.\n\nReturns the first result in the query.\n\n### query.keys()\n\n```js\nvar keys = await webdb.mytable.query().keys()\n```\n\n - Returns Promise\u0026lt;Array\u0026lt;String\u0026gt;\u0026gt;.\n\nReturns the value of the primary key for each matching record.\n\nThe `key` is determined by the index being used.\nBy default, this is the `url` attribute, but it can be changed by using `where()` or `orderBy()`.\n\n```js\nvar ages = await webdb.mytable.orderBy('age').keys()\n```\n\n### query.last()\n\n```js\nvar record = await webdb.mytable.query().last()\n```\n\n - Returns Promise\u0026lt;Object\u0026gt;.\n\nReturns the last result in the query.\n\n### query.limit(n)\n\n```js\nvar query = webdb.mytable.query().limit(10)\n```\n\n - `n` Number.\n - Returns WebDBQuery.\n\nLimits the number of matching record to the given number.\n\n### query.offset(n)\n\n```js\nvar query = webdb.mytable.query().offset(10)\n```\n\n - `n` Number.\n - Returns WebDBQuery.\n\nSkips the given number of matching records.\n\n### query.orderBy(key)\n\n```js\nvar query = webdb.mytable.query().orderBy('foo')\n```\n\n - `key` String.\n - Returns WebDBQuery.\n\nSets the primary key and sets the resulting order to match its values.\n\n### query.put(record)\n\n```js\nvar numWritten = await webdb.mytable.query().put({foo: 'bar'})\n```\n\n - `record` Object.\n - Returns Promise\u0026lt;Number\u0026gt;. The number of written records.\n\nReplaces each matching record with the given value.\n\n### query.urls()\n\n```js\nvar urls = await webdb.mytable.query().urls()\n```\n\n - Returns Promise\u0026lt;Array\u0026lt;String\u0026gt;\u0026gt;.\n\nReturns the url of each matching record.\n\n### query.reverse()\n\n```js\nvar query = webdb.mytable.query().reverse()\n```\n\n - Returns WebDBQuery.\n\nReverses the order of the results.\n\n### query.toArray()\n\n```js\nvar records = await webdb.mytable.query().toArray()\n```\n\n - Returns Promise\u0026lt;Array\u0026lt;Object\u0026gt;\u0026gt;.\n\nReturns the value of each matching record.\n\n### query.uniqueKeys()\n\n```js\nvar keys = await webdb.mytable.query().uniqueKeys()\n```\n\n - Returns Promise\u0026lt;Array\u0026lt;String\u0026gt;\u0026gt;.\n\nReturns the value of the primary key for each matching record, with duplicates filtered out.\n\nThe `key` is determined by the index being used.\nBy default, this is the `url` attribute, but it can be changed by using `where()` or `orderBy()`.\n\nExample: \n\n```js\nvar ages = await webdb.mytable.orderBy('age').uniqueKeys()\n```\n\n### query.until(fn)\n\n```js\nvar query = webdb.mytable.query().until(record =\u003e {\n  return record.foo == 'bar'\n})\n```\n\n - `fn` Function.\n   - `record` Object.\n   - Returns Boolean.\n - Returns WebDBQuery.\n\nStops emitting matching records when the given function returns true.\n\n### query.update(updates)\n\n```js\nvar numUpdated = await webdb.mytable.query().update({foo: 'bar'})\n```\n\n - `updates` Object. The new values to set on the record.\n - Returns Promise\u0026lt;Number\u0026gt;. The number of updated records.\n\nUpdates all matching record with the given values.\n\n### query.update(fn)\n\n```js\nvar numUpdated = await webdb.mytable.query().update(record =\u003e {\n  record.foo = 'bar'\n  return record\n})\n```\n\n - `fn` Function. A method to modify the record.\n   - `record` Object. The record to modify.\n   - Returns Object.\n - Returns Promise\u0026lt;Number\u0026gt;. The number of updated records.\n\nUpdates all matching record with the given function.\n\n### query.where(key)\n\n```js\nvar whereClause = webdb.mytable.query().where('foo')\n```\n\n - `key` String. The attribute to query against.\n - Returns WebDBWhereClause.\n\nCreates a new where clause.\n\n## Instance: WebDBWhereClause\n\n### where.above(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').above('bar')\nvar query = webdb.mytable.query().where('age').above(18)\n```\n\n - `value` Any. The lower bound of the query.\n - Returns WebDBQuery.\n\n### where.aboveOrEqual(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').aboveOrEqual('bar')\nvar query = webdb.mytable.query().where('age').aboveOrEqual(18)\n```\n\n - `value` Any. The lower bound of the query.\n - Returns WebDBQuery.\n\n### where.anyOf(values)\n\n```js\nvar query = webdb.mytable.query().where('foo').anyOf(['bar', 'baz'])\n```\n\n - `values` Array\u0026lt;Any\u0026gt;.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.anyOfIgnoreCase(values)\n\n```js\nvar query = webdb.mytable.query().where('foo').anyOfIgnoreCase(['bar', 'baz'])\n```\n\n - `values` Array\u0026lt;Any\u0026gt;.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.below(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').below('bar')\nvar query = webdb.mytable.query().where('age').below(18)\n```\n\n - `value` Any. The upper bound of the query.\n - Returns WebDBQuery.\n\n### where.belowOrEqual(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').belowOrEqual('bar')\nvar query = webdb.mytable.query().where('age').belowOrEqual(18)\n```\n\n - `value` Any. The upper bound of the query.\n - Returns WebDBQuery.\n\n### where.between(lowerValue, upperValue[, options])\n\n```js\nvar query = webdb.mytable.query().where('foo').between('bar', 'baz', {includeUpper: true, includeLower: true})\nvar query = webdb.mytable.query().where('age').between(18, 55, {includeLower: true})\n```\n\n - `lowerValue` Any.\n - `upperValue` Any.\n - `options` Object.\n   - `includeUpper` Boolean.\n   - `includeLower` Boolean.\n - Returns WebDBQuery.\n\n### where.equals(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').equals('bar')\n```\n\n - `value` Any.\n - Returns WebDBQuery.\n\n### where.equalsIgnoreCase(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').equalsIgnoreCase('bar')\n```\n\n - `value` Any.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.noneOf(values)\n\n```js\nvar query = webdb.mytable.query().where('foo').noneOf(['bar', 'baz'])\n```\n\n - `values` Array\u0026lt;Any\u0026gt;.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.notEqual(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').notEqual('bar')\n```\n\n - `value` Any.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.startsWith(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').startsWith('ba')\n```\n\n - `value` Any.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.startsWithAnyOf(values)\n\n```js\nvar query = webdb.mytable.query().where('foo').startsWithAnyOf(['ba', 'bu'])\n```\n\n - `values` Array\u0026lt;Any\u0026gt;.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.startsWithAnyOfIgnoreCase(values)\n\n```js\nvar query = webdb.mytable.query().where('foo').startsWithAnyOfIgnoreCase(['ba', 'bu'])\n```\n\n - `values` Array\u0026lt;Any\u0026gt;.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n### where.startsWithIgnoreCase(value)\n\n```js\nvar query = webdb.mytable.query().where('foo').startsWithIgnoreCase('ba')\n```\n\n - `value` Any.\n - Returns WebDBQuery.\n\nDoes not work on compound indexes.\n\n\n## How it works\n\nWebDB abstracts over the [DatArchive API](https://beakerbrowser.com/docs/apis/dat.html) to provide a simple database-like interface. It's inspired by [Dexie.js](https://github.com/dfahlander/Dexie.js) and built using [LevelDB](https://github.com/Level/level). (In the browser, it runs on IndexedDB using [level.js](https://github.com/maxogden/level.js).\n\nWebDB scans a set of source Dat archives for files that match a path pattern. Web DB caches and indexes those files so they can be queried easily and quickly. WebDB also provides a simple interface for adding, editing, and removing records from archives.\n\nWebDB sits on top of Dat archives. It duplicates ingested data into IndexedDB, which acts as a throwaway cache. The cached data can be reconstructed at any time from the source Dat archives.\n\nWebDB treats individual files in the Dat archive as individual records in a table. As a result, there's a direct mapping for each table to a folder of JSON files. For instance, if you had a `posts` table, it might map to the `/posts/*.json` files. WebDB's mutators, e.g., `put`, `add`, `update`, simply writes records as JSON files in the `posts/` directory. WebDB's readers and query-ers, like `get()` and `where()`, read from the IndexedDB cache.\n\nWebDB watches its source archives for changes to the JSON files that compose its records. When the files change, it syncs and reads the changes, then updates IndexedDB, keeping query results up-to-date. Roughly, the flow is: `put() -\u003e archive/posts/12345.json -\u003e indexer -\u003e indexeddb -\u003e get()`.\n\n### Why not put all records in one file?\n\nStoring records in one file—`posts.json` for example—is an intuitive way to manage data on the peer-to-peer Web, but putting each record in an individual file is a much better choice for performance and linkability.\n\n#### Performance\n\nThe `dat://` protocol doesn't support partial updates at the file-level, which means that with multiple records in a single file, every time a user adds a record, anyone who follows that user must sync and re-download the *entire* file. As the file continues to grow, performance will degrade. Putting each record in an individual file is much more efficient: when a record is created, peers in the network will only download the newly-created file.\n\n#### Linkability\n\nPutting each record in an individual file also makes each record linkable! This isn't as important as performance, but it's a nice feature to have. See Dog Legs McBoot's status update as an example:\n\n```\ndat://232ac2ce8ad4ed80bd1b6de4cbea7d7b0cad1441fa62312c57a6088394717e41/posts/0jbdviucy.json\n```\n\n## Changelog\n\nA quick overview of the notable changes to WebDB:\n\n### 4.1.0\n\nAdded \"helper tables,\" which make it possible to track private state and build more sophisticated indexes.\n\n### 4.0.0\n\nReplaced JSON-Schema validation with an open `validate` function. This was done to reduce the output bundle size (by 200kb!) and to improve overall flexibility (JSON-Schema and JSON-LD do not work together very well).\n\n### 3.0.0\n\nThe `addSource()` and `removeSource()` methods were replaced with `indexArchive()`, `indexFile()`, `unindexArchive()`, and `unindexFile()`.\nThe `indexArchive()` method also provides an option to disable watching.\n\nThis change was made as we found controlling the index was an important part of using WebDB.\nFrequently we'd want to index an archive temporarily, for instance to view a user's profile on first visit.\n\nThis new API gives better control for those use-cases, and no longer assumes you want to continue watching an archive after indexing it once.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeakerbrowser%2Fwebdb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeakerbrowser%2Fwebdb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeakerbrowser%2Fwebdb/lists"}