UNPKG

@browser-network/database

Version:

A type of distributed database built on top of the distributed browser-network

141 lines (140 loc) 5.37 kB
import { LocalDB, WrappedState } from './LocalDB'; import type Network from '@browser-network/network'; import * as t from './types.d'; type StateUpdateMessage = { type: 'state-update'; data: WrappedState; appId: string; }; type StateRequestMessage = { type: 'state-request'; data: t.IDString; appId: string; destination: string; }; type StateOfferingMessage = { type: 'state-offering'; data: StateOfferings; appId: string; }; type DbMessage = StateUpdateMessage | StateOfferingMessage | StateRequestMessage; type StateOfferings = { [stateId: t.GUID]: t.TimeStamp; }; type DbProps = { /** * @description The @browser-network/network that this Db app sits on top of. Without this there is no Db! */ network: Network; /** * @description The EC private key that Db will use to both generate a public key and validate Db entries with. * See @browser-network/crypto's generateSecret() function. */ secret: string; /** * @description This is the namespace under which Db will make its messages. It just needs to be unique * on the network, that's why it's up to the developer to provide this. Every instance of this db that you * want to share data with needs to have the same appId. You could, if you wanted to, have multiple databases * that each had their own set of data, unshared with each other, by providing different appIds here. */ appId: string; }; export default class Db<S> { appId: string; network: Network<DbMessage>; localDB: LocalDB; networkId: t.IDString; address: t.IDString; publicKey: t.PublicKey; secret: string; switchAddress: t.SwitchAddress; private _denyList; private _allowList; private _onChangeHandlers; constructor({ secret, appId, network }: DbProps); /** * @description This is how you write data to the network. This will put whatever * state you give it into a DB specific wrapper with your state in the `state` key. */ set(state: S): Promise<void>; /** * @description Get the state of the user whose address is passed in. */ get(address: t.IDString): WrappedState<S> | undefined; /** * @description Get all entries from our local DB, wrapped in the DB's * WrappedState type. * * @TODO: Does it need to be wrapped? Does the user ever care about this * wrapping or should they just be able to go straight to their state? */ getAll: () => WrappedState<S>[]; /** * @description This will fire every time we update our state. This way reactive * UIs can listen for changes and update based on the new state of the world */ onChange(handler: () => void): void; /** * @description clear the DB of all listeners. */ removeChangeHandlers(): void; /** * @description clear the DB of a specific listener. */ removeChangeHandler(func: Function): void; /** * @description Clear the local storage of everyone's items. Essentially resets the machine * to as if it's never seen the network before. If it is connected still, it will * rapidly start to repopulate. */ clear: () => void; /** * @description Effectively blocks a user. Adds them to our deny list, which means we'll no longer * accept updates from them, which means we will no longer forward their updates as well. Also * removes their state from our storage. * * It's up to the developer to keep track of these (probably * within the state object that they store in this db), and repopulate this list on startup. * Calling deny with an address that's already blocked is a noop and O(1) time so don't worry about * spamming this call. */ deny: (address: t.PublicKey) => void; /** * @description Unblock a user. Removes them from our deny list, at which point the DB will naturally * start to repopulate that user's state. */ undeny: (address: t.PublicKey) => void; /** * @description Add a user to our allow list. Once a single user is on this list, _only users on the * allow list will be recorded in the database_. All other users will automatically be ignored. * * It's up to the developer to keep track of these (probably * within the state object that they store in this db), and repopulate this list on startup. * Calling allow with an address that's already on the list is a noop and O(1) time so don't worry about * spamming this call. */ allow: (address: t.PublicKey) => void; /** * @description Remove a user from the allow list. Calling this will remove the user's state from * our storage, and that user's state will no longer be forwarded either. */ unallow: (address: t.PublicKey) => void; private onMessage; private onStateOffering; private onStateUpdate; private isForbidden; private broadcastStateUpdate; private broadcastStateUpdateByStateId; /** * We will periodically inform the network of what states we have * and how old they are. If someone else hears that we have a state * newer than what they have on record, they can send us a request for * what we have. */ private broadcastStateOfferings; private setLocal; private runChangeHandlers; private verify; private isNew; } export {};