@browser-network/database
Version:
A type of distributed database built on top of the distributed browser-network
141 lines (140 loc) • 5.37 kB
TypeScript
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 {};