From 7cecc5e91370a018d8eafa9f85dcd373236b1109 Mon Sep 17 00:00:00 2001
From: chen <23054115+cwkang1998@users.noreply.github.com>
Date: Thu, 7 Nov 2024 16:05:31 +0700
Subject: [PATCH] feat: add grid collision example
Add a new example with grid 2d where collision is implemented.
This utilizes the `resolveConflicts` CRO function for conflict resolution
on the user position.
This also reveals an issue with the `this` context within the `object`
package when `resolveConflicts` is callback from `TopologyObject`.
---
examples/grid-collision/README.md | 18 ++
examples/grid-collision/index.html | 42 +++
examples/grid-collision/package.json | 35 +++
examples/grid-collision/src/index.ts | 269 ++++++++++++++++++++
examples/grid-collision/src/objects/grid.ts | 165 ++++++++++++
examples/grid-collision/src/util/color.ts | 70 +++++
examples/grid-collision/tsconfig.json | 14 +
examples/grid-collision/vite.config.mts | 20 ++
pnpm-lock.yaml | 144 +++++++++++
9 files changed, 777 insertions(+)
create mode 100644 examples/grid-collision/README.md
create mode 100644 examples/grid-collision/index.html
create mode 100644 examples/grid-collision/package.json
create mode 100644 examples/grid-collision/src/index.ts
create mode 100644 examples/grid-collision/src/objects/grid.ts
create mode 100644 examples/grid-collision/src/util/color.ts
create mode 100644 examples/grid-collision/tsconfig.json
create mode 100644 examples/grid-collision/vite.config.mts
diff --git a/examples/grid-collision/README.md b/examples/grid-collision/README.md
new file mode 100644
index 00000000..6b2d3766
--- /dev/null
+++ b/examples/grid-collision/README.md
@@ -0,0 +1,18 @@
+# Topology Protocol Collision Example
+
+This is an example that uses Topology Protocol to implement a 2D grid space where users appear to be circles and can move around the integer grid one grid at a time. We additionally implement collision logic into this example so that no 2 circles can be on one grid at a time.
+
+## Specifics
+
+The Grid CRO has a mapping from user id (node id concacenated with a randomly assigned color string) to the user's position on the grid. The CRO leverages the underlying hash graph for conflict-free consistency. The mergeCallback function receives the linearised operations returned from the underlying hash graph, and recomputes the user-position mapping from those operations.
+
+The `resolveConflict` function is additionally used to implement compensation techniques in order to ensure no 2 node can be on the same circle at a time.
+
+## How to run locally
+
+After cloning the repository, run the following commands:
+
+```bash
+cd ts-topology/examples/grid-collision
+pnpm dev
+```
diff --git a/examples/grid-collision/index.html b/examples/grid-collision/index.html
new file mode 100644
index 00000000..c43df353
--- /dev/null
+++ b/examples/grid-collision/index.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Topology - Grid
+
+
+
+
A 2D grid made with CRO
+
Your Peer ID:
+
Peers on dRAM:
+
Discovery Peers:
+
+
Spawn a new Grid CRO
+
|
+
+
Connect to existing Grid CRO
+
+ Connected to Grid CRO ID:
+
+ Copy
+
+
Peers in CRO:
+
+
+
+
+
+
+
+
+
diff --git a/examples/grid-collision/package.json b/examples/grid-collision/package.json
new file mode 100644
index 00000000..7c2c59b9
--- /dev/null
+++ b/examples/grid-collision/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "topology-example-grid-collision",
+ "version": "0.2.1-0",
+ "description": "Topology Protocol Grid Example",
+ "main": "src/index.ts",
+ "repository": "https://github.com/topology-foundation/ts-topology.git",
+ "license": "MIT",
+ "scripts": {
+ "build": "vite build",
+ "clean": "rm -rf dist/ node_modules/",
+ "dev": "vite serve",
+ "start": "ts-node ./src/index.ts"
+ },
+ "dependencies": {
+ "@topology-foundation/network": "0.3.0",
+ "@topology-foundation/node": "0.3.0",
+ "@topology-foundation/object": "0.3.0",
+ "assemblyscript": "^0.27.29",
+ "crypto-browserify": "^3.12.0",
+ "memfs": "^4.11.1",
+ "process": "^0.11.10",
+ "react-spring": "^9.7.4",
+ "stream-browserify": "^3.0.0",
+ "ts-node": "^10.9.2",
+ "uint8arrays": "^5.1.0",
+ "vm-browserify": "^1.1.2"
+ },
+ "devDependencies": {
+ "@types/node": "^22.5.4",
+ "ts-loader": "^9.5.1",
+ "typescript": "^5.5.4",
+ "vite": "^5.4.9",
+ "vite-plugin-node-polyfills": "^0.22.0"
+ }
+}
diff --git a/examples/grid-collision/src/index.ts b/examples/grid-collision/src/index.ts
new file mode 100644
index 00000000..d8c84e69
--- /dev/null
+++ b/examples/grid-collision/src/index.ts
@@ -0,0 +1,269 @@
+import { TopologyNode } from "@topology-foundation/node";
+import type { TopologyObject } from "@topology-foundation/object";
+import { Grid } from "./objects/grid";
+import { hslToRgb, rgbToHex, rgbToHsl } from "./util/color";
+
+const node = new TopologyNode();
+let topologyObject: TopologyObject;
+let gridCRO: Grid;
+let peers: string[] = [];
+let discoveryPeers: string[] = [];
+let objectPeers: string[] = [];
+
+const formatNodeId = (id: string): string => {
+ return `${id.slice(0, 4)}...${id.slice(-4)}`;
+};
+
+const colorMap: Map = new Map();
+
+const hashCode = (str: string): number => {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = (hash << 5) - hash + str.charCodeAt(i);
+ hash |= 0; // Convert to 32bit integer
+ }
+ return hash;
+};
+
+const getColorForNodeId = (id: string): string => {
+ if (!colorMap.has(id)) {
+ const hash = hashCode(id);
+ let r = (hash & 0xff0000) >> 16;
+ let g = (hash & 0x00ff00) >> 8;
+ let b = hash & 0x0000ff;
+
+ // Convert to HSL and adjust lightness to be below 50%
+ let [h, s, l] = rgbToHsl(r, g, b);
+ l = l * 0.5; // Set lightness to below 50%
+
+ // Convert back to RGB
+ [r, g, b] = hslToRgb(h, s, l);
+ const color = rgbToHex(r, g, b); // Convert RGB to hex
+ colorMap.set(id, color);
+ }
+ return colorMap.get(id) || "#000000";
+};
+
+const render = () => {
+ if (topologyObject) {
+ const gridIdElement = document.getElementById("gridId");
+ gridIdElement.innerText = topologyObject.id;
+ const copyGridIdButton = document.getElementById("copyGridId");
+ if (copyGridIdButton) {
+ copyGridIdButton.style.display = "inline"; // Show the button
+ }
+ } else {
+ const copyGridIdButton = document.getElementById("copyGridId");
+ if (copyGridIdButton) {
+ copyGridIdButton.style.display = "none"; // Hide the button
+ }
+ }
+
+ const element_peerId = document.getElementById("peerId");
+ element_peerId.innerHTML = `${formatNodeId(node.networkNode.peerId)} `;
+
+ const element_peers = document.getElementById("peers");
+ element_peers.innerHTML = `[${peers.map((peer) => `${formatNodeId(peer)} `).join(", ")}]`;
+
+ const element_discoveryPeers = (
+ document.getElementById("discoveryPeers")
+ );
+ element_discoveryPeers.innerHTML = `[${discoveryPeers.map((peer) => `${formatNodeId(peer)} `).join(", ")}]`;
+
+ const element_objectPeers = (
+ document.getElementById("objectPeers")
+ );
+ element_objectPeers.innerHTML = `[${objectPeers.map((peer) => `${formatNodeId(peer)} `).join(", ")}]`;
+
+ if (!gridCRO) return;
+ const users = gridCRO.getUsers();
+ const element_grid = document.getElementById("grid");
+ element_grid.innerHTML = "";
+
+ const gridWidth = element_grid.clientWidth;
+ const gridHeight = element_grid.clientHeight;
+ const centerX = Math.floor(gridWidth / 2);
+ const centerY = Math.floor(gridHeight / 2);
+
+ // Draw grid lines
+ const numLinesX = Math.floor(gridWidth / 50);
+ const numLinesY = Math.floor(gridHeight / 50);
+
+ for (let i = -numLinesX; i <= numLinesX; i++) {
+ const line = document.createElement("div");
+ line.style.position = "absolute";
+ line.style.left = `${centerX + i * 50}px`;
+ line.style.top = "0";
+ line.style.width = "1px";
+ line.style.height = "100%";
+ line.style.backgroundColor = "lightgray";
+ element_grid.appendChild(line);
+ }
+
+ for (let i = -numLinesY; i <= numLinesY; i++) {
+ const line = document.createElement("div");
+ line.style.position = "absolute";
+ line.style.left = "0";
+ line.style.top = `${centerY + i * 50}px`;
+ line.style.width = "100%";
+ line.style.height = "1px";
+ line.style.backgroundColor = "lightgray";
+ element_grid.appendChild(line);
+ }
+
+ for (const userColorString of users) {
+ const [id, color] = userColorString.split(":");
+ const position = gridCRO.getUserPosition(userColorString);
+
+ if (position) {
+ const div = document.createElement("div");
+ div.style.position = "absolute";
+ div.style.left = `${centerX + position.x * 50 + 5}px`; // Center the circle
+ div.style.top = `${centerY - position.y * 50 + 5}px`; // Center the circle
+ if (id === node.networkNode.peerId) {
+ div.style.width = `${34}px`;
+ div.style.height = `${34}px`;
+ } else {
+ div.style.width = `${34 + 6}px`;
+ div.style.height = `${34 + 6}px`;
+ }
+ div.style.backgroundColor = color;
+ div.style.borderRadius = "50%";
+ div.style.transition = "background-color 1s ease-in-out";
+ div.style.animation = `glow-${id} 0.5s infinite alternate`;
+
+ // Add black border for the current user's circle
+ if (id === node.networkNode.peerId) {
+ div.style.border = "3px solid black";
+ }
+
+ // Create dynamic keyframes for the glow effect
+ const style = document.createElement("style");
+ style.innerHTML = `
+ @keyframes glow-${id} {
+ 0% {
+ background-color: ${hexToRgba(color, 0.5)};
+ }
+ 100% {
+ background-color: ${hexToRgba(color, 1)};
+ }
+ }`;
+ document.head.appendChild(style);
+
+ element_grid.appendChild(div);
+ }
+ }
+};
+
+// Helper function to convert hex color to rgba
+function hexToRgba(hex: string, alpha: number) {
+ const bigint = Number.parseInt(hex.slice(1), 16);
+ const r = (bigint >> 16) & 255;
+ const g = (bigint >> 8) & 255;
+ const b = bigint & 255;
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+}
+
+async function addUser() {
+ if (!gridCRO) {
+ console.error("Grid CRO not initialized");
+ alert("Please create or join a grid first");
+ return;
+ }
+
+ gridCRO.addUser(
+ node.networkNode.peerId,
+ getColorForNodeId(node.networkNode.peerId),
+ );
+ render();
+}
+
+async function moveUser(direction: string) {
+ if (!gridCRO) {
+ console.error("Grid CRO not initialized");
+ alert("Please create or join a grid first");
+ return;
+ }
+
+ gridCRO.moveUser(node.networkNode.peerId, direction);
+ render();
+}
+
+async function createConnectHandlers() {
+ node.addCustomGroupMessageHandler(topologyObject.id, (e) => {
+ if (topologyObject)
+ objectPeers = node.networkNode.getGroupPeers(topologyObject.id);
+ render();
+ });
+
+ node.objectStore.subscribe(topologyObject.id, (_, obj) => {
+ render();
+ });
+}
+
+async function main() {
+ await node.start();
+ render();
+
+ node.addCustomGroupMessageHandler("", (e) => {
+ peers = node.networkNode.getAllPeers();
+ discoveryPeers = node.networkNode.getGroupPeers("topology::discovery");
+ render();
+ });
+
+ const button_create = (
+ document.getElementById("createGrid")
+ );
+ button_create.addEventListener("click", async () => {
+ topologyObject = await node.createObject(new Grid());
+ gridCRO = topologyObject.cro as Grid;
+ createConnectHandlers();
+ await addUser();
+ render();
+ });
+
+ const button_connect = document.getElementById("joinGrid");
+ button_connect.addEventListener("click", async () => {
+ const croId = (document.getElementById("gridInput"))
+ .value;
+ try {
+ topologyObject = await node.createObject(
+ new Grid(),
+ croId,
+ undefined,
+ true,
+ );
+ gridCRO = topologyObject.cro as Grid;
+ createConnectHandlers();
+ await addUser();
+ render();
+ console.log("Succeeded in connecting with CRO", croId);
+ } catch (e) {
+ console.error("Error while connecting with CRO", croId, e);
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "w") moveUser("U");
+ if (event.key === "a") moveUser("L");
+ if (event.key === "s") moveUser("D");
+ if (event.key === "d") moveUser("R");
+ });
+
+ const copyButton = document.getElementById("copyGridId");
+ copyButton.addEventListener("click", () => {
+ const gridIdText = (document.getElementById("gridId"))
+ .innerText;
+ navigator.clipboard
+ .writeText(gridIdText)
+ .then(() => {
+ // alert("Grid CRO ID copied to clipboard!");
+ console.log("Grid CRO ID copied to clipboard");
+ })
+ .catch((err) => {
+ console.error("Failed to copy: ", err);
+ });
+ });
+}
+
+main();
diff --git a/examples/grid-collision/src/objects/grid.ts b/examples/grid-collision/src/objects/grid.ts
new file mode 100644
index 00000000..aa9fba56
--- /dev/null
+++ b/examples/grid-collision/src/objects/grid.ts
@@ -0,0 +1,165 @@
+import {
+ ActionType,
+ type CRO,
+ type Operation,
+ type ResolveConflictsType,
+ SemanticsType,
+ type Vertex,
+} from "@topology-foundation/object";
+
+export class Grid implements CRO {
+ operations: string[] = ["addUser", "moveUser"];
+ semanticsType: SemanticsType = SemanticsType.pair;
+ positions: Map;
+
+ constructor() {
+ this.positions = new Map();
+ }
+
+ addUser(userId: string, color: string): void {
+ this._addUser(userId, color);
+ }
+
+ private _addUser(userId: string, color: string): void {
+ const userColorString = `${userId}:${color}`;
+ this.positions.set(userColorString, { x: 0, y: 0 });
+ }
+
+ moveUser(userId: string, direction: string): void {
+ this._moveUser(userId, direction);
+ }
+
+ private _computeNewPosition(
+ pos: { x: number; y: number },
+ direction: string,
+ ): { x: number; y: number } {
+ let deltaY = 0;
+ let deltaX = 0;
+ switch (direction) {
+ case "U":
+ deltaY += 1;
+ break;
+ case "D":
+ deltaY -= 1;
+ break;
+ case "L":
+ deltaX -= 1;
+ break;
+ case "R":
+ deltaX += 1;
+ break;
+ }
+
+ return { x: pos.x + deltaX, y: pos.y + deltaY };
+ }
+
+ private _moveUser(userId: string, direction: string): void {
+ const userColorString = [...this.positions.keys()].find((u) =>
+ u.startsWith(`${userId}:`),
+ );
+ if (userColorString) {
+ const position = this.positions.get(userColorString);
+ if (position) {
+ const newPos = this._computeNewPosition(position, direction);
+ this.positions.set(userColorString, newPos);
+ }
+ }
+ }
+
+ getUsers(): string[] {
+ return [...this.positions.keys()];
+ }
+
+ getUserPosition(
+ userColorString: string,
+ ): { x: number; y: number } | undefined {
+ const position = this.positions.get(userColorString);
+ if (position) {
+ return position;
+ }
+ return undefined;
+ }
+
+ resolveConflicts(vertices: Vertex[]): ResolveConflictsType {
+ // Here we implement compensation for the location.
+ // As we operate based on pairwise comparison, there's always only 2 elements.
+ // First the vertices must be available, and also not of the same node.
+ if (vertices.length === 2 && vertices[0].nodeId !== vertices[1].nodeId) {
+ const leftVertex = vertices[0];
+ const rightVertex = vertices[1];
+ const leftVertexPosition = leftVertex.operation
+ ? this.getUserPosition(":".concat(leftVertex.operation.value))
+ : undefined;
+ const rightVertexPosition = rightVertex.operation
+ ? this.getUserPosition(":".concat(rightVertex.operation.value))
+ : undefined;
+ console.log(vertices);
+ // Let's first handle adding a new user
+ if (
+ leftVertex.operation?.type === "addUser" &&
+ rightVertex.operation?.type === "addUser"
+ ) {
+ // This basically tells the cro to accept only the ones that comes first.
+ if (leftVertexPosition) {
+ return { action: ActionType.DropRight };
+ }
+ return { action: ActionType.DropLeft };
+ }
+
+ // Now handle moving the user
+ if (
+ leftVertex.operation?.type === "moveUser" &&
+ rightVertex.operation?.type === "moveUser" &&
+ leftVertexPosition &&
+ rightVertexPosition
+ ) {
+ const leftVertexNextPosition = this._computeNewPosition(
+ leftVertexPosition,
+ leftVertex.operation.value[1],
+ );
+ const rightVertexNextPosition = this._computeNewPosition(
+ rightVertexPosition,
+ rightVertex.operation.value[1],
+ );
+
+ // If they are going to colide, do nothing so they don't move and thus do not colide.
+ if (
+ leftVertexNextPosition.x === rightVertexNextPosition.x &&
+ leftVertexNextPosition.y === rightVertexNextPosition.y
+ ) {
+ return { action: ActionType.Drop };
+ }
+ }
+ }
+
+ // If none of the operations match our criteria, they are concurrent
+ // safe, and thus we don't need to do anything.
+ return { action: ActionType.Nop };
+ }
+
+ mergeCallback(operations: Operation[]): void {
+ // reset this.positions
+ this.positions = new Map();
+
+ // apply operations to this.positions
+ for (const op of operations) {
+ if (!op.value) continue;
+ switch (op.type) {
+ case "addUser": {
+ const [userId, color] = op.value;
+ this._addUser(userId, color);
+ break;
+ }
+ case "moveUser": {
+ const [userId, direction] = op.value;
+ this._moveUser(userId, direction);
+ break;
+ }
+ }
+ }
+ }
+}
+
+export function createGrid(): Grid {
+ return new Grid();
+}
diff --git a/examples/grid-collision/src/util/color.ts b/examples/grid-collision/src/util/color.ts
new file mode 100644
index 00000000..a5243339
--- /dev/null
+++ b/examples/grid-collision/src/util/color.ts
@@ -0,0 +1,70 @@
+export const rgbToHsl = (
+ rInt: number,
+ gInt: number,
+ bInt: number,
+): [number, number, number] => {
+ const r = rInt / 255;
+ const g = gInt / 255;
+ const b = bInt / 255;
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ let h = 0;
+ let s: number;
+ const l = (max + min) / 2; // Initialize h with a default value
+
+ if (max === min) {
+ h = s = 0; // achromatic
+ } else {
+ const chromaticity = max - min;
+ s = l > 0.5 ? chromaticity / (2 - max - min) : chromaticity / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / chromaticity + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / chromaticity + 2;
+ break;
+ case b:
+ h = (r - g) / chromaticity + 4;
+ break;
+ }
+ h /= 6;
+ }
+ return [h * 360, s, l];
+};
+
+export const hslToRgb = (
+ h: number,
+ s: number,
+ l: number,
+): [number, number, number] => {
+ let r: number;
+ let g: number;
+ let b: number;
+
+ if (s === 0) {
+ r = g = b = l; // achromatic
+ } else {
+ const hue2rgb = (p: number, q: number, t_: number) => {
+ let t = t_;
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
+ if (t < 1 / 2) return q;
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+ return p;
+ };
+
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ r = hue2rgb(p, q, h / 360 + 1 / 3);
+ g = hue2rgb(p, q, h / 360);
+ b = hue2rgb(p, q, h / 360 - 1 / 3);
+ }
+
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+};
+
+export const rgbToHex = (r: number, g: number, b: number): string => {
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
+};
diff --git a/examples/grid-collision/tsconfig.json b/examples/grid-collision/tsconfig.json
new file mode 100644
index 00000000..23d99aec
--- /dev/null
+++ b/examples/grid-collision/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "rootDir": ".",
+ "strict": true,
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "allowJs": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ }
+}
diff --git a/examples/grid-collision/vite.config.mts b/examples/grid-collision/vite.config.mts
new file mode 100644
index 00000000..e3518486
--- /dev/null
+++ b/examples/grid-collision/vite.config.mts
@@ -0,0 +1,20 @@
+import path from "node:path";
+import { defineConfig } from "vite";
+import { nodePolyfills } from "vite-plugin-node-polyfills";
+
+export default defineConfig({
+ build: {
+ target: "esnext",
+ },
+ plugins: [nodePolyfills()],
+ optimizeDeps: {
+ esbuildOptions: {
+ target: "esnext",
+ },
+ },
+ resolve: {
+ alias: {
+ "@topology-foundation": path.resolve(__dirname, "../../packages"),
+ },
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 720d8323..8fd15d2e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -195,6 +195,61 @@ importers:
specifier: ^0.22.0
version: 0.22.0(rollup@4.24.4)(vite@6.0.0(@types/node@22.9.0)(terser@5.36.0)(tsx@4.19.1)(yaml@2.6.0))
+ examples/grid-collision:
+ dependencies:
+ '@topology-foundation/network':
+ specifier: 0.3.0
+ version: 0.3.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))
+ '@topology-foundation/node':
+ specifier: 0.3.0
+ version: 0.3.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))
+ '@topology-foundation/object':
+ specifier: 0.3.0
+ version: 0.3.0
+ assemblyscript:
+ specifier: ^0.27.29
+ version: 0.27.30
+ crypto-browserify:
+ specifier: ^3.12.0
+ version: 3.12.1
+ memfs:
+ specifier: ^4.11.1
+ version: 4.14.0
+ process:
+ specifier: ^0.11.10
+ version: 0.11.10
+ react-spring:
+ specifier: ^9.7.4
+ version: 9.7.4(@react-three/fiber@8.17.10(react-dom@18.3.1(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)(three@0.169.0))(konva@9.3.16)(react-dom@18.3.1(react@18.3.1))(react-konva@18.2.10(konva@9.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react-zdog@1.2.2)(react@18.3.1)(three@0.169.0)(zdog@1.1.3)
+ stream-browserify:
+ specifier: ^3.0.0
+ version: 3.0.0
+ ts-node:
+ specifier: ^10.9.2
+ version: 10.9.2(@types/node@22.8.4)(typescript@5.6.3)
+ uint8arrays:
+ specifier: ^5.1.0
+ version: 5.1.0
+ vm-browserify:
+ specifier: ^1.1.2
+ version: 1.1.2
+ devDependencies:
+ '@types/node':
+ specifier: ^22.5.4
+ version: 22.8.4
+ ts-loader:
+ specifier: ^9.5.1
+ version: 9.5.1(typescript@5.6.3)(webpack@5.95.0)
+ typescript:
+ specifier: ^5.5.4
+ version: 5.6.3
+ vite:
+ specifier: ^5.4.9
+ version: 5.4.10(@types/node@22.8.4)(terser@5.36.0)
+ vite-plugin-node-polyfills:
+ specifier: ^0.22.0
+ version: 0.22.0(rollup@4.24.3)(vite@5.4.10(@types/node@22.8.4)(terser@5.36.0))
+
examples/local-bootstrap:
dependencies:
'@topology-foundation/blueprints':
@@ -2228,6 +2283,24 @@ packages:
'@tootallnate/quickjs-emscripten@0.23.0':
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
+ '@topology-foundation/blueprints@0.3.0':
+ resolution: {integrity: sha512-Wa3s1jSB2g1qiNc1pcV8nA/MpkmVsgSVayoDYQe9rDysDhza8MLv6sdIGx3GAnYM+8oCgYfpA1mDtWabgGbU3Q==}
+
+ '@topology-foundation/logger@0.3.0':
+ resolution: {integrity: sha512-7TAcFn20ccxx1QvDneOBk+ScZZTQ/Z8BfVY7RBeB76lQPPvPYRh33mrZzi5k+o7fyXhdHJq5Xlxd1ebEloyNnQ==}
+
+ '@topology-foundation/logger@0.3.1':
+ resolution: {integrity: sha512-ABYUFg71dGTvdFmrITv4ZJHY/P9SZzXJqYeIB6jEeVSWrp1oAIdbhKBrqo1GLuKSod0u/YG2a2VDcyuREQWTSA==}
+
+ '@topology-foundation/network@0.3.0':
+ resolution: {integrity: sha512-5+drJJIoMTeFrEarOMbJEmkTt2ET9MCKiVyVbJv8moXLcA6Xr3ZQWSpHMezI9oNiLoe1fmDFEjtDdRgPELFsZw==}
+
+ '@topology-foundation/node@0.3.0':
+ resolution: {integrity: sha512-kXTnErmc65eGJN9slS8912GwOoXUx4GXA2nCz99GGMIhGAP8DV1LzQyFSHBTssGCqkQZTfS7zO0ek3c0XyKb6g==}
+
+ '@topology-foundation/object@0.3.0':
+ resolution: {integrity: sha512-TVIqY9hMHxEWldhzTGmttUmYnThO5YNAtCYT923rXTnsF0x02qcXv6S6yBxdusH1RubXf//7IhLta0kYWk0VXg==}
+
'@tsconfig/node10@1.0.11':
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@@ -7852,6 +7925,77 @@ snapshots:
'@tootallnate/quickjs-emscripten@0.23.0': {}
+ '@topology-foundation/blueprints@0.3.0':
+ dependencies:
+ '@thi.ng/random': 4.1.2
+
+ '@topology-foundation/logger@0.3.0':
+ dependencies:
+ loglevel: 1.9.2
+ loglevel-plugin-prefix: 0.8.4
+
+ '@topology-foundation/logger@0.3.1':
+ dependencies:
+ loglevel: 1.9.2
+ loglevel-plugin-prefix: 0.8.4
+
+ '@topology-foundation/network@0.3.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))':
+ dependencies:
+ '@bufbuild/protobuf': 2.2.1
+ '@chainsafe/libp2p-gossipsub': 14.1.0
+ '@chainsafe/libp2p-noise': 16.0.0
+ '@chainsafe/libp2p-yamux': 7.0.1
+ '@libp2p/autonat': 2.0.10
+ '@libp2p/bootstrap': 11.0.10
+ '@libp2p/circuit-relay-v2': 2.1.5
+ '@libp2p/crypto': 5.0.6
+ '@libp2p/dcutr': 2.0.10
+ '@libp2p/devtools-metrics': 1.1.8
+ '@libp2p/identify': 3.0.10
+ '@libp2p/mdns': 11.0.10
+ '@libp2p/peer-id': 5.0.7
+ '@libp2p/pubsub-peer-discovery': 11.0.0
+ '@libp2p/webrtc': 5.0.16(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))
+ '@libp2p/websockets': 9.0.11
+ '@libp2p/webtransport': 5.0.16
+ '@multiformats/multiaddr': 12.3.1
+ '@topology-foundation/logger': 0.3.1
+ it-length-prefixed: 9.1.0
+ it-map: 3.1.1
+ it-pipe: 3.0.1
+ libp2p: 2.2.1
+ ts-proto: 2.2.5
+ uint8arrays: 5.1.0
+ transitivePeerDependencies:
+ - bufferutil
+ - react-native
+ - supports-color
+ - utf-8-validate
+
+ '@topology-foundation/node@0.3.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))':
+ dependencies:
+ '@chainsafe/libp2p-gossipsub': 14.1.0
+ '@grpc/grpc-js': 1.12.2
+ '@libp2p/interface': 2.2.0
+ '@topology-foundation/blueprints': 0.3.0
+ '@topology-foundation/logger': 0.3.0
+ '@topology-foundation/network': 0.3.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))
+ '@topology-foundation/object': 0.3.0
+ commander: 12.1.0
+ dotenv: 16.4.5
+ google-protobuf: 3.21.4
+ transitivePeerDependencies:
+ - bufferutil
+ - react-native
+ - supports-color
+ - utf-8-validate
+
+ '@topology-foundation/object@0.3.0':
+ dependencies:
+ '@bufbuild/protobuf': 2.2.1
+ '@topology-foundation/logger': 0.3.1
+ ts-proto: 2.2.5
+
'@tsconfig/node10@1.0.11': {}
'@tsconfig/node12@1.0.11': {}