UNPKG

@x5e/gink

Version:

an eventually consistent database

140 lines (132 loc) 4.85 kB
import { LogBackedStore } from "./LogBackedStore"; import { Store } from "./Store"; import { Database } from "./Database"; import { AuthFunction } from "./typedefs"; import { SimpleServer } from "./SimpleServer"; import { ensure, generateTimestamp, getIdentity, logToStdErr, noOp, isAlive, } from "./utils"; import { IndexedDbStore } from "./IndexedDbStore"; import { start, REPLServer } from "node:repl"; import { Directory } from "./Directory"; import { Box } from "./Box"; import { Sequence } from "./Sequence"; import { KeySet } from "./KeySet"; import { Accumulator } from "./Accumulator"; /** Intended to manage server side running of Gink. Basically it takes some settings in the form of arguments plus a list of peers to connect to then starts up the Gink Instance, or Gink Server if port listening is specified. */ export class CommandLineInterface { targets: string[]; store?: Store; instance?: Database; replServer?: REPLServer; authToken?: string; reconnectOnClose: boolean; logger: (_: string) => void; constructor(args: { connect_to?: string[]; listen_on?: string; data_file?: string; identity?: string; reconnect?: boolean; static_path?: string; auth_token?: string; ssl_cert?: string; ssl_key?: string; verbose?: boolean; exclusive?: boolean; }) { // This makes debugging through integration tests way easier. globalThis.ensure = ensure; this.logger = args.verbose ? logToStdErr : noOp; this.authToken = args.auth_token; this.reconnectOnClose = args.reconnect; this.targets = args.connect_to ?? []; const identity = args.identity ?? getIdentity(); ensure(identity); let authFunc: AuthFunction | null = null; if (this.authToken) { authFunc = (token: string) => { // Expecting token to have already been decoded from hex. if (!token) return false; // Purposely using includes here since the token will have been // decoded and may contain '\x00' as a prefix. ensure(token.includes("token ")); let key = this.authToken.toLowerCase(); token = token.toLowerCase().split("token ")[1].trimStart(); return token === key; }; } if (args.data_file) { const is_exclusive = args.exclusive ? "exclusive" : "not exclusive"; this.logger(`using data file=${args.data_file} (${is_exclusive})`); this.store = new LogBackedStore(args.data_file, args.exclusive); } else { this.logger(`using in-memory database`); this.store = new IndexedDbStore(generateTimestamp().toString()); } if (args.listen_on) { const common = { port: args.listen_on, sslKeyFilePath: args.ssl_key, sslCertFilePath: args.ssl_cert, staticContentRoot: args.static_path, logger: this.logger, authFunc: authFunc, }; this.instance = new SimpleServer({ store: this.store, identity: identity, ...common, }); } else { // port not set so don't listen for incoming connections this.instance = new Database({ store: this.store, identity, logger: this.logger, }); } } async run() { if (this.instance) { await this.instance.ready; globalThis.isAlive = isAlive; globalThis.database = this.instance; globalThis.root = Directory.get(this.instance); globalThis.Accumulator = Accumulator; globalThis.Sequence = Sequence; globalThis.Box = Box; globalThis.KeySet = KeySet; globalThis.Directory = Directory; for (const target of this.targets) { this.logger(`connecting to: ${target}`); await this.instance.connectTo(target, { reconnectOnClose: this.reconnectOnClose, authToken: this.authToken, onError: (e) => { this.logger( `Failed connection to ${target}. Bad Auth token?\n` + e, ); }, }).ready; this.logger(`connected!`); } } this.replServer = start({ prompt: "node+gink> ", useGlobal: true }); this.replServer.on("exit", () => { process.exit(0); }); } }