Repository: datproject/sdk Branch: master Commit: b5980211c7bb Files: 20 Total size: 61.4 KB Directory structure: gitextract_wwgwrlvq/ ├── .github/ │ └── workflows/ │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── dist/ │ ├── index.d.ts │ └── test.d.ts ├── index.js ├── package.json ├── test.js ├── tsconfig.json └── types/ ├── corestore.d.ts ├── hyperbee.d.ts ├── hypercore-crypto.d.ts ├── hypercore.d.ts ├── hyperdrive.d.ts ├── hyperswarm.d.ts ├── rocksdb-native.d.ts ├── test-tmp.d.ts └── z32.d.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/test.yml ================================================ name: Testing on: [ push, pull_request ] jobs: build: strategy: matrix: node: [ '24' ] os: [macos-latest, ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} name: Unit tests ${{ matrix.node }} ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - run: npm install - run: npm run lint - run: npm run test:node ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # next.js build output .next package-lock.json test-bundle.js bundle.js dat-sdk-bundle.js # IDE .idea ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hi@datproject.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Dat Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # hyper-sdk A Software Development Kit for the [hypercore-protocol](https://hypercore-protocol.org/) ## Why use this? Hypercore-protocol and it's ecosystem consists of a bunch of low level building blocks for working with data in distributed applications. Although this modularity makes it easy to mix and match pieces, it adds complexity when it comes to actually building something. The Hyper SDK combines the lower level pieces of the Hyper stack into high level APIs that you can use across platforms so that you can focus on your application rather than the gritty details of how it works. ## Goals - High level API - Cross-platform with same codebase - ✔ [Node.js](https://nodejs.org/en) - ✔ [Electron](https://www.electronjs.org/) - ✔ [Pear](https://docs.pears.com/) - 🏗️ Web (PRs welcome) ## Installation Make sure you've set up [Node.js](https://nodejs.org/). ```shell npm install --save hyper-sdk # or yarn ``` ```js import * as SDK from "hyper-sdk" ``` ## API ### SDK.create() ```JavaScript const sdk = await SDK.create({ // This argument is mandatory since Hypercore no longer support in-memory // Check out the env-paths module for application specific path storage storage: './hyper-sdk', // This controls whether the SDK will automatically start swarming when loading a core via `get` // Set this to false if you want to have more fine control over peer discovery autoJoin: true, // Specify options to pass to the Corestore constructor // The storage will get derived from the `storage` parameter // https://github.com/hypercore-protocol/corestore/ corestoreOpts: {}, // Specify options to pass to the hyperswarm constructor // The keypair will get derived automatically from the corestore // https://github.com/hyperswarm/hyperswarm swarmOpts: {}, }) ``` ### sdk.publicKey The public key used for identifying this peer in the hyperswarm network. This is a 32 byte buffer which can be use in conjunction with `sdk.joinPeer()` to connect two peers directly together. ### sdk.connections The list of active connections to other peers, taken from hyperswarm. ### sdk.peers The list of active peers. Each peer has a `publicKey`, and list of `topics` You can find more docs in the [hyperswarm](https://github.com/hyperswarm/hyperswarm#peerinfo-api) repo. ### sdk.cores List of active Hypercores. ### sdk.on('peer-add', peerInfo) / sdk.on('peer-remove', peerInfo) You can listen on when a peer gets connected or disconnected with this event. You can find more docs in the [hyperswarm](https://github.com/hyperswarm/hyperswarm#peerinfo-api) repo. ```JavaScript sdk.on('peer-add', (peerInfo) => { console.log('Connected to', peerInfo.publicKey, 'on', peerInfo.topics) }) sdk.on('peer-add', (peerInfo) => { console.log('Disconnected from') }) ``` ### sdk.get() You can initialize a [Hypercore](https://github.com/hypercore-protocol/hypercore) instance by passing in a key, a name to derive a key from, or a URL containing either a key or a DNS name. Unlike corestore, you may not initialize a hypercore from a `null` key since everything must be derivable or loadable. Unless `autoJoin` is set to `false`, the peer discovery will be automatically started for the core. ```JavaScript // Derive a key from a "name" const core = await sdk.get('example name') // Resolve DNS to a hypercore const core = await sdk.get('hyper://example.mauve.moe') // Buffer key, 32 bytes of 0's const core = await sdk.get(b4a.alloc(32, 0)) // Hex key, equivalent to 32 bytes of zeros const core = await sdk.get('hyper://0000000000000000000000000000000000000000000000000000000000000000') // z32 encoded, equivalent to 32 bytes of zeros const core = await sdk.get('hyper://yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy') // Don't auto-join the swarm for the core on init const core = await sdk.get('example', {autoJoin: false}) ``` ### sdk.getDrive() You can initialize a [Hyperdrive](https://github.com/holepunchto/hyperdrive-next) instance by passing in the same arguments as in `sdk.get()`. In addition to the usual `hyperdrive` properties, there's a new `url` property to get the `hyper://` URL for the drive to used elsewhere. Note that the drives's metadata DB's discovery key will be used for replicating if `autoJoin` is `true`. Hyperdrive is mostly useful for storing and loading files since it splits the metadata representing the file systema and the blob storage into separate cores. ```JavaScript const drive = await sdk.getDrive('hyper://blob.mauve.moe') for(const path of drive.readdir('/')) { const stat = drive.stat(path) } ``` ### sdk.getBee() You can initialize a [Hyperbee](https://github.com/holepunchto/hyperbee) instance by passing the same arguments as in `sdk.get()`. In addition to the usual `hyperbee` properties, there's a new `url` property to get the `hyper://` URL for the bee to used elsewhere. Additionally, you should pass in a `keyEncoding` and a `valueEncoding` in order to control the encoding for data that's being written. Hyperbee is best used when you want to create database indexes. For an out of the box database with a proper query language, check out [HyperbeeDeeBee](https://github.com/RangerMauve/hyperbeedeebee/). ```JavaScript const db = await sdk.getBee('example db') const db = await sdk.getBee('example db', {keyEncoding: 'utf8', valueEncoding: 'json') await db.put('hello', 'world') for(const entry of db.createReadStream()) { console.log(entry) } ``` ### sdk.resolveDNSToKey() You can manually resolve DNS addresses to hypercore keys on domains using the DNS Link spec with this method. However, it's not mandatory to use DNS since `sdk.get()` will automatically detect and perform resolutions of DNS for `hyper://` URLs. Hyper-SDK currently bypasses the OS DNS resolver and uses DNS Over HTTPS. You can configure your own using the `dnsResolver` config option and any of the options [on this list](https://dnsprivacy.org/public_resolvers/#dns-over-https-doh). By default we use the one provided by [Mozilla](https://developers.cloudflare.com/1.1.1.1/commitment-to-privacy/privacy-policy/firefox/). ```JavaScript const key = await sdk.resolveDNSToKey('example.mauve.moe') ``` ### sdk.namespace() Get back a namespaced [Corestore](https://github.com/hypercore-protocol/corestore/) instance which can be passed to things like Hyperdrive. Note that cores initialized with a namespaced corestore will not be auto-joined and you will need to call `sdk.join(core.discoveryKey)` on said cores. ```JavaScript import Hypderdrive from "hyperdrive" const drive = new Hyperdrive(sdk.namespace('example')) // Wait for the drive to initiailize await drive.ready() // Manually trigger peer lookup for this drive sdk.join(drive.publicKey) ``` ### sdk.join() / sdk.leave() You can manually trigger peer discovery of hypercores as well as stop peer discovery. This can be done by using the `discoveryKey` of a hypercore, or any 32 byte buffer. As well, you can use string names for topics in order to discover peers based on a human readable string. When using string topics, they are converted to 32 byte buffers using the [Hypercore Crypto namespace algorithm](https://github.com/mafintosh/hypercore-crypto#list--cryptonamespacename-count). ```JavaScript const core = await sdk.get('example', {autoJoin: false}) // Start finding peers without advertising sdk.join(core.discoveryKey, {server: false}) // Listen on a human readable topic sdk.join("cool cat videos") sdk.leave(core.discoveryKey) sdk.leave("cool cat videos") ``` ### sdk.joinPeer() / sdk.leavePeer() ```JavaScript const sdk1 = await SDK.create({storage: './sdk1'}) const sdk2 = await SDK.create({storage: './sdk1'}) sdk1.joinPeer(sdk2.publicKey) ``` ### sdk.close() This will gracefully close connections, remove advertisements from the DHT, and close any open file handles. Make sure you invoke this to keep the network fast and to avoid data corruption! ### sdk.suspend() This will pause network and data operations. Use it when your app is in the background or the user wants to pause seeding. ### sdk.resume() Undo the effects of `suspend()`, re-enable network and storage. ## TypeScript Support This module comes with TypeScript types, but by default Hypercore and other Holepunch libraries do not. You can add these missing types by importing them from the SDK's `types` folder in your `tsconfig.json` file. Your `tsconfig.json` file should look something like this: ``` { "compilerOptions": { "module": "nodenext", "moduleResolution": "nodenext", }, "extends": "@tsconfig/node20/tsconfig.json", "include": [ "./test.js", "./node_modules/hyper-sdk/types/*.d.ts" ] } ``` ================================================ FILE: dist/index.d.ts ================================================ /** * * @param {object} options * @param {string} [options.storage] * @param {CoreStoreOpts} [options.corestoreOpts] * @param {SwarmOpts} [options.swarmOpts] * @param {typeof globalThis["fetch"]} [options.fetch] * @param {HyperSwarm} [options.swarm] * @param {CoreStore} [options.corestore] * @param {RocksDB} [options.dnsCache] * @param {CoreOpts} [options.defaultCoreOpts] * @param {JoinOpts} [options.defaultJoinOpts] * @param {string} [options.dnsResolver] * @param {boolean} [options.autoJoin=true] * @param {boolean} [options.doReplicate=true] * @returns {Promise} */ export function create({ storage, corestoreOpts, swarmOpts, fetch, ...opts }?: { storage?: string | undefined; corestoreOpts?: CoreStoreOpts | undefined; swarmOpts?: SwarmOpts | undefined; fetch?: typeof globalThis.fetch | undefined; swarm?: HyperSwarm | undefined; corestore?: CoreStore | undefined; dnsCache?: RocksDB | undefined; defaultCoreOpts?: CoreOpts | undefined; defaultJoinOpts?: JoinOpts | undefined; dnsResolver?: string | undefined; autoJoin?: boolean | undefined; doReplicate?: boolean | undefined; }): Promise; /** @import {JoinOpts,SwarmOpts,PeerDiscovery,Connection} from "hyperswarm" */ /** @import {BeeOpts} from "hyperbee" */ /** @import {CoreOpts} from "hypercore" */ /** @import {DriveOpts} from "hyperdrive" */ /** @import {CoreStoreOpts} from "corestore" */ /** @typedef {Buffer|Uint8Array} Key */ /** @typedef {string|Key} NameOrKeyOrURL */ /** @typedef {{key?:Key|null, name?:string}} ResolvedKeyOrName */ /** * @typedef {object} DNSResponse * @property {{name: string, data: string}} DNSResponse.Answer */ export const HYPER_PROTOCOL_SCHEME: "hyper://"; export const DEFAULT_CORE_OPTS: {}; export namespace DEFAULT_JOIN_OPTS { let server: boolean; let client: boolean; } export const DEFAULT_CORESTORE_OPTS: {}; export const DEFAULT_SWARM_OPTS: {}; export class SDK extends EventEmitter { /** * @param {object} [options] * @param {typeof globalThis["fetch"]} [options.fetch] * @param {HyperSwarm} [options.swarm] * @param {CoreStore} [options.corestore] * @param {CoreOpts} [options.defaultCoreOpts] * @param {JoinOpts} [options.defaultJoinOpts] * @param {string} [options.dnsResolver] * @param {boolean} [options.autoJoin=true] * @param {boolean} [options.doReplicate=true] * @param {RocksDB} [options.dnsCache] */ constructor({ swarm, corestore, dnsCache, fetch, defaultCoreOpts, defaultJoinOpts, dnsResolver, autoJoin, doReplicate }?: { fetch?: typeof globalThis.fetch | undefined; swarm?: HyperSwarm | undefined; corestore?: CoreStore | undefined; defaultCoreOpts?: CoreOpts | undefined; defaultJoinOpts?: JoinOpts | undefined; dnsResolver?: string | undefined; autoJoin?: boolean | undefined; doReplicate?: boolean | undefined; dnsCache?: RocksDB | undefined; }); autoJoin: boolean; get swarm(): HyperSwarm; get corestore(): CoreStore; get publicKey(): import("hyperswarm").Key; get connections(): Connection[]; get peers(): Map; /** * @type {Hypercore[]} */ get cores(): Hypercore[]; /** * @type {Hyperdrive[]} */ get drives(): Hyperdrive[]; /** @type {Hyperbee[]} */ get bees(): Hyperbee[]; /** * Resolve DNS names to a hypercore key using the DNSLink spec * @param {string} hostname Hostname to resolve, e,g, `agregore.mauve.moe` * @returns {Promise} */ resolveDNSToKey(hostname: string): Promise; /** * Resolves a string to be a key or opts and resolves DNS * Useful for hypercore opts or Hyperdrive * @param {NameOrKeyOrURL} nameOrKeyOrURL Name or key or URL to resolve * @returns {Promise} */ resolveNameOrKeyToOpts(nameOrKeyOrURL: NameOrKeyOrURL): Promise; /** * * @param {NameOrKeyOrURL} nameOrKeyOrURL Name or key or hyper URL for the bee * @param {BeeOpts & CoreOpts & JoinOpts} opts Options for configuring Hyperbee * @returns {Promise} */ getBee(nameOrKeyOrURL: NameOrKeyOrURL, opts?: BeeOpts & CoreOpts & JoinOpts): Promise; /** * * @param {NameOrKeyOrURL} nameOrKeyOrURL * @param {DriveOpts&JoinOpts} opts * @returns {Promise} */ getDrive(nameOrKeyOrURL: NameOrKeyOrURL, opts?: DriveOpts & JoinOpts): Promise; /** * @template DataType * Get a HyperCore by its name or key or URL * @param {NameOrKeyOrURL} nameOrKeyOrURL * @param {CoreOpts&JoinOpts} [opts] * @returns {Promise>} */ get(nameOrKeyOrURL: NameOrKeyOrURL, opts?: CoreOpts & JoinOpts): Promise>; /** * Get a sub CoreStore for a given namespace. Use this to derive core names for a particular group * @param {string} namespace Namespace to store cores under * @returns {CoreStore} */ namespace(namespace: string): CoreStore; /** * Derive a topic key (for hypercores) from a namespace. * @param {string} name Name of the namespace to derive * @returns {Key} */ makeTopicKey(name: string): Key; /** * Start peer discovery on a core. Use this if you created a core on a namespaced CoreStore * @param {Hypercore} core * @param {JoinOpts} opts * @returns {Promise} */ joinCore(core: Hypercore, opts?: JoinOpts): Promise; /** * * @param {string|Key} topic * @param {JoinOpts} opts * @returns {PeerDiscovery} */ join(topic: string | Key, opts?: JoinOpts): PeerDiscovery; /** * * @param {string|Key} topic * @returns {Promise} */ leave(topic: string | Key): Promise; /** * @param {Key} id */ joinPeer(id: Key): void; /** * @param {Key} id */ leavePeer(id: Key): void; ready(): Promise; close(): Promise; /** * Replicate a connection from hyperswarm manually * @param {Connection} connection */ replicate(connection: Connection): void; #private; } export type Key = Buffer | Uint8Array; export type NameOrKeyOrURL = string | Key; export type ResolvedKeyOrName = { key?: Key | null; name?: string; }; export type DNSResponse = { Answer: { name: string; data: string; }; }; import type { CoreStoreOpts } from "corestore"; import type { SwarmOpts } from "hyperswarm"; import HyperSwarm from 'hyperswarm'; import CoreStore from 'corestore'; import RocksDB from 'rocksdb-native'; import type { CoreOpts } from "hypercore"; import type { JoinOpts } from "hyperswarm"; import { EventEmitter } from 'events'; import type { Connection } from "hyperswarm"; import Hypercore from 'hypercore'; import Hyperdrive from 'hyperdrive'; import Hyperbee from 'hyperbee'; import type { BeeOpts } from "hyperbee"; import type { DriveOpts } from "hyperdrive"; import type { PeerDiscovery } from "hyperswarm"; ================================================ FILE: dist/test.d.ts ================================================ export {}; ================================================ FILE: index.js ================================================ import HyperSwarm from 'hyperswarm' import CoreStore from 'corestore' import Hypercore from 'hypercore' import Hyperdrive from 'hyperdrive' import Hyperbee from 'hyperbee' import crypto from 'hypercore-crypto' import z32 from 'z32' import b4a from 'b4a' import { EventEmitter } from 'events' import { join } from 'path' import RocksDB from 'rocksdb-native' /** @import {JoinOpts,SwarmOpts,PeerDiscovery,Connection} from "hyperswarm" */ /** @import {BeeOpts} from "hyperbee" */ /** @import {CoreOpts} from "hypercore" */ /** @import {DriveOpts} from "hyperdrive" */ /** @import {CoreStoreOpts} from "corestore" */ /** @typedef {Buffer|Uint8Array} Key */ /** @typedef {string|Key} NameOrKeyOrURL */ /** @typedef {{key?:Key|null, name?:string}} ResolvedKeyOrName */ /** * @typedef {object} DNSResponse * @property {{name: string, data: string}} DNSResponse.Answer */ // TODO: Base36 encoding/decoding for URLs instead of hex export const HYPER_PROTOCOL_SCHEME = 'hyper://' export const DEFAULT_CORE_OPTS = {} export const DEFAULT_JOIN_OPTS = { server: true, client: true } export const DEFAULT_CORESTORE_OPTS = {} export const DEFAULT_SWARM_OPTS = {} // Monkey-patching with first class URL support Object.defineProperty(Hypercore.prototype, 'url', { get: function () { return `${HYPER_PROTOCOL_SCHEME}${this.id}/` } }) Object.defineProperty(Hyperdrive.prototype, 'url', { get: function () { return `${HYPER_PROTOCOL_SCHEME}${this.core.id}/` } }) Object.defineProperty(Hyperbee.prototype, 'url', { get: function () { return `${HYPER_PROTOCOL_SCHEME}${this.feed.id}/` } }) const DEFAULT_DNS_RESOLVER = 'https://mozilla.cloudflare-dns.com/dns-query' const DNSLINK_PREFIX = 'dnslink=/hyper/' export class SDK extends EventEmitter { #fetch #dnsCache #dnsMemoryCache #defaultCoreOpts #defaultJoinOpts #dnsResolver #swarm #corestore #coreCache #beeCache #driveCache /** * @param {object} [options] * @param {typeof globalThis["fetch"]} [options.fetch] * @param {HyperSwarm} [options.swarm] * @param {CoreStore} [options.corestore] * @param {CoreOpts} [options.defaultCoreOpts] * @param {JoinOpts} [options.defaultJoinOpts] * @param {string} [options.dnsResolver] * @param {boolean} [options.autoJoin=true] * @param {boolean} [options.doReplicate=true] * @param {RocksDB} [options.dnsCache] */ constructor ({ swarm, corestore, dnsCache, fetch = globalThis.fetch, defaultCoreOpts = DEFAULT_CORE_OPTS, defaultJoinOpts = DEFAULT_JOIN_OPTS, dnsResolver = DEFAULT_DNS_RESOLVER, autoJoin = true, doReplicate = true } = {}) { super() if (!swarm) throw new TypeError('Missing parameter swarm') if (!corestore) throw new TypeError('Missing parameter corestore') if (!dnsCache) throw new TypeError('Missing parameter dnsCache') this.#swarm = swarm this.#corestore = corestore this.#dnsCache = dnsCache this.#fetch = fetch // These probably shouldn't be accessed this.#dnsMemoryCache = new Map() this.#coreCache = new Map() this.#beeCache = new Map() this.#driveCache = new Map() this.#defaultCoreOpts = defaultCoreOpts this.#defaultJoinOpts = defaultJoinOpts this.#dnsResolver = dnsResolver this.autoJoin = autoJoin if (doReplicate) { swarm.on('connection', (connection, peerInfo) => { this.emit('peer-add', peerInfo) connection.once('close', () => this.emit('peer-remove', peerInfo)) this.replicate(connection) }) } } get swarm () { return this.#swarm } get corestore () { return this.#corestore } get publicKey () { return this.#swarm.keyPair.publicKey } get connections () { return this.#swarm.connections } get peers () { return this.#swarm.peers } /** * @type {Hypercore[]} */ get cores () { return [...this.#coreCache.values()] } /** * @type {Hyperdrive[]} */ get drives () { return [...this.#driveCache.values()] } /** @type {Hyperbee[]} */ get bees () { return [...this.#beeCache.values()] } /** * Resolve DNS names to a hypercore key using the DNSLink spec * @param {string} hostname Hostname to resolve, e,g, `agregore.mauve.moe` * @returns {Promise} */ async resolveDNSToKey (hostname) { // TODO: Check for TTL? if (this.#dnsMemoryCache.has(hostname)) { return this.#dnsMemoryCache.get(hostname) } const fetch = this.#fetch const subdomained = `_dnslink.${hostname}` const url = `${this.#dnsResolver}?name=${subdomained}&type=TXT` let answers = null try { const response = await fetch(url, { headers: { accept: 'application/dns-json' } }) if (!response.ok) { throw new Error( `Unable to resolve DoH for ${hostname} ${await response.text()}` ) } const dnsResults = /** @type {DNSResponse} */ (await response.json()) answers = dnsResults.Answer await this.#dnsCache.put(hostname, JSON.stringify(dnsResults)) } catch (e) { const cached = await this.#dnsCache.get(hostname) if (cached) { answers = JSON.parse(cached).Answer } } for (let { name, data } of answers) { if (name !== subdomained || !data) { continue } if (data.startsWith('"')) { data = data.slice(1, -1) } if (!data.startsWith(DNSLINK_PREFIX)) { continue } const key = data.split('/')[2] this.#dnsMemoryCache.set(hostname, key) return key } throw new Error(`DNS-Link Record not found for TXT ${subdomained}`) } /** * Resolves a string to be a key or opts and resolves DNS * Useful for hypercore opts or Hyperdrive * @param {NameOrKeyOrURL} nameOrKeyOrURL Name or key or URL to resolve * @returns {Promise} */ async resolveNameOrKeyToOpts (nameOrKeyOrURL) { // If a URL, use the hostname as either a key or a DNS to resolve // If not a URL, try to decode to a key // if not a key, use as name to generate a hypercore // Else it's an errorW const isKeyString = typeof nameOrKeyOrURL === 'string' if (!isKeyString) { // If a 32 byte buffer, use it as the key if (nameOrKeyOrURL && nameOrKeyOrURL.length === 32) { return { key: nameOrKeyOrURL } } else { throw new Error( 'Must specify a name, url, or a 32 byte buffer with a key' ) } } if (nameOrKeyOrURL.startsWith(HYPER_PROTOCOL_SCHEME)) { const url = new URL(nameOrKeyOrURL) // probably a domain if (url.hostname.includes('.')) { const key = await this.resolveDNSToKey(url.hostname) return { key: stringToKey(key) } } else { // Try to parse the hostname to a key const key = stringToKey(url.hostname) if (!key) { // If not a key or a domain, throw an error throw new Error( 'URLs must have either an encoded key or a valid DNSlink domain' ) } return { key } } } else { const parsed = stringToKey(nameOrKeyOrURL) if (parsed) { return { key: parsed } } else { return { name: nameOrKeyOrURL } } } } /** * * @param {NameOrKeyOrURL} nameOrKeyOrURL Name or key or hyper URL for the bee * @param {BeeOpts & CoreOpts & JoinOpts} opts Options for configuring Hyperbee * @returns {Promise} */ async getBee (nameOrKeyOrURL, opts = {}) { const core = await this.get(nameOrKeyOrURL, opts) if (this.#beeCache.has(core.url)) { return this.#beeCache.get(core.url) } const bee = new Hyperbee(core, opts) core.once('close', () => { this.#beeCache.delete(core.url) }) this.#beeCache.set(core.url, bee) await bee.ready() return bee } /** * * @param {NameOrKeyOrURL} nameOrKeyOrURL * @param {DriveOpts&JoinOpts} opts * @returns {Promise} */ async getDrive (nameOrKeyOrURL, opts = {}) { const coreOpts = { ...this.#defaultCoreOpts, autoJoin: this.autoJoin, ...opts } const resolvedOpts = await this.resolveNameOrKeyToOpts(nameOrKeyOrURL) const { key, name } = resolvedOpts let stringKey = key && key.toString('hex') if (this.#driveCache.has(name)) { return this.#driveCache.get(name) } else if (this.#driveCache.has(stringKey)) { return this.#driveCache.get(stringKey) } Object.assign(coreOpts, resolvedOpts) let corestore = this.corestore if (stringKey) { corestore = this.namespace(stringKey) } else if (name) { corestore = this.namespace(name) } else { throw new Error('Unable to parse') } const drive = new Hyperdrive(corestore, key || null) await drive.ready() const core = drive.core stringKey = core.key.toString('hex') drive.once('close', () => { this.#driveCache.delete(stringKey) this.#driveCache.delete(name) }) this.#driveCache.set(stringKey, drive) if (name) this.#driveCache.set(name, drive) if (coreOpts.autoJoin && !core.discovery) { await this.joinCore(core, opts) } return drive } /** * @template DataType * Get a HyperCore by its name or key or URL * @param {NameOrKeyOrURL} nameOrKeyOrURL * @param {CoreOpts&JoinOpts} [opts] * @returns {Promise>} */ async get (nameOrKeyOrURL, opts = {}) { const coreOpts = { ...this.#defaultCoreOpts, autoJoin: this.autoJoin, ...opts } const resolvedOpts = await this.resolveNameOrKeyToOpts(nameOrKeyOrURL) const { key, name } = resolvedOpts let stringKey = key && key.toString('hex') if (this.#coreCache.has(name)) { return this.#coreCache.get(name) } else if (this.#coreCache.has(stringKey)) { return this.#coreCache.get(stringKey) } Object.assign(coreOpts, resolvedOpts) // There shouldn't be a way to pass null for the key const core = this.corestore.get(coreOpts) // Await for core to be ready await core.ready() core.once('close', () => { this.#coreCache.delete(stringKey) this.#coreCache.delete(name) }) stringKey = core.key.toString('hex') this.#coreCache.set(stringKey, core) if (name) this.#coreCache.set(name, core) if (coreOpts.autoJoin && !core.discovery) { await this.joinCore(core, opts) } return core } /** * Get a sub CoreStore for a given namespace. Use this to derive core names for a particular group * @param {string} namespace Namespace to store cores under * @returns {CoreStore} */ namespace (namespace) { return this.corestore.namespace(namespace) } /** * Derive a topic key (for hypercores) from a namespace. * @param {string} name Name of the namespace to derive * @returns {Key} */ makeTopicKey (name) { const [key] = crypto.namespace(name, 1) return key } /** * Start peer discovery on a core. Use this if you created a core on a namespaced CoreStore * @param {Hypercore} core * @param {JoinOpts} opts * @returns {Promise} */ async joinCore (core, opts = {}) { if (core.discovery) return const discovery = this.join(core.discoveryKey, opts) core.discovery = discovery // If we're the owner, then we wait until is fully announced if (core.writable) { await discovery.flushed() } // Await for initial peer for new readable cores if (!core.writable && !core.length) { const done = core.findingPeers() this.swarm.flush().then(done) await core.update() } core.once('close', () => { discovery.destroy() }) } /** * * @param {string|Key} topic * @param {JoinOpts} opts * @returns {PeerDiscovery} */ join (topic, opts = {}) { if (typeof topic === 'string') { return this.join(this.makeTopicKey(topic), opts) } const joinOpts = { ...this.#defaultJoinOpts, ...opts } return this.swarm.join(topic, joinOpts) } /** * * @param {string|Key} topic * @returns {Promise} */ leave (topic) { if (typeof topic === 'string') { return this.leave(this.makeTopicKey(topic)) } return this.swarm.leave(topic) } /** * @param {Key} id */ joinPeer (id) { this.swarm.joinPeer(id) } /** * @param {Key} id */ leavePeer (id) { this.swarm.leavePeer(id) } async ready () { // Wait for the network to be configured? await this.corestore.ready() await this.swarm.listen() } async close () { await this.#dnsCache.flush() // Close corestore, close hyperswarm await Promise.all([ this.corestore.close(), this.swarm.destroy(), this.#dnsCache.close() ]) } /** * Persist any pending transactions to disk and pause network activity. * Use this when the app goes in the background or you want to pause seeding. */ async suspend() { await this.swarm.suspend() await this.corestore.suspend() } /** * Resume network interactions and re-enable storage after suspending. */ async resume() { await this.corestore.resume() await this.swarm.resume() } /** * Replicate a connection from hyperswarm manually * @param {Connection} connection */ replicate (connection) { this.corestore.replicate(connection) } } /** * Initialize the SDK * @param {object} options * @param {string} options.storage * @param {CoreStoreOpts} [options.corestoreOpts] * @param {SwarmOpts} [options.swarmOpts] * @param {typeof globalThis["fetch"]} [options.fetch] * @param {HyperSwarm} [options.swarm] * @param {CoreStore} [options.corestore] * @param {RocksDB} [options.dnsCache] * @param {CoreOpts} [options.defaultCoreOpts] * @param {JoinOpts} [options.defaultJoinOpts] * @param {string} [options.dnsResolver] * @param {boolean} [options.autoJoin=true] * @param {boolean} [options.doReplicate=true] * @returns {Promise} */ export async function create ({ storage, corestoreOpts = DEFAULT_CORESTORE_OPTS, swarmOpts = DEFAULT_SWARM_OPTS, fetch = globalThis.fetch, ...opts } = {storage: ''}) { if (!storage) { throw new Error('Storage parameter is required to be a valid file path') } const corestore = opts.corestore || new CoreStore(storage, { ...corestoreOpts }) const dnsCache = opts.dnsCache || new RocksDB(join(storage, 'dnsCache')) const networkKeypair = await corestore.createKeyPair('noise') const swarm = opts.swarm || new HyperSwarm({ keyPair: networkKeypair, ...swarmOpts }) const sdk = new SDK({ ...opts, fetch: fetch || (await import('bare-fetch')).default, corestore, swarm, dnsCache }) await sdk.ready() return sdk } /** * @param {string} string * @returns {Key|null} */ function stringToKey (string) { if (string.length === 52) { try { return z32.decode(string) } catch { // Not formatted properly, probs a name? } } else if (string.length === 64) { // Parse as hex key try { return b4a.from(string, 'hex') } catch { // Not formatted properly, probs a name? } } return null } ================================================ FILE: package.json ================================================ { "name": "hyper-sdk", "version": "6.2.2", "description": "A Software Development Kit for the Hypercore-Protocol", "type": "module", "exports": { ".": { "default": "./index.js", "types": "./dist/index.d.ts" } }, "imports": { "events": { "default": "bare-events" }, "stream": { "default": "bare-stream" }, "path": { "default": "bare-path" } }, "types": "dist", "scripts": { "test": "npm run test:node && npm run test:bare", "test:bare": "bare test.js", "test:node": "node test.js", "lint": "standard --fix && tsc", "upgrade-hyper": "npm i --save corestore@latest hypercore@latest hyperswarm@latest hyperdrive@latest hyperbee@latest z32@latest b4a@latest bare-events@latest bare-fetch@latest bare-os@latest bare-path@latest bare-stream@latest" }, "repository": { "type": "git", "url": "git+https://github.com/rangermauve/hyper-sdk.git" }, "keywords": [ "dat", "sdk", "hyperdrive", "hypercore", "hypercore-protocol", "p2p" ], "author": "RangerMauve", "license": "MIT", "bugs": { "url": "https://github.com/rangermauve/hyper-sdk/issues" }, "homepage": "https://github.com/rangermauve/hyper-sdk#readme", "dependencies": { "b4a": "^1.7.3", "bare-events": "^2.8.2", "bare-fetch": "^2.5.1", "bare-os": "^3.6.2", "bare-path": "^3.0.0", "bare-stream": "^2.7.0", "corestore": "^7.6.1", "dns-query": "^0.11.2", "hyperbee": "^2.26.5", "hypercore": "^11.20.1", "hyperdrive": "^13.0.2", "hyperswarm": "^4.16.0", "rocksdb-native": "^3.5.10", "test-tmp": "^1.4.0", "z32": "^1.1.0" }, "devDependencies": { "@tsconfig/node20": "^20.1.8", "@types/b4a": "^1.6.5", "@types/brittle": "^3.5.0", "@types/compact-encoding": "^2.15.0", "@types/node": "^25.0.3", "@types/streamx": "^2.9.5", "bare": "^1.22.0", "brittle": "^3.7.0", "standard": "^17.0.0", "tape": "^5.6.1", "tmp-promise": "^3.0.3", "typescript": "^5.9.3" } } ================================================ FILE: test.js ================================================ import { test, configure } from 'brittle' import { once } from 'events' import { create } from './index.js' import b4a from 'b4a' import tmp from 'test-tmp' /** @import Hyperbee from 'hyperbee' */ const NULL_KEY = 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy' const NULL_BUFFER = b4a.alloc(32, 0) const NULL_HEX_KEY = NULL_BUFFER.toString('hex') const NULL_URL = `hyper://${NULL_KEY}/` // Close can take a while const timeout = 120_000 configure({ timeout }) test('Specify storage for sdk', async (t) => { const storage = await tmp() const name = 'example' const data = 'Hello World!' let sdk = await create({ storage }) let sdk2 = null try { try { sdk2 = await create({ storage }) t.fail('Should not be able to load SDK over existing dir') } catch { t.pass('Threw error when opening same storage path twice') } finally { if (sdk2) await sdk2.close() } const core1 = await sdk.get(name) const url1 = core1.url await core1.append(data) await sdk.close() sdk = await create({ storage }) const core2 = await sdk.get(name) const url2 = core2.url t.is(url1, url2, 'Loaded core has same key') const contents = await core2.get(0) t.alike(contents.toString('utf8'), data, 'Got data back from disk') } finally { await sdk.close() } }) test('Support storage reuse by default', async (t) => { const storage = await tmp() const sdk = await create({ storage }) const core = await sdk.get('persist in memory') const key = core.key const data = b4a.from('beep') await core.append(data) await core.close() t.ok(core.closed, 'initial core was closed') const coreAgain = await sdk.get(key) t.alike(await coreAgain.get(0, { wait: false }), data, 'found persisted data') await sdk.close() }) test('Load hypercores by names and urls', async (t) => { const storage = await tmp() const sdk = await create({ storage }) const name = 'example' try { const core = await sdk.get(name) t.ok(core, 'Got core for name') const toTry = [ NULL_KEY, NULL_BUFFER, NULL_HEX_KEY, `hyper://${NULL_KEY}`, `hyper://${NULL_HEX_KEY}` ] for (const key of toTry) { // @ts-ignore const core = await sdk.get(key) t.ok(core, `Got core for ${key}`) t.is(core.url, NULL_URL, 'Correct URL got loaded') } } finally { await sdk.close() } }) test('Loading same key twice results in same core', async (t) => { const storage = await tmp() const sdk = await create({ storage }) const name = 'example' try { const core1 = await sdk.get(name) const core2 = await sdk.get(core1.key) const core3 = await sdk.get(core1.url) t.is(core1, core2, 'Key loaded same core from memory') t.is(core1, core3, 'URL loaded same core from memory') const drive1 = await sdk.getDrive(name) const drive2 = await sdk.getDrive(drive1.key) const drive3 = await sdk.getDrive(drive1.url) t.is(drive1, drive2, 'Key loaded same drive from memory') t.is(drive1, drive3, 'URL loaded same drive from memory') const bee1 = await sdk.getBee(name) const bee2 = await sdk.getBee(bee1.key) const bee3 = await sdk.getBee(bee1.url) t.is(bee1, bee2, 'Key loaded same bee from memory') t.is(bee1, bee3, 'URL loaded same bee from memory') await core1.close() await drive1.close() const core4 = await sdk.get(name) t.not(core1, core4, 'New core after close') const drive4 = await sdk.getDrive(name) t.not(drive1, drive4, 'New drive after close') const bee4 = await sdk.getBee(name) t.not(bee1, bee4, 'New bee after close') } finally { await sdk.close() } }) test('Resolve DNS entries to keys', async (t) => { const storage = await tmp() const expected = NULL_KEY const sdk = await create({ storage }) try { const resolved = await sdk.resolveDNSToKey('example.mauve.moe') t.is(resolved, expected, 'Resolved to correct key') } finally { await sdk.close() } }) test('Resolve DNS in hyper URLs', async (t) => { const storage = await tmp() const expected = NULL_KEY const sdk = await create({ storage }) try { const core = await sdk.get('hyper://example.mauve.moe') t.is(core.id, expected, 'Loaded correct core from DNSLink') } finally { await sdk.close() } }) test('Get hostname from cache when fetch fails', async (t) => { const storage = await tmp() const expected = NULL_KEY const fetch = globalThis.fetch || (await import('bare-fetch')).default let isFirst = true let hasFailed = false /** @type {typeof globalThis['fetch']} */ function testFetch (...args) { if (isFirst) { isFirst = false return fetch(...args) } hasFailed = true throw new Error('Simulated Network Fail') } let sdk = await create({ fetch: testFetch, storage }) try { const resolved = await sdk.resolveDNSToKey('example.mauve.moe') t.is(resolved, expected, 'Resolved to correct key') await sdk.close() console.log('close') sdk = await create({ fetch: testFetch, storage }) const resolved2 = await sdk.resolveDNSToKey('example.mauve.moe') t.is(resolved2, expected, 'Resolved to correct key, without network') t.is(hasFailed, true, 'Fetch was called and failed') } finally { await sdk.close() } }) test('Load a core between two peers', async (t) => { const storage1 = await tmp() const storage2 = await tmp() const sdk1 = await create({ storage: storage1 }) const sdk2 = await create({ storage: storage2 }) try { t.comment('Initializing core on first peer') const core1 = await sdk1.get('example') await core1.append('Hello World!') t.comment('Loading core on second peer') const core2 = await sdk2.get(core1.url) t.ok(core2.peers?.length, 'Found peer') t.is(core2.url, core1.url, 'Got expected URL') t.is(core2.length, 1, 'Not empty') const data = await core2.get(0) t.alike(data, Buffer.from('Hello World!'), 'Got block back out') } finally { await Promise.all([ sdk1.close(), sdk2.close() ]) } }) test('Connect directly between two peers', async (t) => { const storage1 = await tmp() const storage2 = await tmp() const sdk1 = await create({ storage: storage1 }) const sdk2 = await create({ storage: storage2 }) const onPeer = once(sdk2, 'peer-add') const onPeernt = once(sdk2, 'peer-remove') try { await sdk1.joinPeer(sdk2.publicKey) const [peerInfo] = await onPeer t.alike(peerInfo.publicKey, sdk1.publicKey, 'Connected to peer') } finally { await Promise.all([ sdk1.close(), sdk2.close() ]) } await onPeernt t.pass('Peer remove event detected') }) test('Get a hyperdrive and share a file', async (t) => { const storage1 = await tmp() const storage2 = await tmp() const sdk1 = await create({ storage: storage1 }) const sdk2 = await create({ storage: storage2 }) try { const drive1 = await sdk1.getDrive('example') const ws = drive1.createWriteStream('/blob.txt') const onWrote = once(ws, 'close') ws.write('Hello, ') ws.write('world!') ws.end() await onWrote const drive2 = await sdk2.getDrive(drive1.url) t.is(drive2.url, drive1.url, 'Loaded drive has same URL') const rs = drive2.createReadStream('/blob.txt') let data = '' for await (const chunk of rs) { data += chunk.toString('utf8') } t.is(data, 'Hello, world!', 'Loaded expected data') } finally { await Promise.all([ sdk1.close(), sdk2.close() ]) } }) test('Get a hyperbee and share a key value pair', async (t) => { const storage1 = await tmp() const storage2 = await tmp() const sdk1 = await create({ storage: storage1 }) const sdk2 = await create({ storage: storage2 }) try { const encodingOpts = { keyEncoding: 'utf-8', valueEncoding: 'utf-8' } // @ts-ignore const db1 = /** @type {Hyperbee} */ (await sdk1.getBee('example', encodingOpts)) await db1.put('hello', 'world') // @ts-ignore const db2 = /** @type {Hyperbee} */ (await sdk2.getBee(db1.url, encodingOpts)) t.is(db2.url, db1.url, 'Loaded bee has same URL') t.is(db2.version, db1.version, 'Loaded bee has same version') const gotValue = await db2.get('hello') if (!gotValue) return t.fail('unable to get value for key') const { value } = gotValue t.is(value, 'world', 'Got value for key') } finally { await Promise.all([ sdk1.close(), sdk2.close() ]) } }) // test('', async (t) => {}) ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "es2022", "module": "nodenext", "checkJs": true, "allowJs": true, "declaration": true, "emitDeclarationOnly": true, "moduleResolution": "nodenext", "outDir": "dist", }, "extends": "@tsconfig/node20/tsconfig.json", "files": ["./index.js","./test.js"], "include": ["./index.js","./test.js", "types/*.d.ts"], "exclude": ["node_modules", "index.d.ts"], } ================================================ FILE: types/corestore.d.ts ================================================ declare module "corestore" { import type { Connection } from "hyperswarm"; import type { CoreOpts } from "hypercore"; import type Hypercore, { Key, KeyPair } from "hypercore"; import type RocksDB from "rocksdb-native"; import { Readable } from "streamx"; interface CoreStoreOpts { writable?: boolean; readOnly?: boolean; primaryKey?: Key; unsafe?: boolean; } type GetOpts = CoreOpts | { name: string } | { key: Key }; export default class CoreStore { constructor(storage: string | RocksDB, opts: CoreStoreOpts?); get(opts: GetOpts): Hypercore; ready(): Promise; close(): Promise; namespace(namespace: string): CoreStore; session(): CoreStore; list(namespace?: string): Readable; watch(cb: (core: Hypercore) => void); unwatch(cb: (core: Hypercore) => void); suspend(): Promise; resume(): Promise; createKeyPair(name: string): KeyPair; replicate(connection: Connection); } } ================================================ FILE: types/hyperbee.d.ts ================================================ declare module "hyperbee" { import type { EncodingType } from "hypercore"; import type Hypercore, { Key } from "hypercore"; import { EventEmitter } from "events"; interface BeeOpts { keyEncoding?: EncodingType; valueEncoding?: EncodingType; } interface Entry { readonly seq: number; readonly key: Key; readonly value: Value; } type HistoryEntry = Entry & { readonly type: "put" | "del"; }; interface Range { gt?: Key; gte?: Key; lt?: Key; lte?: Key; } interface BatchOptions { cas?(prev: Entry, next: Entry): boolean; } type DBWatcher = AsyncIterable< Hyperbee & { close(): Never } > & { ready(): Promise; close(): Promise; }; type KeyWatcher = EventEmitter<{ update: [] }> & { readonly node: Entry; close(): Promise; }; interface SubOpts { sep?: Buffer; valueEncoding?: EncodingType; keyEncoding?: EncodingType; } interface ReadStreamOpts { reverse?: boolean; limit?: number; } type HistoryOpts = Range & ReadStreamOpts & { live?: boolean; }; interface Batch { put(key: Key, [value]?: Value, [options]?: BatchOptions): Promise; get(key: Key): Promise | null>; del(key: Key, [options]?: BatchOptions): Promise; flush(): Promise; close(): Promise; } export default class Hyperbee< Key = Buffer, Value = Buffer > extends EventEmitter { constructor(core: Hypercore, opts?: BeeOpts); readonly url: string; readonly id: string; readonly writable: boolean; readonly readable: boolean; readonly key: Key; readonly discoveryKey: Key; readonly version: number; readonly core: Hypercore; ready(): Promise; put(key: Key, [value]?: Value, [options]?: BatchOptions): Promise; get(key: Key): Promise | null>; del(key: Key, [options]?: BatchOptions): Promise; batch(): Batch; getBySeq(seq: number, options?: {}): Promise | null>; createReadStream( range?: Range, options?: ReadStreamOpts ): AsyncIterable>; peek( range?: Range, options?: ReadStreamOpts ): Promise>; createHistoryStream( options?: HistoryOpts ): AsyncIterable>; createDiffStream( otherVersion: Hyperbee, options?: Range ): AsyncIterable<{ left: Entry | null; right: Entry | null; }>; getAndWatch(key: Key, options?: {}): Promise>; watch(range?: Range): DBWatcher; checkout(version: number): Hyperbee; snapshot(): Hyperbee; sub(prefix: string, options?: SubOpts): Hyperbee; close(): Promise; } } ================================================ FILE: types/hypercore-crypto.d.ts ================================================ declare module "hypercore-crypto" { import type {KeyPair} from "hypercore-crypto"; interface KeyPair { publicKey: Buffer; } export default { namespace(name: string, count: number[] | number) : Buffer[] } } ================================================ FILE: types/hypercore.d.ts ================================================ declare module "hypercore" { import { EventEmitter } from "node:events"; import type RocksDB from "rocksdb-native"; type Key = Buffer | Uint8Array; interface KeyPair { publicKey: Key; secretKey: Key; } type EncodingType = "json" | "utf-8" | "binary"; type Stringable = string | { toString(): string }; // Plain JSON object based on this stack overflow answer // https://stackoverflow.com/a/59647842 type Primitive = bigint | boolean | null | number | string; type JSONValue = Primitive | JSONObject | JSONArray; interface JSONObject { [key: string]: JSONValue; } interface JSONArray extends Array {} interface CoreOpts { valueEncoding?: EncodingType; keyPair?: KeyPair; encryption?: { key: Key }; timeout?: number; writable?: boolean; inflightRange?: [number, number]; userData?: { [key: string]: any }; key?: Key; } interface GetOpts { wait?: boolean; onwait?: () => void; timeout?: number; activeRequests?: any[]; valueEncoding?: EncodingType; decrypt?: boolean; raw?: boolean; } interface Peer { remotePublicKey: Key; readonly paused: boolean; readonly removed: boolean; readonly extensions: Map } interface Extension { send(message: Encoding, peer: Peer): void; broadcast(message: Encoding): void; destroy(): void; } interface ExtensionOpts { onmessage: (message: Encoding, peer: Peer) => void; } interface HypercoreEvents { close: []; ready: []; append: []; "peer-add": [peer: Peer]; "peer-remove": [peer: Peer]; upload: [index: number, byteLength: number, peer: Peer]; download: [index: number, byteLength: number, peer: Peer]; } export default class Hypercore< DataType = string | Buffer | Uint8Array > extends EventEmitter { constructor(storage: string | RocksDB, opts?: CoreOpts); readonly key: Buffer; readonly url: string; readonly id: string; readonly discoveryKey: Buffer; discovery: object; readonly writable: boolean; readonly length: number; readonly closed: boolean; readonly peers: Peer[]; readonly extensions: Map findingPeers(): () => void; ready(): Promise; update(options?: { wait?: false; activeRequests?: any[]; force?: false; }): Promise; close(options?: { error?: Error }): Promise; get(index: number, opts?: GetOpts): Promise; append( data: DataType | DataType[], options?: { writable?: boolean } ): Promise; registerExtension( name: string, extensionOpts: ExtensionOpts & { encoding: "utf-8" } ): Extension; registerExtension( name: string, extensionOpts: ExtensionOpts & { encoding?: "buffer"; } ): Extension; registerExtension( name: string, extensionOpts: ExtensionOpts & { encoding: "json" } ): Extension; } } ================================================ FILE: types/hyperdrive.d.ts ================================================ declare module "hyperdrive" { import { EventEmitter } from "node:stream"; import type Hypercore from "hypercore"; import type Hyperbee from "hyperbee"; import type CoreStore from "corestore"; interface DriveOpts { key?: Buffer | Uint8Array; } interface Entry { seq: number; key: string; value: { executable: boolean; linkname: null | string; blob: { blockOffset: number; blockLength: number; byteOffset: number; byteLength: number; }; metadata: Metadata | null; }; } type Metadata = { [key: string]: any }; interface WriteOptions { executable?: boolean; metadata: Metadata; } interface ReadOptions { wait?: boolean; timeout?: number; start?: number; end?: number; length?: number; } interface ListOptions { recursive?: boolean; ignore?: string | string[]; wait?: boolean; } type EntryOptions = ReadOptions & { follow?: boolean; }; interface Diff { left: Entry; right: Entry; } interface Download { done(): Promise; destroy(): void; } interface HypedriveEvents { close: []; } type UncloseableDrive = Hyperdrive & { close(): never; }; interface MirrorDriveOptions { prefix?: string; dryRun?: boolean; prune?: boolean; includeEquals?: boolean; filter: (key: string) => boolean; batch?: boolean; ignore?: string | string[]; } interface MirrorEvent { op: "add"; key: string; bytesRemoved: number; bytesAdded: number; } type MirrorDrive = AsyncIterable & { readonly count: number; done: Promise; }; export default class Hyperdrive extends EventEmitter { readonly url: string; readonly id: string; readonly writable: boolean; readonly readable: boolean; readonly key: Key; readonly discoveryKey: Key; readonly version: number; readonly supportsMetadata: true; readonly core: Hypercore; readonly corestore: CoreStore; readonly db: Hyperbee; constructor( store: CoreStore, key: Buffer | Uint8Array | null, opts?: DriveOpts ); ready(): Promise; close(): Promise; put( path: string, buffer: Uint8Array, options?: WriteOptions ): Promise; get(path: string, options?: ReadOptions): Promise; entry(path: string, options?: EntryOptions): Promise; exists(path: string): Promise; del(path: string): Promise; compare(entryA: Entry, entryB: Entry): number; clear(path: string, options?: { diff?: boolean }): Promise; clearAll(options?: { diff?: boolean }): Promise; truncate( version: number, options?: { blobs?: number } ): Promise; purge(): Promise; symlink(path: string, linkname: string): Promise; batch(): Hyperdrive & { flush(): Promise }; list(folder: string, options?: ListOptions): AsyncIterable; readdir( folder: string, options?: { wait?: boolean } ): AsyncIterable; has(path: string): Promise; entries: Hyperbee["createReadStream"]; mirror(out: Hyperdrive, options?: MirrorDriveOptions): MirrorDrive; watch(folder?: string): AsyncIterable<[UncloseableDrive, UncloseableDrive]>; ready(): Promise; destroy(): void; createReadStream(path: string, options?: ReadOptions): any; createWriteStream(path: string, options?: WriteOptions): any; download(folder: string, options?: ListOptions): Download; checkout(version: number): Hyperdrive; diff(version: number, folder: string, options?: any): AsyncIterable; downloadDiff(version: number, folder: string, options?: any): Download; downloadRange(dbRanges: any, blobRanges: any): Download; findingPeers(): () => void; update(options?: { wait: boolean }): Promise; } } ================================================ FILE: types/hyperswarm.d.ts ================================================ declare module "hyperswarm" { import { EventEmitter } from "stream"; import { Duplex } from "streamx"; type Key = Buffer | Uint8Array; interface KeyPair { publicKey: Key; secretKey: Key; } interface SwarmOpts { keyPair?: KeyPair; seed?: Key; maxKeys?: number; firewall?: (remotePublicKey: Key) => boolean; } type Connection = Duplex; interface PeerInfo { readonly publicKey: Key; readonly topics: Key[]; readonly prioritized: boolean; ban(banStatus: boolean): void; } interface JoinOpts { server?: boolean; client?: boolean; } interface PeerDiscovery { flushed(): Promise; destroy(): Promise; refresh(joinOpts: JoinOpts): Promise; } interface SwarmEvents { close: []; connection: [connection: Connection, peer: PeerInfo]; update: []; ban: [peerInfo: PeerInfo, err: Error]; } export default class Hyperswarm extends EventEmitter { readonly keyPair: KeyPair; readonly connections: Connection[]; readonly peers: Map; constructor(opts?: SwarmOpts); flush(): Promise; join(topic: Key, joinOpts?: JoinOpts): PeerDiscovery; leave(topic: Key): Promise; joinPeer(topic: Key): void; leavePeer(topic: Key): void; destroy(): Promise; listen(): Promise; suspend(): Promise; resume(): Promise; } } ================================================ FILE: types/rocksdb-native.d.ts ================================================ declare module "rocksdb-native" { import type { Encoder } from "compact-encoding"; interface SessionOpts { valueEncoding?: Encoder; keyEncoding?: Encoder; columnFamily?: string; readOnly?: boolean; } interface IteratorOpts { gt?: Key; gte?: Key; lt?: Key; lte?: Key; reverse?: boolean; limit?: number; values?: boolean; keys?: boolean; } interface Entry { key: Key; value: Value; } /** * RocksDB-Native Instance. * **WARNING**: This is a stub used in hyper-sdk. * Most methods are not yet documented */ export default class RocksDB { constructor(storageLocation: string, options?: SessionOpts); put(key: Key, value: Value): Promise; get(key: Key): Promise; delete(key: Key): Promise; flush(): Promise; close(): Promise; session( options?: SessionOpts ): RocksDB; iterator( range?: IteratorOpts, options?: IteratorOpts ): AsyncIterable>; } } ================================================ FILE: types/test-tmp.d.ts ================================================ declare module "test-tmp" { export default function testTMP(): Promise } ================================================ FILE: types/z32.d.ts ================================================ declare module "z32" { export function decode(encoded: string) : Buffer }