UNPKG

dagify-hyper

Version:

A collection of Dagify nodes for peer-to-peer hyper ecosystem tools.

267 lines (219 loc) 11.2 kB
import {test, solo} from "brittle"; import RAM from "random-access-memory"; import Hypercore from "hypercore"; import {sleep} from "./helpers/sleep.js"; import {hypercoreLatestNode} from "../lib/hypercore/index.js"; import {createNode, createShallowNode, NO_EMIT} from "dagify"; import {hypercoreHeadNode} from "../lib/hypercore/hypercoreHeadNode.js"; import Corestore from "corestore"; import {useCorestore} from "../lib/corestore/index.js"; import {hypercoreNode} from "../lib/hypercore/hypercoreNode.js"; import {hypercoreRangeNode} from "../lib/hypercore/hypercoreRangeNode.js"; import {hypercoreLiveNode} from "../lib/hypercore/hypercoreLiveNode.js"; test("Hypercore latest", async t => { // Initialize a hypercore instance in RAM with utf8 encoding. const hypercore = new Hypercore(RAM, {valueEncoding: "utf8"}); // Create a reactive node that tracks the latest value from the hypercore. const coreNode = hypercoreLatestNode({hypercore}); // Append "hello" to the hypercore and allow a tick for propagation. await hypercore.append("hello"); await sleep(0); // Verify that the reactive node's value has updated to "hello". t.is(coreNode.value, "hello", "coreNode.value should be 'hello' after first append"); // Append "world" to the hypercore and wait for the update to propagate. await hypercore.append("world"); await sleep(0); // Verify that the reactive node's value has updated to "world". t.is(coreNode.value, "world", "coreNode.value should be 'world' after second append"); // Complete the reactive node to trigger cleanup. coreNode.complete(); // Close the hypercore to release resources. await hypercore.close(); // Ensure that no lingering 'append' event listeners remain. t.is(hypercore.listenerCount("append"), 0, "Hypercore should have zero 'append' listeners after cleanup"); }); test("Hypercore latest with offset", async t => { // Initialize a hypercore instance in RAM with utf8 encoding. const hypercore = new Hypercore(RAM, {valueEncoding: "utf8"}); // Create a reactive node that tracks the latest value from the hypercore, // but with an offset of -2. This means the node will only emit a value // when there are at least 2 entries in the core, and it will return the (length - 2)th entry. const coreNode = hypercoreLatestNode({hypercore, offset: -2}); // Append "hello" to the hypercore. Since the core length is now 1, // the offset (-2) cannot be satisfied, so we expect NO_EMIT. await hypercore.append("hello"); await sleep(0); t.ok(coreNode.value === NO_EMIT, "With core length 1, offset -2 yields NO_EMIT"); // Append "world" to the hypercore. The core length is now 2, so the node // should emit the value at index: Math.max(0, 2 + (-2)) = 0, which is "hello". await hypercore.append("world"); await sleep(0); t.is(coreNode.value, "hello", "With core length 2, offset -2 yields the first entry ('hello')"); // Complete the reactive node to trigger cleanup. coreNode.complete(); // Close the hypercore to release resources. await hypercore.close(); // Ensure that no lingering 'append' event listeners remain on the hypercore. t.is(hypercore.listenerCount("append"), 0, "Hypercore should have zero 'append' listeners after cleanup"); }); test("Hypercore latest where hypercore is set late", async t => { // Initialize a hypercore instance in RAM with utf8 encoding. const hypercore = new Hypercore(RAM, {valueEncoding: "utf8"}); // Create a reactive node that tracks the latest value from the hypercore, // but with an offset of -2. This means the node will only emit a value // when there are at least 2 entries in the core, and it will return the (length - 2)th entry. const config = createNode({}); const coreNode = hypercoreLatestNode(config); await sleep(0); t.ok(coreNode.value === NO_EMIT, "Without a core set yet, we should have no emit."); // Append "hello" to the hypercore. Since the core length is now 1, // the offset (-2) cannot be satisfied, so we expect NO_EMIT. await hypercore.append("hello"); await sleep(100); config.update(config => ({...config, hypercore})); // Set hypercore now. await sleep(10); t.ok(coreNode.value, "hello", "With core length 1, offset -2 yields NO_EMIT"); // Complete the reactive node to trigger cleanup. coreNode.complete(); await sleep(); // Ensure that no lingering 'append' event listeners remain on the hypercore. t.is(hypercore.listenerCount("append"), 0, "Hypercore should have zero 'append' listeners after cleanup"); // Close the hypercore to release resources. await hypercore.close(); }); test("Hypercore head node", async t => { const hypercore = new Hypercore(RAM, {valueEncoding: "utf8"}); await hypercore.append("hello"); const head = hypercoreHeadNode({hypercore}); await sleep(); t.is(head.value, "hello", "head node should be 'hello'"); }); test("Hypercore range", async t => { const hypercore = new Hypercore(RAM, {valueEncoding: "utf8"}); await hypercore.append(["hello", "world", "you", "are", "a", "rockstar"]); // Create a range node starting at index 2 (i.e. "you") through the end of the hypercore. const rangeConfig = createNode({start: 2, end: hypercore.length}); const range = hypercoreRangeNode({hypercore, range: rangeConfig}); await sleep(10); // Expect the range node to contain: ["you", "are", "a", "rockstar"] t.alike(range.value, ["you", "are", "a", "rockstar"], "range node should be ['you', 'are', 'a', 'rockstar']"); // Update the configuration to start at index 4 (i.e. "a"). rangeConfig.update(config => ({...config, start: 4})); await sleep(10); // Expect the range node to contain: ["a", "rockstar"] t.alike(range.value, ["a", "rockstar"], "range node should be ['a', 'rockstar']"); rangeConfig.set({start: -3, end: -1}); await sleep(10); t.alike(range.value, ["are", "a"], "negative ranges work"); }); test("Create hypercore live node and hotswapping", async t => { const hypercore = new Hypercore(RAM, {valueEncoding: "utf8"}); const coreNode = createShallowNode(hypercore); const values = []; let completeCalled = false; const liveNode = hypercoreLiveNode({hypercore: coreNode}); liveNode.subscribe({ next: value => { values.push(value); }, complete: () => { completeCalled = true; }, // error: e => t.fail("error occurred") }); await hypercore.append("hello"); await hypercore.append("world"); await hypercore.append("!!!"); await sleep(100); await hypercore.update(); await hypercore.close(); t.alike(values, ["hello", "world", "!!!"]); await sleep(100); const newHypercore = new Hypercore(RAM, {valueEncoding: "utf8"}); coreNode.set(newHypercore); await newHypercore.append(["Hey...", "we're", "calling", "about", "your", "auto", "insurance"]); await sleep(30); liveNode.complete(); await sleep(30); t.ok(completeCalled); t.is(liveNode.streamCount, 0); t.alike(values, ["hello", "world", "!!!", "Hey...", "we're", "calling", "about", "your", "auto", "insurance"]) t.teardown(async () => { await hypercore.close(); await newHypercore.close(); }); }); test("Create hypercore from corestore", async t => { const corestore = new Corestore(RAM); let completeCalled = false; let coresClosed = 0; useCorestore(corestore); const coreNode = hypercoreNode({name: "hello", valueEncoding: "utf8"}); coreNode.skip.subscribe({ next: async core => { await core.append("hello"); await core.append("world"); core.once("close", () => coresClosed++); }, complete: () => { completeCalled = true; } }); const latest = hypercoreLatestNode({hypercore: coreNode}); await sleep(10); t.is(latest.value, "world", "latest node should be 'world'"); coreNode.complete(); latest.complete(); await sleep(); t.ok(completeCalled, "ensure that node completion also completes the subscriptions."); t.is(coresClosed, 1, "Only one core was created and thus closed."); }); test("Create hypercore from corestore with hotswap", async t => { // Create a new corestore instance using an in-memory store. const corestore = new Corestore(RAM); // Track whether the subscription's complete callback has been called. let completeCalled = false; // Track the number of cores that have been closed. let coresClosed = 0; // Array to hold IDs of cores created during the test. let coreIds = []; // Register the corestore for use (assumes a global or contextual binding). useCorestore(corestore); // Create an initial configuration node with a name and value encoding. const config = createNode({ name: "hello", valueEncoding: "utf8" }); // Create a hypercore node using the configuration. const coreNode = hypercoreNode(config); // Subscribe to the skip stream on the coreNode. // This subscription will be called for each new core created. coreNode.skip.subscribe({ // For each new core, store its id, append a message, and register a close listener. next: async core => { coreIds.push(core.id); await core.append(`hello world from this core: ${core.id}`); // Increment the counter when the core is closed. core.once("close", () => coresClosed++); }, // When the subscription completes, set the flag. complete: () => completeCalled = true }); // Create a node that provides access to the latest core value. const latest = hypercoreLatestNode({hypercore: coreNode}); // Wait a bit for the first core to initialize and the message to be appended. await sleep(10); // Check that the latest value matches the expected message from the first core. t.is(latest.value, `hello world from this core: ${coreIds[0]}`, "Initial core value should match appended message."); // Update the config to trigger a hotswap (assumes update triggers a new core creation). config.update(config => ({ ...config, name: "coolBeans" })); // Wait for the new core to be created and processed. await sleep(); // Validate that the latest core now holds the new message from the second core. t.is(latest.value, `hello world from this core: ${coreIds[1]}`, "Updated core value should match new appended message."); // Complete the core node and the latest node to trigger their cleanup routines. coreNode.complete(); latest.complete(); // Wait briefly to ensure all cleanup is processed. await sleep(); // Verify that the complete callback in the subscription was called. t.ok(completeCalled, "Ensure that node completion also completes the subscriptions."); t.is(coresClosed, 2, "Both cores should have been closed. When you close the node responsible for the creation of the hypercore, it closes all of them"); });