diff --git a/lib/core-manager/core-index.js b/lib/core-manager/core-index.js new file mode 100644 index 00000000..bfcdd010 --- /dev/null +++ b/lib/core-manager/core-index.js @@ -0,0 +1,121 @@ +import crypto from 'hypercore-crypto' +import { TypedEmitter } from 'tiny-typed-emitter' + +/** @typedef {import('./index.js').Namespace} Namespace */ +/** @typedef {{ core: import('hypercore').default, key: Buffer, namespace: Namespace }} CoreRecord */ +/** + * @typedef {Object} CoreIndexEvents + * @property {(coreRecord: CoreRecord) => void} add-core + */ + +// WARNING: If changed once in production then we need a migration strategy +const TABLE = 'cores' +const CREATE_SQL = `CREATE TABLE IF NOT EXISTS ${TABLE} ( + publicKey BLOB NOT NULL, + namespace TEXT NOT NULL +)` + +/** + * An in-memory index of open cores that persists core keys & namespaces to disk + * + * @extends {TypedEmitter} + */ +export class CoreIndex extends TypedEmitter { + /** @type {Map} */ + #coresByDiscoveryId = new Map() + /** @type {Map} */ + #writersByNamespace = new Map() + #addCoreSqlStmt + + /** + * @param {object} options + * @param {import('better-sqlite3').Database} options.db better-sqlite3 database instance + */ + constructor ({ db }) { + super() + // Make sure table exists for persisting known cores + db.prepare(CREATE_SQL).run() + // Pre-prepare SQL statement for better performance + this.#addCoreSqlStmt = db.prepare( + `INSERT OR IGNORE INTO ${TABLE} VALUES (@publicKey, @namespace)` + ) + } + + [Symbol.iterator]() { + return this.#coresByDiscoveryId.values() + } + + /** + * NB. Need to pass key here because `core.key` is not populated until the + * core is ready, but we know it beforehand. + * + * @param {Object} options + * @param {import('hypercore').default} options.core Hypercore instance + * @param {Buffer} options.key Buffer containing public key of this core + * @param {Namespace} options.namespace + * @param {boolean} [options.writer] Is this a writer core? + * @param {boolean} [options.persist] Persist to cb? + */ + add ({ core, key, namespace, writer = false, persist = false }) { + const discoveryKey = crypto.discoveryKey(key) + const discoveryId = discoveryKey.toString('hex') + const record = { core, key, namespace } + if (writer) { + this.#writersByNamespace.set(namespace, record) + } + this.#coresByDiscoveryId.set(discoveryId, record) + if (persist) { + this.#addCoreSqlStmt.run({ publicKey: key, namespace }) + } + this.emit('add-core', { core, key, namespace }) + } + + /** + * Get all known cores in a namespace + * + * @param {Namespace} namespace + * @returns {CoreRecord[]} + */ + getByNamespace (namespace) { + const records = [] + for (const record of this.#coresByDiscoveryId.values()) { + if (record.namespace === namespace) records.push(record) + } + return records + } + + /** + * Get the write core for the given namespace + * + * @param {Namespace} namespace + * @returns {CoreRecord} + */ + getWriter (namespace) { + const writerRecord = this.#writersByNamespace.get(namespace) + // Shouldn't happen, since we add all the writers in the contructor + if (!writerRecord) + throw new Error(`Writer for namespace '${namespace}' is not defined`) + return writerRecord + } + + /** + * Get a core by its discoveryId (discover key as hex string) + * + * @param {string} discoveryId + * @returns {CoreRecord | undefined} + */ + getByDiscoveryId (discoveryId) { + return this.#coresByDiscoveryId.get(discoveryId) + } + + /** + * Get a core by its public key + * + * @param {Buffer} coreKey + * @returns {CoreRecord | undefined} + */ + getByCoreKey (coreKey) { + const discoveryId = crypto.discoveryKey(coreKey).toString('hex') + return this.#coresByDiscoveryId.get(discoveryId) + } +} diff --git a/lib/core-manager/index.js b/lib/core-manager/index.js new file mode 100644 index 00000000..b9bbfb41 --- /dev/null +++ b/lib/core-manager/index.js @@ -0,0 +1,331 @@ +// @ts-check +import { TypedEmitter } from 'tiny-typed-emitter' +import Corestore from 'corestore' +import assert from 'assert' +import Hypercore from 'hypercore' +import { ProjectExtension } from './messages.js' +import { CoreIndex } from './core-index.js' +import { ReplicationStateMachine } from './replication-state-machine.js' + +// WARNING: Changing these will break things for existing apps, since namespaces +// are used for key derivation +const NAMESPACES = /** @type {const} */ (['auth', 'data', 'blobIndex', 'blob']) + +/** @typedef {(typeof NAMESPACES)[number]} Namespace */ +/** @typedef {import('streamx').Duplex} DuplexStream */ +/** @typedef {{ rsm: ReplicationStateMachine, stream: DuplexStream, cores: Set }} ReplicationRecord */ +/** @typedef {DuplexStream & { noiseStream: DuplexStream }} NoiseStream */ +/** @typedef {DuplexStream & { noiseStream: DuplexStream & { userData: any }}} ProtocolStream */ +/** + * @typedef {Object} Events + * @property {import('./core-index.js').CoreIndexEvents['add-core']} add-core + */ + +/** + * @extends {TypedEmitter} + */ +export class CoreManager extends TypedEmitter { + #corestore + #coreIndex + /** @type {Hypercore} */ + #creatorCore + #projectKey + /** @type {Set} */ + #replications = new Set() + #extension + + static get namespaces () { + return NAMESPACES + } + + /** + * @param {Object} options + * @param {import('better-sqlite3').Database} options.db better-sqlite3 database instance + * @param {import('@mapeo/crypto').KeyManager} options.keyManager mapeo/crypto KeyManager instance + * @param {Buffer} options.projectKey 32-byte public key of the project creator core + * @param {Buffer} [options.projectSecretKey] 32-byte secret key of the project creator core + * @param {import('hypercore').HypercoreStorage} options.storage Folder to store all hypercore data + */ + constructor ({ db, keyManager, projectKey, projectSecretKey, storage }) { + super() + assert( + projectKey.length === 32, + 'project owner core public key must be 32-byte buffer' + ) + assert( + !projectSecretKey || projectSecretKey.length === 64, + 'project owner core secret key must be 64-byte buffer' + ) + const primaryKey = keyManager.getDerivedKey('primaryKey', projectKey) + this.#projectKey = projectKey + + // Note: this should not be used, because we do not rely on corestore for + // key storage (i.e. we do not get cores from corestore via a name, which + // would derive the keypair from the primary key), but setting this just in + // case a dependency does (e.g. hyperdrive-next) and we miss it. + this.#corestore = new Corestore(storage, { primaryKey }) + // Persistent index of core keys and namespaces in the project + this.#coreIndex = new CoreIndex({ db }) + this.#coreIndex.on('add-core', core => this.emit('add-core', core)) + + // Writer cores and root core, keys and namespaces are not persisted because + // we derive the keys here. + for (const namespace of NAMESPACES) { + let keyPair + if (namespace === 'auth' && projectSecretKey) { + // For the project creator, the creatorCore is the same as the writer + // core for the 'auth' namespace + keyPair = { publicKey: projectKey, secretKey: projectSecretKey } + } else { + // Deterministic keypair, based on rootKey, namespace & projectKey + keyPair = keyManager.getHypercoreKeypair(namespace, projectKey) + } + const writer = this.#addCore(keyPair, namespace) + if (namespace === 'auth' && projectSecretKey) { + this.#creatorCore = writer.core + } + } + + if (!this.#creatorCore) { + // For anyone other than the project creator, creatorCore is readonly + this.#creatorCore = this.addCore(projectKey, 'auth').core + } + + this.#extension = this.#creatorCore.registerExtension('mapeo/project', { + onmessage: (data, peer) => { + this.#handleExtensionMessage(data, peer) + } + }) + } + + get creatorCore () { + return this.#creatorCore + } + + /** + * Get the writer core for the given namespace + * + * @param {Namespace} namespace + */ + getWriterCore (namespace) { + return this.#coreIndex.getWriter(namespace) + } + + /** + * Get an array of all cores in the given namespace + * + * @param {Namespace} namespace + * @returns + */ + getCores (namespace) { + return this.#coreIndex.getByNamespace(namespace) + } + + /** + * Get a core by its public key + * @private (only used in tests) + * + * @param {Buffer} key + * @returns {Hypercore | undefined} + */ + getCoreByKey (key) { + const coreRecord = this.#coreIndex.getByCoreKey(key) + return coreRecord && coreRecord.core + } + + /** + * Add a core to the manager (will be persisted across restarts) + * + * @param {Buffer} key 32-byte public key of core to add + * @param {Namespace} namespace + * @returns {import('./core-index.js').CoreRecord} + */ + addCore (key, namespace) { + return this.#addCore({ publicKey: key }, namespace) + } + + /** + * Add a core to the manager (writer cores and project creator core not persisted) + * + * @param {{ publicKey: Buffer, secretKey?: Buffer }} keyPair + * @param {Namespace} namespace + * @returns {import('./core-index.js').CoreRecord} + */ + #addCore (keyPair, namespace) { + // No-op if core is already managed + const existingCore = this.#coreIndex.getByCoreKey(keyPair.publicKey) + if (existingCore) return existingCore + + const { publicKey: key, secretKey } = keyPair + const writer = !!secretKey + // Don't persist the writer cores or the creatorCore - they are derived from + // the keyManager and the projectKey + const persist = !writer && !key.equals(this.#projectKey) + // @ts-ignore - TS doesn't understand checking writer ensures secretKey + const core = this.#corestore.get(writer ? { keyPair } : key) + this.#coreIndex.add({ core, key, namespace, writer, persist }) + + // **Hack** As soon as a peer is added, eagerly send a "want" for the entire + // core. This ensures that the peer sends back its entire bitfield. + // Otherwise this would only happen once we call core.download() + core.on('peer-add', peer => { + if (core.length === 0) return + // **Warning** uses internal method, but should be covered by tests + peer._maybeWant(0, core.length) + }) + + // A non-writer core will emit 'append' when its length is updated from the + // initial sync with a peer, and we will not have sent a "maybe want" for + // this range, so we need to do it now. Subsequent appends are propogated + // (more efficiently) via range broadcasts, so we only need to listen to the + // first append. + if (!writer) { + core.once('append', () => { + for (const peer of core.peers) { + // TODO: It would be more efficient (in terms of network traffic) to + // send a want with start = length of previous want. Need to track + // "last want length" sent by peer. + peer._maybeWant(0, core.length) + } + }) + } + + for (const { stream, rsm, cores } of this.#replications.values()) { + if (cores.has(core)) continue + if (rsm.state.enabledNamespaces.has(namespace)) { + core.replicate(stream) + } else { + rsm.on('enable-namespace', function onNamespace (enabledNamespace) { + if (enabledNamespace !== namespace) return + if (!cores.has(core)) { + core.replicate(stream) + } + rsm.off('enable-namespace', onNamespace) + }) + } + } + + return { core, key, namespace } + } + + /** + * Start replicating cores managed by CoreManager to a Noise Secret Stream (as + * created by @hyperswarm/secret-stream). Important: only one CoreManager can + * be replicated to a given stream - attempting to replicate a second + * CoreManager to the same stream will cause sharing of auth core keys to + * fail - see https://github.com/holepunchto/corestore/issues/45 + * + * Initially only cores in the `auth` namespace are replicated to the stream. + * All cores in the `auth` namespace are shared to all peers who have the + * `rootCoreKey` core, and also replicated to the stream + * + * To start replicating other namespaces call `enableNamespace(ns)` on the + * returned state machine + * + * @param {NoiseStream | ProtocolStream} noiseStream framed noise secret stream, i.e. @hyperswarm/secret-stream + */ + replicate (noiseStream) { + if (/** @type {ProtocolStream} */ (noiseStream).noiseStream?.userData) { + console.warn( + 'Passed an existing protocol stream to coreManager.replicate(). Other corestores and core managers replicated to this stream will no longer automatically inject shared cores into the stream' + ) + } + const stream = Hypercore.createProtocolStream(noiseStream) + const protocol = stream.noiseStream.userData + if (!protocol) throw new Error('Invalid stream') + // If the noise stream already had a protomux instance attached to + // noiseStream.userData, then Hypercore.createProtocolStream does not attach + // the ondiscoverykey listener, so we make sure we are listening for this, + // and that we override any previous notifier that was attached to protomux. + // This means that only one Core Manager instance can currently be + // replicated to a stream if we want sharing of unknown auth cores to work. + protocol.pair( + { protocol: 'hypercore/alpha' }, + /** @param {Buffer} discoveryKey */ discoveryKey => { + this.#handleDiscoveryKey(discoveryKey, stream) + } + ) + const rsm = new ReplicationStateMachine() + /** @type {ReplicationRecord['cores']} */ + const replicatingCores = new Set() + + for (const { core } of this.getCores('auth')) { + core.replicate(stream) + replicatingCores.add(core) + } + + /** @type {ReplicationRecord} */ + const replicationRecord = { stream, rsm, cores: replicatingCores } + this.#replications.add(replicationRecord) + + rsm.on('enable-namespace', namespace => { + for (const { core } of this.getCores(namespace)) { + if (!replicatingCores.has(core)) { + core.replicate(stream) + } + } + }) + + stream.once('close', () => { + rsm.disableAll() + rsm.removeAllListeners() + this.#replications.delete(replicationRecord) + }) + + return rsm + } + + /** + * @param {Buffer} discoveryKey + * @param {any} stream + */ + async #handleDiscoveryKey (discoveryKey, stream) { + const discoveryId = discoveryKey.toString('hex') + const peer = await this.#findPeer(stream.remotePublicKey) + if (!peer) { + // TODO: How to handle this and when does it happen? + return + } + // If we already know about this core, then we will add it to the + // replication stream when we are ready + if (this.#coreIndex.getByDiscoveryId(discoveryId)) return + const message = ProjectExtension.encode({ + wantCoreKeys: [discoveryKey], + authCoreKeys: [] + }).finish() + this.#extension.send(message, peer) + } + + /** + * @param {Buffer} publicKey + */ + async #findPeer (publicKey) { + await this.#creatorCore.ready() + return this.#creatorCore.peers.find(peer => + peer.remotePublicKey.equals(publicKey) + ) + } + + /** + * @param {Buffer} data + * @param {any} peer + */ + #handleExtensionMessage (data, peer) { + const { wantCoreKeys, authCoreKeys } = ProjectExtension.decode(data) + for (const discoveryKey of wantCoreKeys) { + const discoveryId = discoveryKey.toString('hex') + const coreRecord = this.#coreIndex.getByDiscoveryId(discoveryId) + if (!coreRecord) continue + if (coreRecord.namespace === 'auth') { + const message = ProjectExtension.encode({ + authCoreKeys: [coreRecord.key], + wantCoreKeys: [] + }).finish() + this.#extension.send(message, peer) + } + } + for (const authCoreKey of authCoreKeys) { + this.addCore(authCoreKey, 'auth') + } + } +} diff --git a/lib/core-manager/messages.d.ts b/lib/core-manager/messages.d.ts new file mode 100644 index 00000000..f51aaf2a --- /dev/null +++ b/lib/core-manager/messages.d.ts @@ -0,0 +1,10 @@ +/// +import _m0 from "protobufjs/minimal.js"; +export interface ProjectExtension { + authCoreKeys: Buffer[]; + wantCoreKeys: Buffer[]; +} +export declare const ProjectExtension: { + encode(message: ProjectExtension, writer?: _m0.Writer): _m0.Writer; + decode(input: _m0.Reader | Uint8Array, length?: number): ProjectExtension; +}; diff --git a/lib/core-manager/messages.js b/lib/core-manager/messages.js new file mode 100644 index 00000000..de6e4cf1 --- /dev/null +++ b/lib/core-manager/messages.js @@ -0,0 +1,39 @@ +/* eslint-disable */ +import _m0 from "protobufjs/minimal.js"; +function createBaseProjectExtension() { + return { authCoreKeys: [], wantCoreKeys: [] }; +} +export var ProjectExtension = { + encode: function (message, writer) { + if (writer === void 0) { writer = _m0.Writer.create(); } + for (var _i = 0, _a = message.authCoreKeys; _i < _a.length; _i++) { + var v = _a[_i]; + writer.uint32(10).bytes(v); + } + for (var _b = 0, _c = message.wantCoreKeys; _b < _c.length; _b++) { + var v = _c[_b]; + writer.uint32(18).bytes(v); + } + return writer; + }, + decode: function (input, length) { + var reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + var end = length === undefined ? reader.len : reader.pos + length; + var message = createBaseProjectExtension(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.authCoreKeys.push(reader.bytes()); + break; + case 2: + message.wantCoreKeys.push(reader.bytes()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + } +}; diff --git a/lib/core-manager/replication-state-machine.js b/lib/core-manager/replication-state-machine.js new file mode 100644 index 00000000..890f4a40 --- /dev/null +++ b/lib/core-manager/replication-state-machine.js @@ -0,0 +1,59 @@ +import { TypedEmitter } from 'tiny-typed-emitter' + +/** @typedef {import('./index.js').Namespace} Namespace */ +/** @typedef {Set} EnabledNamespaces */ +/** @typedef {{ enabledNamespaces: EnabledNamespaces }} ReplicationState */ + +/** + * @typedef {object} StateMachineEvents + * @property {(state: ReplicationState) => void } state + * @property {(namespace: Namespace) => void} enable-namespace Fired whenever a namespace is enabled for replication + */ + +/** + * A simple state machine to manage which namespaces are enabled for replication + * + * @extends {TypedEmitter} + */ +export class ReplicationStateMachine extends TypedEmitter { + /** @type {ReplicationState} */ + #state = { + enabledNamespaces: new Set(['auth']) + } + + get state () { + return this.#state + } + + /** + * Enable a namespace for replication - will add known cores in the namespace + * to the replication stream + * + * @param {Namespace} namespace */ + enableNamespace (namespace) { + if (this.#state.enabledNamespaces.has(namespace)) return + this.#state.enabledNamespaces.add(namespace) + this.emit('enable-namespace', namespace) + this.emit('state', this.#state) + } + + // No obvious way to implement this + // /** @param {Namespace} namespace */ + // disableNamespace (namespace) { + // if (!this.#state.enabledNamespaces.has(namespace)) return + // this.#state.enabledNamespaces.delete(namespace) + // this.emit('disable-namespace', namespace) + // this.emit('state', this.#state) + // } + + /** + * @internal + * Should only be called when the stream is closed, because no obvious way to + * implement this otherwise. + */ + disableAll () { + if (!this.#state.enabledNamespaces.size) return + this.#state.enabledNamespaces.clear() + this.emit('state', this.#state) + } +} diff --git a/package-lock.json b/package-lock.json index 4536d3d0..a875c189 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,18 +9,20 @@ "version": "9.0.0-alpha.0", "license": "MIT", "dependencies": { - "@hyperswarm/secret-stream": "^6.1.0", - "@mapeo/crypto": "github:digidem/mapeo-crypto", + "@hyperswarm/secret-stream": "^6.1.2", + "@mapeo/crypto": "^1.0.0-alpha.4", "@mapeo/sqlite-indexer": "github:digidem/mapeo-sqlite-indexer#no-prepare", "b4a": "^1.6.1", "base32.js": "^0.1.0", "better-sqlite3": "^8.0.1", "corestore": "^6.4.0", "hypercore": "^10.5.3", + "hypercore-crypto": "^3.3.1", "hyperswarm": "^4.3.5", "mapeo-schema": "github:digidem/mapeo-schema#protobufsTypescript", "multi-core-indexer": "github:digidem/multi-core-indexer#no-prepare", "multicast-service-discovery": "^4.0.4", + "protobufjs": "^7.1.2", "sodium-universal": "^3.1.0", "tiny-typed-emitter": "^2.1.0" }, @@ -35,6 +37,8 @@ "eslint-config-prettier": "^8.5.0", "prettier": "^2.8.1", "random-access-memory": "^6.1.0", + "rimraf": "^4.1.2", + "ts-proto": "^1.138.0", "typedoc": "^0.23.23", "typedoc-plugin-markdown": "^3.14.0", "typescript": "^4.9.4" @@ -379,20 +383,45 @@ } }, "node_modules/@hyperswarm/secret-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.1.0.tgz", - "integrity": "sha512-6GjmdrYxZgJYGi8Ji7AZoqmmwQXSR9ByERkMPatcC5FMYgx9n5lDkwgKRK7faPQHpgtwjg2oLCrFveyGRLHV5g==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.1.2.tgz", + "integrity": "sha512-oem+ZEG+wOU1K47qGi51pKyqG1N3F+zz42xmReHeGZVR84y+K+6VQIXCON4EozYad8HEGCixpupt8yH8W4sMxg==", "dependencies": { "b4a": "^1.1.0", - "hypercore-crypto": "^3.3.0", - "noise-curve-ed": "^2.0.0", - "noise-handshake": "^3.0.0", - "sodium-secretstream": "^1.0.0", - "sodium-universal": "^3.0.4", - "streamx": "^2.10.2", + "hypercore-crypto": "^3.3.1", + "noise-curve-ed": "^2.0.1", + "noise-handshake": "^3.0.2", + "sodium-secretstream": "^1.1.0", + "sodium-universal": "^4.0.0", + "streamx": "^2.13.0", "timeout-refresh": "^2.0.0" } }, + "node_modules/@hyperswarm/secret-stream/node_modules/sodium-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.0.tgz", + "integrity": "sha512-U5nZ0sFHtPYJncNBS5SkmBnpSDeVpWuzo//07meNVO4p0K8ybcLuQxDX4ByCeVdT/9OLGa1ayvr1pB5iMBbh0g==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + } + }, + "node_modules/@hyperswarm/secret-stream/node_modules/sodium-universal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.0.tgz", + "integrity": "sha512-iKHl8XnBV96k1c75gwwzANFdephw/MDWSjQAjPmBE+du0y3P23Q8uf7AcdcfFsYAMwLg7WVBfSAIBtV/JvRsjA==", + "dependencies": { + "blake2b": "^2.1.1", + "chacha20-universal": "^1.0.4", + "nanoassert": "^2.0.0", + "sha256-universal": "^1.1.0", + "sha512-universal": "^1.1.0", + "siphash24": "^1.0.1", + "sodium-javascript": "~0.8.0", + "sodium-native": "^4.0.0", + "xsalsa20": "^1.0.0" + } + }, "node_modules/@hyperswarm/testnet": { "version": "3.1.0", "dev": true, @@ -456,9 +485,9 @@ } }, "node_modules/@mapeo/crypto": { - "version": "1.0.0-alpha.2", - "resolved": "git+ssh://git@github.com/digidem/mapeo-crypto.git#4db63f6169fdb47770c9973dcf9127a464db0eb6", - "license": "ISC", + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@mapeo/crypto/-/crypto-1.0.0-alpha.4.tgz", + "integrity": "sha512-dY4FPTpNtGuOkCjNvoqpwaFZ/peKQB4xk6f4RA3IYGx42vLkVqQ/8Q8LqyrdsCW3Oe1XOQEsDbSn61WE1a+d1Q==", "dependencies": { "base-x": "^3.0.9", "base32.js": "^0.1.0", @@ -521,6 +550,60 @@ "node": ">= 8" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@sinclair/typebox": { "version": "0.25.13", "license": "MIT" @@ -542,11 +625,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "dev": true + }, "node_modules/@types/node": { "version": "18.11.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" }, + "node_modules/@types/object-hash": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz", + "integrity": "sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==", + "dev": true + }, "node_modules/@types/parse-json": { "version": "4.0.0", "dev": true, @@ -961,6 +1056,41 @@ "node": ">=10.12.0" } }, + "node_modules/c8/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/call-bind": { "version": "1.0.2", "dev": true, @@ -992,6 +1122,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/case-anything": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", + "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==", + "dev": true, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/chacha20-universal": { "version": "1.0.4", "license": "ISC", @@ -1163,6 +1305,12 @@ "node": ">= 8" } }, + "node_modules/dataloader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", + "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "license": "MIT", @@ -1367,6 +1515,27 @@ "node": ">=6.0.0" } }, + "node_modules/dprint-node": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.7.tgz", + "integrity": "sha512-NTZOW9A7ipb0n7z7nC3wftvsbceircwVHSgzobJsEQa+7RnOMbhrfX5IflA6CtC4GA63DSAiHYXa4JKEy9F7cA==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3" + } + }, + "node_modules/dprint-node/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "dev": true, @@ -1711,6 +1880,41 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flat-tree": { "version": "1.9.0", "license": "MIT" @@ -1968,19 +2172,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hmac-blake2b": { - "version": "2.0.0", - "license": "ISC", - "dependencies": { - "nanoassert": "^1.1.0", - "sodium-native": "^3.1.1", - "sodium-universal": "^3.0.0" - } - }, - "node_modules/hmac-blake2b/node_modules/nanoassert": { - "version": "1.1.0", - "license": "ISC" - }, "node_modules/html-escaper": { "version": "2.0.2", "dev": true, @@ -2011,12 +2202,38 @@ } }, "node_modules/hypercore-crypto": { - "version": "3.3.0", - "license": "MIT", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.3.1.tgz", + "integrity": "sha512-Fo0ZBMDW3P7HFh58AQdxsVop0Xh0Bper4Pyl5Dpc+tvGos6je2ufJpeepaHaI0mNkKq4we1ikd47kTnx2XJd/w==", "dependencies": { "b4a": "^1.1.0", "compact-encoding": "^2.5.1", - "sodium-universal": "^3.0.0" + "sodium-universal": "^4.0.0" + } + }, + "node_modules/hypercore-crypto/node_modules/sodium-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.0.tgz", + "integrity": "sha512-U5nZ0sFHtPYJncNBS5SkmBnpSDeVpWuzo//07meNVO4p0K8ybcLuQxDX4ByCeVdT/9OLGa1ayvr1pB5iMBbh0g==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + } + }, + "node_modules/hypercore-crypto/node_modules/sodium-universal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.0.tgz", + "integrity": "sha512-iKHl8XnBV96k1c75gwwzANFdephw/MDWSjQAjPmBE+du0y3P23Q8uf7AcdcfFsYAMwLg7WVBfSAIBtV/JvRsjA==", + "dependencies": { + "blake2b": "^2.1.1", + "chacha20-universal": "^1.0.4", + "nanoassert": "^2.0.0", + "sha256-universal": "^1.1.0", + "sha512-universal": "^1.1.0", + "siphash24": "^1.0.1", + "sodium-javascript": "~0.8.0", + "sodium-native": "^4.0.0", + "xsalsa20": "^1.0.0" } }, "node_modules/hyperswarm": { @@ -2517,6 +2734,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, "node_modules/loose-envify": { "version": "1.4.0", "dev": true, @@ -2756,22 +2979,73 @@ } }, "node_modules/noise-curve-ed": { - "version": "2.0.0", - "license": "ISC", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.0.1.tgz", + "integrity": "sha512-8HMZ40Wmarg8RQjVemLrjB49JSL6eGeOD+tlzaQW5/p+hNPfHFEMC3UZZ57zUqUprMuz6GN+gsPExpz2DWL+iA==", "dependencies": { "b4a": "^1.1.0", "nanoassert": "^2.0.0", - "sodium-universal": "^3.0.4" + "sodium-universal": "^4.0.0" + } + }, + "node_modules/noise-curve-ed/node_modules/sodium-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.0.tgz", + "integrity": "sha512-U5nZ0sFHtPYJncNBS5SkmBnpSDeVpWuzo//07meNVO4p0K8ybcLuQxDX4ByCeVdT/9OLGa1ayvr1pB5iMBbh0g==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + } + }, + "node_modules/noise-curve-ed/node_modules/sodium-universal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.0.tgz", + "integrity": "sha512-iKHl8XnBV96k1c75gwwzANFdephw/MDWSjQAjPmBE+du0y3P23Q8uf7AcdcfFsYAMwLg7WVBfSAIBtV/JvRsjA==", + "dependencies": { + "blake2b": "^2.1.1", + "chacha20-universal": "^1.0.4", + "nanoassert": "^2.0.0", + "sha256-universal": "^1.1.0", + "sha512-universal": "^1.1.0", + "siphash24": "^1.0.1", + "sodium-javascript": "~0.8.0", + "sodium-native": "^4.0.0", + "xsalsa20": "^1.0.0" } }, "node_modules/noise-handshake": { - "version": "3.0.0", - "license": "ISC", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-3.0.2.tgz", + "integrity": "sha512-4RQ9/6R/GLKA3DPcLDeo954ZBZezHBNpc4YnhyisZ9DPiTRnc81aGdCbH3J9pHllDfj82/f9wKHRRsU7C6pNEg==", "dependencies": { "b4a": "^1.1.0", - "hmac-blake2b": "^2.0.0", "nanoassert": "^2.0.0", - "sodium-universal": "^3.0.4" + "sodium-universal": "^4.0.0" + } + }, + "node_modules/noise-handshake/node_modules/sodium-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.0.tgz", + "integrity": "sha512-U5nZ0sFHtPYJncNBS5SkmBnpSDeVpWuzo//07meNVO4p0K8ybcLuQxDX4ByCeVdT/9OLGa1ayvr1pB5iMBbh0g==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + } + }, + "node_modules/noise-handshake/node_modules/sodium-universal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.0.tgz", + "integrity": "sha512-iKHl8XnBV96k1c75gwwzANFdephw/MDWSjQAjPmBE+du0y3P23Q8uf7AcdcfFsYAMwLg7WVBfSAIBtV/JvRsjA==", + "dependencies": { + "blake2b": "^2.1.1", + "chacha20-universal": "^1.0.4", + "nanoassert": "^2.0.0", + "sha256-universal": "^1.1.0", + "sha512-universal": "^1.1.0", + "siphash24": "^1.0.1", + "sodium-javascript": "~0.8.0", + "sodium-native": "^4.0.0", + "xsalsa20": "^1.0.0" } }, "node_modules/normalize-path": { @@ -2782,6 +3056,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.2", "dev": true, @@ -3038,9 +3321,38 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/protobufjs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + }, "node_modules/protomux": { - "version": "3.4.0", - "license": "MIT", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.4.1.tgz", + "integrity": "sha512-V8MDCiDGqxM4/hGOewmezbCX7HZfcYGtpdO0MK6pEhBLSknENuqqE98OEWyQuwDalfHULVO8ml7LSwTB5g5Z6g==", "dependencies": { "b4a": "^1.3.1", "compact-encoding": "^2.5.1", @@ -3266,33 +3578,15 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz", + "integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==", "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "rimraf": "dist/cjs/src/bin.js" }, "engines": { - "node": "*" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3340,7 +3634,8 @@ }, "node_modules/safety-catch": { "version": "1.0.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.2.tgz", + "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==" }, "node_modules/sass": { "version": "1.56.2", @@ -3546,11 +3841,37 @@ } }, "node_modules/sodium-secretstream": { - "version": "1.0.2", - "license": "MIT", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.1.0.tgz", + "integrity": "sha512-Qg7D2xomELDjDCWAmE4izk1aecG/il8pQIGmSWFaKgah/V58BVWG/PuSZF6vseTpcqnetIFGaOWzmPNzyTD50A==", "dependencies": { "b4a": "^1.1.1", - "sodium-universal": "^3.0.4" + "sodium-universal": "^4.0.0" + } + }, + "node_modules/sodium-secretstream/node_modules/sodium-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.0.tgz", + "integrity": "sha512-U5nZ0sFHtPYJncNBS5SkmBnpSDeVpWuzo//07meNVO4p0K8ybcLuQxDX4ByCeVdT/9OLGa1ayvr1pB5iMBbh0g==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + } + }, + "node_modules/sodium-secretstream/node_modules/sodium-universal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-4.0.0.tgz", + "integrity": "sha512-iKHl8XnBV96k1c75gwwzANFdephw/MDWSjQAjPmBE+du0y3P23Q8uf7AcdcfFsYAMwLg7WVBfSAIBtV/JvRsjA==", + "dependencies": { + "blake2b": "^2.1.1", + "chacha20-universal": "^1.0.4", + "nanoassert": "^2.0.0", + "sha256-universal": "^1.1.0", + "sha512-universal": "^1.1.0", + "siphash24": "^1.0.1", + "sodium-javascript": "~0.8.0", + "sodium-native": "^4.0.0", + "xsalsa20": "^1.0.0" } }, "node_modules/sodium-universal": { @@ -3771,6 +4092,95 @@ "node": ">=8.0" } }, + "node_modules/ts-poet": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.3.0.tgz", + "integrity": "sha512-RjS37SnXMa9En8xvQf//+rvNNNCB7x2TKP/ZfsiQFidtfN3A6FYgPDESe4r7YA3F663XO2ozx+2buQItGOLMxg==", + "dev": true, + "dependencies": { + "dprint-node": "^1.0.7" + } + }, + "node_modules/ts-proto": { + "version": "1.138.0", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.138.0.tgz", + "integrity": "sha512-C12rKQdzV2/7ncusEkcyO6Z3EK+04TfZSVdRwmhwkrNcwcktm3Azg7NKBpDTgvpktGQ4nTTPRSlO5CGTkx1zJg==", + "dev": true, + "dependencies": { + "@types/object-hash": "^1.3.0", + "case-anything": "^2.1.10", + "dataloader": "^1.4.0", + "object-hash": "^1.3.1", + "protobufjs": "^6.11.3", + "ts-poet": "^6.2.0", + "ts-proto-descriptors": "1.7.1" + }, + "bin": { + "protoc-gen-ts_proto": "protoc-gen-ts_proto" + } + }, + "node_modules/ts-proto-descriptors": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.7.1.tgz", + "integrity": "sha512-oIKUh3K4Xts4v29USGLfUG+2mEk32MsqpgZAOUyUlkrcIdv34yE+k2oZ2Nzngm6cV/JgFdOxRCqeyvmWHuYAyw==", + "dev": true, + "dependencies": { + "long": "^4.0.0", + "protobufjs": "^6.8.8" + } + }, + "node_modules/ts-proto-descriptors/node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/ts-proto/node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "license": "Apache-2.0", diff --git a/package.json b/package.json index a0578fac..142cd448 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "test": "brittle tests/*.js", "type": "tsc", "deps": "depcheck --ignore-dirs=docs,types --ignores=@types/*,typescript,typedoc,typedoc-plugin-markdown,@hyperswarm/testnet", - "doc": "typedoc --plugin typedoc-plugin-markdown --out docs/api" + "doc": "typedoc --plugin typedoc-plugin-markdown --out docs/api", + "protobuf": "node ./scripts/build-messages.js" }, "prettier": { "semi": false, @@ -49,23 +50,27 @@ "eslint-config-prettier": "^8.5.0", "prettier": "^2.8.1", "random-access-memory": "^6.1.0", + "rimraf": "^4.1.2", + "ts-proto": "^1.138.0", "typedoc": "^0.23.23", "typedoc-plugin-markdown": "^3.14.0", "typescript": "^4.9.4" }, "dependencies": { - "@hyperswarm/secret-stream": "^6.1.0", - "@mapeo/crypto": "github:digidem/mapeo-crypto", + "@hyperswarm/secret-stream": "^6.1.2", + "@mapeo/crypto": "^1.0.0-alpha.4", "@mapeo/sqlite-indexer": "github:digidem/mapeo-sqlite-indexer#no-prepare", "b4a": "^1.6.1", "base32.js": "^0.1.0", "better-sqlite3": "^8.0.1", "corestore": "^6.4.0", "hypercore": "^10.5.3", + "hypercore-crypto": "^3.3.1", "hyperswarm": "^4.3.5", "mapeo-schema": "github:digidem/mapeo-schema#protobufsTypescript", "multi-core-indexer": "github:digidem/multi-core-indexer#no-prepare", "multicast-service-discovery": "^4.0.4", + "protobufjs": "^7.1.2", "sodium-universal": "^3.1.0", "tiny-typed-emitter": "^2.1.0" } diff --git a/proto/buf.gen.yaml b/proto/buf.gen.yaml new file mode 100644 index 00000000..ddfedab6 --- /dev/null +++ b/proto/buf.gen.yaml @@ -0,0 +1,16 @@ +version: v1 +plugins: + - name: ts + out: ../lib/core-manager + strategy: all + path: ../node_modules/ts-proto/protoc-gen-ts_proto + opt: + - esModuleInterop=true + - snakeToCamel=false + - importSuffix=.js + - initializeFieldsAsUndefined=false + - env=node + - exportCommonSymbols=false + - outputJsonMethods=false + - useOptionals=none + - outputPartialMethods=false diff --git a/proto/buf.yaml b/proto/buf.yaml new file mode 100644 index 00000000..4e76e4f6 --- /dev/null +++ b/proto/buf.yaml @@ -0,0 +1,9 @@ +version: v1 +breaking: + use: + - FILE +lint: + use: + - DEFAULT +build: + excludes: [node_modules] diff --git a/proto/messages.proto b/proto/messages.proto new file mode 100644 index 00000000..e5ff2956 --- /dev/null +++ b/proto/messages.proto @@ -0,0 +1,4 @@ +message ProjectExtension { + repeated bytes authCoreKeys = 1; + repeated bytes wantCoreKeys = 2; +} diff --git a/scripts/build-messages.js b/scripts/build-messages.js new file mode 100755 index 00000000..3824f72c --- /dev/null +++ b/scripts/build-messages.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +import { execSync } from "child_process"; +import rimraf from 'rimraf' + +const protoURL = new URL('../proto', import.meta.url) +const projectRootURL = new URL('..', import.meta.url) +const messagesTSPath = new URL('../lib/core-manager/messages.ts', import.meta.url).pathname + +const command1 = 'buf generate .' +console.log(command1) +execSync(command1, { cwd: protoURL, stdio: 'inherit' }) +const command2 = 'tsc --module es2020 --declaration --allowSyntheticDefaultImports --moduleResolution node ' + messagesTSPath +console.log(command2) +execSync(command2, { cwd: projectRootURL, stdio: 'inherit' }) +console.log('rimraf ' + messagesTSPath) +rimraf.sync(messagesTSPath) diff --git a/tests/core-manager.js b/tests/core-manager.js new file mode 100644 index 00000000..01269ba6 --- /dev/null +++ b/tests/core-manager.js @@ -0,0 +1,319 @@ +import test from 'brittle' +import NoiseSecretStream from '@hyperswarm/secret-stream' +import Hypercore from 'hypercore' +import RAM from 'random-access-memory' +import { createCoreManager, replicate } from './helpers/core-manager.js' +import { randomBytes } from 'crypto' +import Sqlite from 'better-sqlite3' +import { KeyManager } from '@mapeo/crypto' +import { CoreManager } from '../lib/core-manager/index.js' +import assert from 'assert' + +async function createCore (...args) { + const core = new Hypercore(RAM, ...args) + await core.ready() + return core +} + +test('shares auth cores', async function (t) { + const projectKey = randomBytes(32) + const cm1 = createCoreManager({ projectKey }) + const cm2 = createCoreManager({ projectKey }) + + replicate(cm1, cm2) + + await Promise.all([ + waitForCores(cm1, getKeys(cm2, 'auth')), + waitForCores(cm2, getKeys(cm1, 'auth')) + ]) + + const cm1Keys = getKeys(cm1, 'auth').sort(Buffer.compare) + const cm2Keys = getKeys(cm2, 'auth').sort(Buffer.compare) + + t.alike(cm1Keys, cm2Keys, 'Share same auth cores') +}) + +test('project creator auth core has project key', async function (t) { + const db = new Sqlite(':memory:') + const keyManager = new KeyManager(randomBytes(16)) + const { publicKey: projectKey, secretKey: projectSecretKey } = + keyManager.getHypercoreKeypair('auth', randomBytes(32)) + const cm = new CoreManager({ + db, + keyManager, + storage: RAM, + projectKey, + projectSecretKey + }) + const { key: authCoreKey } = cm.getWriterCore('auth') + t.ok(authCoreKey.equals(projectKey)) +}) + +test('getCreatorCore()', async t => { + const projectKey = randomBytes(32) + const cm = createCoreManager({ projectKey }) + await cm.creatorCore.ready() + t.ok(cm.creatorCore.key.equals(projectKey)) +}) + +test('eagerly updates remote bitfields', async function (t) { + // Replication progress relies on the peer.remoteBitfield to actually match + // the bitfield of the peer. By default hypercore only updates the + // remoteBitfield for the ranges of a hypercore that you try to download. We + // "hack" hypercore to get the bitfield for the whole core, and this test + // checks that functionality. + + const projectKey = randomBytes(32) + const cm1 = createCoreManager({ projectKey }) + const cm2 = createCoreManager({ projectKey }) + const cm3 = createCoreManager({ projectKey }) + + const cm1Core = cm1.getWriterCore('auth').core + await cm1Core.ready() + await cm1Core.append(['a', 'b', 'c', 'd', 'e']) + // Hypercore only shares the contiguous length on initial handshake, not the + // whole bitfield, so we need to test replicating a bitfield with + // contiguousLength < length + await cm1Core.clear(2, 3) + + const destroyReplication = replicate(cm1, cm2) + + await waitForCores(cm2, [cm1Core.key]) + const cm2Core = cm2.getCoreByKey(cm1Core.key) + t.ok(cm2Core, 'writer core has replicated') + + // Need to wait for now, since no event for when a remote bitfield is updated + await new Promise(res => setTimeout(res, 200)) + + t.is(cm2Core.length, cm1Core.length) + + { + // This is testing that the remote bitfield is a duplicate of the bitfield + // on the core that is being replicated, prior to calling core.download() + t.ok( + bitfieldEquals( + cm2Core.peers[0].remoteBitfield, + cm1Core.core.bitfield, + cm1Core.length + ), + 'remote bitfield is same as source bitfield' + ) + } + + // replicate the (sparse) data and then clear data on the writer + await cm2Core + .download({ start: 0, end: cm2Core.length, ifAvailable: true }) + .done() + await destroyReplication() + await cm1Core.clear(0, 2) + + { + // This is ensuring that bitfields also get propogated in the other + // direction, e.g. from the non-writer to the writer + const destroy = replicate(cm1, cm2) + // Need to wait for now, since no event for when a remote bitfield is updated + await new Promise(res => setTimeout(res, 200)) + t.ok( + bitfieldEquals( + cm1Core.peers[0].remoteBitfield, + cm2Core.core.bitfield, + cm1Core.length + ), + 'remote bitfield on writer matches bitfield from replica' + ) + await destroy() + } + + { + // cm3 is not connected directly to cm1. Important to catch edge case of + // propertly propagating updates to remoteBitfield between peers that are + // not directly connected + replicate(cm1, cm2) + replicate(cm2, cm3) + + await new Promise(res => setTimeout(res, 200)) + + const cm3Core = cm3.getCoreByKey(cm1Core.key) + t.alike(cm3Core.length, cm1Core.length) + + t.ok( + bitfieldEquals( + cm3Core.peers[0].remoteBitfield, + cm2Core.core.bitfield, + cm2Core.length + ), + 'remote bitfield updated via indirect replication' + ) + + await cm1Core.append(['f', 'g', 'h', 'i', 'j']) + await cm1Core.append(['k', 'l', 'm', 'o', 'p']) + await cm2Core.download({ start: 9, end: 12 }).done() + + await new Promise(res => setTimeout(res, 200)) + + t.alike(cm3Core.length, cm1Core.length) + t.ok( + bitfieldEquals( + cm3Core.peers[0].remoteBitfield, + cm2Core.core.bitfield, + cm2Core.length + ), + 'remote bitfield updated via indirect replication' + ) + } +}) + +test('works with an existing protocol stream for replications', async function (t) { + const projectKey = randomBytes(32) + const cm1 = createCoreManager({ projectKey }) + const cm2 = createCoreManager({ projectKey }) + + const n1 = new NoiseSecretStream(true) + const n2 = new NoiseSecretStream(false) + n1.rawStream.pipe(n2.rawStream).pipe(n1.rawStream) + + const s1 = Hypercore.createProtocolStream(n1) + const s2 = Hypercore.createProtocolStream(n2) + + cm1.replicate(s1) + cm2.replicate(s2) + + await Promise.all([ + waitForCores(cm1, getKeys(cm2, 'auth')), + waitForCores(cm2, getKeys(cm1, 'auth')) + ]) + + const cm1Keys = getKeys(cm1, 'auth').sort(Buffer.compare) + const cm2Keys = getKeys(cm2, 'auth').sort(Buffer.compare) + + t.alike(cm1Keys, cm2Keys, 'Share same auth cores') +}) + +test.skip('can mux other project replications over same stream', async function (t) { + // This test fails because https://github.com/holepunchto/corestore/issues/45 + // The `ondiscoverykey` hook for `Hypercore.createProtocolStream()` that we + // use to know when other cores are muxed in the stream is only called the + // first time the protocol stream is created. When a second core replicates + // to the same stream, it sees it is already a protomux stream, and it does + // not add the notify hook for `ondiscoverykey`. + // We might be able to work around this if we want to enable multi-project + // muxing before the issue is resolved by creating the protomux stream outside + // the core manager, and then somehow hooking into the relevant corestore. + t.plan(2) + const projectKey = randomBytes(32) + const cm1 = createCoreManager({ projectKey }) + const cm2 = createCoreManager({ projectKey }) + const otherProject = createCoreManager() + + const n1 = new NoiseSecretStream(true) + const n2 = new NoiseSecretStream(false) + n1.rawStream.pipe(n2.rawStream).pipe(n1.rawStream) + + await Promise.all([ + waitForCores(cm1, getKeys(cm2, 'auth')), + waitForCores(cm2, getKeys(cm1, 'auth')) + ]) + + cm1.replicate(n1) + otherProject.replicate(n2) + cm2.replicate(n2) +}) + +test('multiplexing waits for cores to be added', async function (t) { + // Mapeo code expects replication to work when cores are not added to the + // replication stream at the same time. This is not explicitly tested in + // Hypercore so we check here that this behaviour works. + t.plan(2) + + const a1 = await createCore() + const a2 = await createCore() + + const b1 = await createCore(a1.key) + const b2 = await createCore(a2.key) + + const n1 = new NoiseSecretStream(true) + const n2 = new NoiseSecretStream(false) + n1.rawStream.pipe(n2.rawStream).pipe(n1.rawStream) + + const stream1 = Hypercore.createProtocolStream(n1) + const stream2 = Hypercore.createProtocolStream(n2) + + a1.replicate(stream1, { keepAlive: false }) + a2.replicate(stream1, { keepAlive: false }) + + await a1.append('hi') + await a2.append('ho') + + setTimeout(() => { + b1.replicate(stream2, { keepAlive: false }) + b2.replicate(stream2, { keepAlive: false }) + }, 7000) + + t.alike(await b1.get(0), Buffer.from('hi')) + t.alike(await b2.get(0), Buffer.from('ho')) +}) + +async function waitForCores (coreManager, keys) { + const allKeys = getAllKeys(coreManager) + if (hasKeys(keys, allKeys)) return + return new Promise(res => { + coreManager.on('add-core', function onAddCore ({ key }) { + allKeys.push(key) + if (hasKeys(keys, allKeys)) { + coreManager.off('add-core', onAddCore) + res() + } + }) + }) +} + +function getAllKeys (coreManager) { + const keys = [] + for (const namespace of CoreManager.namespaces) { + keys.push.apply(keys, getKeys(coreManager, namespace)) + } + return keys +} + +function getKeys (coreManager, namespace) { + return coreManager.getCores(namespace).map(({ key }) => key) +} + +function hasKeys (someKeys, allKeys) { + for (const key of someKeys) { + if (!allKeys.find(k => k.equals(key))) return false + } + return true +} + +const DEBUG = process.env.DEBUG + +// Compare two bitfields (instance of core.core.bitfield or peer.remoteBitfield) +// Need to pass len, since bitfields don't know their own length +function bitfieldEquals (actual, expected, len) { + assert(typeof len === 'number') + let actualStr = '' + let expectedStr = '' + for (let i = 0; i < len; i++) { + const actualBit = actual.get(i) + const expectedBit = expected.get(i) + if (DEBUG) { + // This will cause memory issues for large bitfields, so only do for debug + actualStr += actualBit ? '1' : '0' + expectedStr += expectedBit ? '1' : '0' + } else { + if (actualBit !== expectedBit) return false + } + } + if (DEBUG) { + if (actualStr === expectedStr) { + console.log(`bitfield as expected: '${expectedStr}'`) + return true + } else { + console.error(`expected '${expectedStr}', but got '${actualStr}'`) + return false + } + } else { + return true + } +} diff --git a/tests/helpers/core-manager.js b/tests/helpers/core-manager.js new file mode 100644 index 00000000..bc3eef02 --- /dev/null +++ b/tests/helpers/core-manager.js @@ -0,0 +1,42 @@ +import { CoreManager } from '../../lib/core-manager/index.js' +import Sqlite from 'better-sqlite3' +import { randomBytes } from 'crypto' +import { KeyManager } from '@mapeo/crypto' +import RAM from 'random-access-memory' +import NoiseSecretStream from '@hyperswarm/secret-stream' + +export function createCoreManager ({ + rootKey = randomBytes(16), + projectKey = randomBytes(32) +} = {}) { + const db = new Sqlite(':memory:') + const keyManager = new KeyManager(rootKey) + return new CoreManager({ + db, + keyManager, + storage: RAM, + projectKey + }) +} + +export function replicate(cm1, cm2) { + const n1 = new NoiseSecretStream(true) + const n2 = new NoiseSecretStream(false) + n1.rawStream.pipe(n2.rawStream).pipe(n1.rawStream) + + cm1.replicate(n1) + cm2.replicate(n2) + + return async function destroy () { + return Promise.all([ + new Promise((res) => { + n1.on('close', res) + n1.destroy() + }), + new Promise((res) => { + n2.on('close', res) + n2.destroy() + }) + ]) + } +} diff --git a/tsconfig.json b/tsconfig.json index 08e747b7..fbc93331 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "strictNullChecks": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, - "module": "es2022", + "module": "ES2020", "moduleResolution": "node", "allowJs": true, "checkJs": true, diff --git a/types/corestore.d.ts b/types/corestore.d.ts index e8660199..66a5f485 100644 --- a/types/corestore.d.ts +++ b/types/corestore.d.ts @@ -1,6 +1,6 @@ declare module 'corestore' { import { TypedEmitter } from 'tiny-typed-emitter' - import Hypercore from 'hypercore' + import Hypercore, { type HypercoreStorage, type HypercoreOptions } from 'hypercore' interface CorestoreEvents { 'core-open'(core: Corestore): void @@ -8,19 +8,19 @@ declare module 'corestore' { } class Corestore extends TypedEmitter { - constructor(storage: Hypercore.HypercoreStorage) + constructor(storage: HypercoreStorage, options?: { primaryKey?: Buffer | Uint8Array }) get(key: Buffer | Uint8Array): Hypercore - get(options: Hypercore.HypercoreOptions & { name: string }): Hypercore + get(options: HypercoreOptions & { name: string }): Hypercore get( - options: Hypercore.HypercoreOptions & { key: Buffer | Uint8Array } + options: HypercoreOptions & { key: Buffer | Uint8Array } ): Hypercore get( - options: Hypercore.HypercoreOptions & { + options: HypercoreOptions & { keyPair: { publicKey: Buffer; secretKey: Buffer } } ): Hypercore replicate: typeof Hypercore.prototype.replicate - store(name: string): Corestore + namespace(name: string): Corestore ready(): Promise close(): Promise cores: Map diff --git a/types/hypercore-crypto.d.ts b/types/hypercore-crypto.d.ts new file mode 100644 index 00000000..80afc80e --- /dev/null +++ b/types/hypercore-crypto.d.ts @@ -0,0 +1,3 @@ +declare module 'hypercore-crypto' { + export function discoveryKey(publicKey: Buffer | Uint8Array): Buffer +} diff --git a/types/hypercore.d.ts b/types/hypercore.d.ts index 529bb070..98727e8b 100644 --- a/types/hypercore.d.ts +++ b/types/hypercore.d.ts @@ -16,51 +16,62 @@ declare module 'hypercore' { type ValueEncoding = 'json' | 'utf-8' | 'binary' - namespace Hypercore { - export type HypercoreStorage = - | string - | ((name: string) => RandomAccessStorage) - | typeof RandomAccessStorage - export interface HypercoreGetOptions { - wait?: boolean // wait for block to be downloaded - onwait?(): void // hook that is called if the get is waiting for download - timeout?: number // wait at max some milliseconds (0 means no timeout) - valueEncoding?: ValueEncoding // defaults to the core's valueEncoding - } - export interface HypercoreOptions { - createIfMissing?: boolean // create a new Hypercore key pair if none was present in storage - overwrite?: boolean // overwrite any old Hypercore that might already exist - valueEncoding?: ValueEncoding // defaults to binary - encodeBatch?(batch: any[]): void // optionally apply an encoding to complete batches - keyPair?: { publicKey: Buffer; secretKey: Buffer } // optionally pass the public key and secret key as a key pair - encryptionKey?: Buffer // optionally pass an encryption key to enable block encryption - sparse?: boolean // optionally disable sparse mode - } + export type HypercoreStorage = + | string + | ((name: string) => RandomAccessStorage) + | typeof RandomAccessStorage + export interface HypercoreGetOptions { + wait?: boolean // wait for block to be downloaded + onwait?(): void // hook that is called if the get is waiting for download + timeout?: number // wait at max some milliseconds (0 means no timeout) + valueEncoding?: ValueEncoding // defaults to the core's valueEncoding + } + export interface HypercoreOptions { + createIfMissing?: boolean // create a new Hypercore key pair if none was present in storage + overwrite?: boolean // overwrite any old Hypercore that might already exist + valueEncoding?: ValueEncoding // defaults to binary + encodeBatch?(batch: any[]): void // optionally apply an encoding to complete batches + keyPair?: { publicKey: Buffer; secretKey: Buffer } // optionally pass the public key and secret key as a key pair + encryptionKey?: Buffer // optionally pass an encryption key to enable block encryption + sparse?: boolean // optionally disable sparse mode + } + + export interface HypercoreExtension { + name: string + encoding: any + send (data: Buffer | Uint8Array, peer: any): void + broadcast (data: Buffer | Uint8Array): void + destroy(): void } class Hypercore< TValueEncoding extends ValueEncoding = 'binary' > extends TypedEmitter { + static createProtocolStream( + stream: Duplex, + options?: { ondiscoverykey?: (discoveryKey: Buffer) => void } + ): Duplex & { noiseStream: Duplex & { userData: any }} constructor( - options: Hypercore.HypercoreOptions & { - storage: Hypercore.HypercoreStorage + options: HypercoreOptions & { + storage: HypercoreStorage valueEncoding?: TValueEncoding } ) constructor( - storage: Hypercore.HypercoreStorage, + storage: HypercoreStorage, key: Buffer | string, - options?: Hypercore.HypercoreOptions & { valueEncoding?: TValueEncoding } + options?: HypercoreOptions & { valueEncoding?: TValueEncoding } ) constructor( - storage: Hypercore.HypercoreStorage, - options?: Hypercore.HypercoreOptions & { valueEncoding?: TValueEncoding } + storage: HypercoreStorage, + options?: HypercoreOptions & { valueEncoding?: TValueEncoding } ) [key: string]: any + readonly peers: any[] readonly key: Buffer get( index: number, - options?: Hypercore.HypercoreGetOptions & { + options?: HypercoreGetOptions & { valueEncoding?: TGetValueEncoding } ): Promise< @@ -73,11 +84,12 @@ declare module 'hypercore' { ? any : unknown) > + registerExtension(name: string, handlers?: { encoding?: any, onmessage?: (buf: Buffer, peer: any) => void}): HypercoreExtension replicate( isInitiatorOrReplicationStream: boolean | Duplex, opts?: { keepAlive?: boolean } ): Duplex } - export = Hypercore + export default Hypercore } diff --git a/types/modules.d.ts b/types/modules.d.ts index 0bdf8c91..e0c3ff73 100644 --- a/types/modules.d.ts +++ b/types/modules.d.ts @@ -7,7 +7,6 @@ declare module 'brittle' declare module 'multi-core-indexer' declare module '@mapeo/sqlite-indexer' -declare module '@mapeo/crypto' declare module 'sodium-universal' declare module 'base32.js'