diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 00000000..2cc56d55 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,10 @@ +version: v2 +plugins: + - local: ./node_modules/ts-proto/protoc-gen-ts_proto + strategy: directory + out: ./packages + opt: + - esModuleInterop=true + - fileSuffix=_pb +inputs: + - directory: ./packages diff --git a/examples/canvas/src/objects/canvas.ts b/examples/canvas/src/objects/canvas.ts index 708ff1a4..b9cb11cf 100644 --- a/examples/canvas/src/objects/canvas.ts +++ b/examples/canvas/src/objects/canvas.ts @@ -1,7 +1,7 @@ import { TopologyObject } from "@topology-foundation/object"; import { IPixel, Pixel } from "./pixel"; -export interface ICanvas extends TopologyObject { +export interface ICanvas { width: number; height: number; canvas: IPixel[][]; @@ -20,13 +20,12 @@ export interface ICanvas extends TopologyObject { merge(peerCanvas: Canvas): void; } -export class Canvas extends TopologyObject implements ICanvas { +export class Canvas implements TopologyObject, ICanvas { width: number; height: number; canvas: IPixel[][]; constructor(peerId: string, width: number, height: number) { - super(peerId); this.width = width; this.height = height; this.canvas = Array.from(new Array(width), () => diff --git a/examples/canvas/src/objects/pixel.ts b/examples/canvas/src/objects/pixel.ts index 8c5eaf40..f9ec0703 100644 --- a/examples/canvas/src/objects/pixel.ts +++ b/examples/canvas/src/objects/pixel.ts @@ -1,7 +1,6 @@ import { GCounter } from "@topology-foundation/crdt"; -import { TopologyObject } from "@topology-foundation/object"; -export interface IPixel extends TopologyObject { +export interface IPixel { red: GCounter; green: GCounter; blue: GCounter; @@ -11,13 +10,12 @@ export interface IPixel extends TopologyObject { merge(peerPixel: IPixel): void; } -export class Pixel extends TopologyObject implements IPixel { +export class Pixel implements IPixel { red: GCounter; green: GCounter; blue: GCounter; - constructor(peerId: string) { - super(peerId); + constructor() { this.red = new GCounter({}); this.green = new GCounter({}); this.blue = new GCounter({}); diff --git a/examples/chat/src/index.ts b/examples/chat/src/index.ts index 678da38b..285a9fb8 100644 --- a/examples/chat/src/index.ts +++ b/examples/chat/src/index.ts @@ -1,4 +1,5 @@ import { TopologyNode } from "@topology-foundation/node"; +import * as topology from "@topology-foundation/node"; import { Chat, IChat } from "./objects/chat"; import { handleChatMessages } from "./handlers"; import { GSet } from "@topology-foundation/crdt"; @@ -11,119 +12,119 @@ let discoveryPeers: string[] = []; let objectPeers: string[] = []; const render = () => { - const element_peerId = document.getElementById("peerId"); - element_peerId.innerHTML = node.networkNode.peerId; - - const element_peers = document.getElementById("peers"); - element_peers.innerHTML = "[" + peers.join(", ") + "]"; - - const element_discoveryPeers = document.getElementById("discoveryPeers"); - element_discoveryPeers.innerHTML = "[" + discoveryPeers.join(", ") + "]"; - - const element_objectPeers = document.getElementById("objectPeers"); - element_objectPeers.innerHTML = "[" + objectPeers.join(", ") + "]"; - - if(!chatCRO) return; - const chat = chatCRO.getMessages(); - const element_chat = document.getElementById("chat"); - element_chat.innerHTML = ""; - - if(chat.set().size == 0){ - const div = document.createElement("div"); - div.innerHTML = "No messages yet"; - div.style.padding = "10px"; - element_chat.appendChild(div); - return; - } - Array.from(chat.set()).sort().forEach((message: string) => { - const div = document.createElement("div"); - div.innerHTML = message; - div.style.padding = "10px"; - element_chat.appendChild(div); - }); + const element_peerId = document.getElementById("peerId"); + element_peerId.innerHTML = node.networkNode.peerId; + + const element_peers = document.getElementById("peers"); + element_peers.innerHTML = "[" + peers.join(", ") + "]"; + + const element_discoveryPeers = document.getElementById("discoveryPeers"); + element_discoveryPeers.innerHTML = "[" + discoveryPeers.join(", ") + "]"; + + const element_objectPeers = document.getElementById("objectPeers"); + element_objectPeers.innerHTML = "[" + objectPeers.join(", ") + "]"; + + if (!chatCRO) return; + const chat = chatCRO.getMessages(); + const element_chat = document.getElementById("chat"); + element_chat.innerHTML = ""; + + if (chat.set().size == 0) { + const div = document.createElement("div"); + div.innerHTML = "No messages yet"; + div.style.padding = "10px"; + element_chat.appendChild(div); + return; + } + Array.from(chat.set()).sort().forEach((message: string) => { + const div = document.createElement("div"); + div.innerHTML = message; + div.style.padding = "10px"; + element_chat.appendChild(div); + }); } async function sendMessage(message: string) { - let timestamp: string = Date.now().toString(); - if(!chatCRO) { - console.error("Chat CRO not initialized"); - alert("Please create or join a chat room first"); - return; - } - console.log("Sending message: ", `(${timestamp}, ${message}, ${node.networkNode.peerId})`); - chatCRO.addMessage(timestamp, message, node.networkNode.peerId); - - node.updateObject(chatCRO, `addMessage(${timestamp}, ${message}, ${node.networkNode.peerId})`); - render(); + let timestamp: string = Date.now().toString(); + if (!chatCRO) { + console.error("Chat CRO not initialized"); + alert("Please create or join a chat room first"); + return; + } + console.log("Sending message: ", `(${timestamp}, ${message}, ${node.networkNode.peerId})`); + chatCRO.addMessage(timestamp, message, node.networkNode.peerId); + + // topology.updateObject(node, chatCRO, `addMessage(${timestamp}, ${message}, ${node.networkNode.peerId})`); + render(); } async function main() { - await node.start(); + await node.start(); + render(); + + node.addCustomGroupMessageHandler(chatCRO.cro.id, (e) => { + handleChatMessages(chatCRO, e); + peers = node.networkNode.getAllPeers(); + discoveryPeers = node.networkNode.getGroupPeers("topology::discovery"); + if (chatCRO) objectPeers = node.networkNode.getGroupPeers(chatCRO.cro.id); render(); + }); - node.addCustomGroupMessageHandler((e) => { - handleChatMessages(chatCRO, e); - peers = node.networkNode.getAllPeers(); - discoveryPeers = node.networkNode.getGroupPeers("topology::discovery"); - if(chatCRO) objectPeers = node.networkNode.getGroupPeers(chatCRO.getObjectId()); - render(); - }); - - let button_create = document.getElementById("createRoom"); - button_create.addEventListener("click", () => { - chatCRO = new Chat(node.networkNode.peerId); - node.createObject(chatCRO); - (document.getElementById("chatId")).innerHTML = chatCRO.getObjectId(); - render(); - }); - - let button_connect = document.getElementById("joinRoom"); - button_connect.addEventListener("click", async () => { - let input: HTMLInputElement = document.getElementById("roomInput"); - let objectId = input.value; - if(!objectId){ - alert("Please enter a room id"); - return; - } - await node.subscribeObject(objectId, true); - }); - - let button_fetch = document.getElementById("fetchMessages"); - button_fetch.addEventListener("click", async () => { - let input: HTMLInputElement = document.getElementById("roomInput"); - let objectId = input.value; - try { - - let object: any = node.getObject(objectId); - console.log("Object received: ", object); - - let arr: string[] = Array.from(object["chat"]["_set"]); - object["chat"]["_set"] = new Set(arr); - object["chat"] = Object.assign(new GSet(new Set()), object["chat"]); - chatCRO = Object.assign(new Chat(node.networkNode.peerId), object); - - (document.getElementById("chatId")).innerHTML = chatCRO.getObjectId(); - render(); - } catch (e) { - console.error("Error while connecting to the CRO ", objectId, e); - } - }); - - let button_send = document.getElementById("sendMessage"); - button_send.addEventListener("click", async () => { - let input: HTMLInputElement = document.getElementById("messageInput"); - let message: string = input.value; - input.value = ""; - if(!message){ - console.error("Tried sending an empty message"); - alert("Please enter a message"); - return; - } - await sendMessage(message); - const element_chat = document.getElementById("chat"); - element_chat.scrollTop = element_chat.scrollHeight; - }); + let button_create = document.getElementById("createRoom"); + button_create.addEventListener("click", () => { + chatCRO = new Chat(node.networkNode.peerId); + topology.createObject(node, chatCRO); + (document.getElementById("chatId")).innerHTML = chatCRO.cro.id; + render(); + }); + + let button_connect = document.getElementById("joinRoom"); + button_connect.addEventListener("click", async () => { + let input: HTMLInputElement = document.getElementById("roomInput"); + let objectId = input.value; + if (!objectId) { + alert("Please enter a room id"); + return; + } + await topology.subscribeObject(node, objectId, true); + }); + + let button_fetch = document.getElementById("fetchMessages"); + button_fetch.addEventListener("click", async () => { + let input: HTMLInputElement = document.getElementById("roomInput"); + let objectId = input.value; + try { + + let object: any = node.objectStore.get(objectId); + console.log("Object received: ", object); + + let arr: string[] = Array.from(object["chat"]["_set"]); + object["chat"]["_set"] = new Set(arr); + object["chat"] = Object.assign(new GSet(new Set()), object["chat"]); + chatCRO = Object.assign(new Chat(node.networkNode.peerId), object); + + (document.getElementById("chatId")).innerHTML = chatCRO.cro.id; + render(); + } catch (e) { + console.error("Error while connecting to the CRO ", objectId, e); + } + }); + + let button_send = document.getElementById("sendMessage"); + button_send.addEventListener("click", async () => { + let input: HTMLInputElement = document.getElementById("messageInput"); + let message: string = input.value; + input.value = ""; + if (!message) { + console.error("Tried sending an empty message"); + alert("Please enter a message"); + return; + } + await sendMessage(message); + const element_chat = document.getElementById("chat"); + element_chat.scrollTop = element_chat.scrollHeight; + }); } -main(); \ No newline at end of file +main(); diff --git a/examples/chat/src/objects/chat.ts b/examples/chat/src/objects/chat.ts index d8333df7..d8b9612a 100644 --- a/examples/chat/src/objects/chat.ts +++ b/examples/chat/src/objects/chat.ts @@ -1,32 +1,35 @@ -import { TopologyObject } from "@topology-foundation/object"; +import { newTopologyObject, TopologyObject } from "@topology-foundation/object"; import { GSet } from "@topology-foundation/crdt"; -export interface IChat extends TopologyObject { - chat: GSet; - addMessage(timestamp: string, message: string, node_id: string): void; - getMessages(): GSet; - merge(other: Chat): void; +export interface IChat { + cro: TopologyObject; + chat: GSet; + addMessage(timestamp: string, message: string, node_id: string): void; + getMessages(): GSet; + merge(other: Chat): void; } -export class Chat extends TopologyObject implements IChat { - // store messages as strings in the format (timestamp, message, peerId) - chat: GSet; +export class Chat implements IChat { + // TODO: Change this to build a TopologyObject with the + // wasm compilation inside and just use the topology object + cro: TopologyObject; + // store messages as strings in the format (timestamp, message, peerId) + chat: GSet; - constructor(peerId: string) { - super(peerId); - this.chat = new GSet(new Set()); - } + constructor(peerId: string) { + this.cro = newTopologyObject(peerId); + this.chat = new GSet(new Set()); + } - addMessage(timestamp: string, message: string, node_id: string): void { - this.chat.add(`(${timestamp}, ${message}, ${node_id})`); - } + addMessage(timestamp: string, message: string, node_id: string): void { + this.chat.add(`(${timestamp}, ${message}, ${node_id})`); + } - getMessages(): GSet { - return this.chat; - } - - merge(other: Chat): void { - this.chat.merge(other.chat); - } + getMessages(): GSet { + return this.chat; + } + merge(other: Chat): void { + this.chat.merge(other.chat); + } } diff --git a/package.json b/package.json index 5b7365a9..28251890 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "scripts": { "docs": "typedoc", + "proto-gen": "buf generate", "release": "release-it", "test": "vitest" }, diff --git a/packages/network/src/index.ts b/packages/network/src/index.ts index 3d92a68d..dc9314e9 100644 --- a/packages/network/src/index.ts +++ b/packages/network/src/index.ts @@ -1,2 +1,3 @@ -export { TopologyNetworkNodeConfig, TopologyNetworkNode } from "./node.js"; -export { stringToStream, streamToString } from "./stream.js"; +export * from "./node.js"; +export * from "./stream.js"; +export * from "./proto/messages_pb.js"; diff --git a/packages/network/src/node.ts b/packages/network/src/node.ts index ac2413c9..a3e34e19 100644 --- a/packages/network/src/node.ts +++ b/packages/network/src/node.ts @@ -24,6 +24,8 @@ import { bootstrap } from "@libp2p/bootstrap"; import { webTransport } from "@libp2p/webtransport"; import { autoNAT } from "@libp2p/autonat"; import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; +import * as lp from "it-length-prefixed"; +import { Message } from "./proto/messages_pb.js"; // snake_casing to match the JSON config export interface TopologyNetworkNodeConfig { @@ -184,10 +186,10 @@ export class TopologyNetworkNode { return peers.map((peer) => peer.toString()); } - async broadcastMessage(topic: string, message: Uint8Array) { + async broadcastMessage(topic: string, message: Message) { try { - if (this._pubsub?.getSubscribers(topic)?.length === 0) return; - await this._pubsub?.publish(topic, message); + let messageBuffer = Message.encode(message).finish(); + await this._pubsub?.publish(topic, messageBuffer); console.log( "topology::network::broadcastMessage: Successfuly broadcasted message to topic", @@ -198,11 +200,14 @@ export class TopologyNetworkNode { } } - async sendMessage(peerId: string, protocols: string[], message: string) { + async sendMessage(peerId: string, protocols: string[], message: Message) { try { const connection = await this._node?.dial([multiaddr(`/p2p/${peerId}`)]); const stream = await connection?.newStream(protocols); - stringToStream(stream, message); + let messageBuffer = Message.encode(message).finish(); + stream.sink(lp.encode([messageBuffer])) + + // stringToStream(stream, message); console.log( `topology::network::sendMessage: Successfuly sent message to peer: ${peerId} with message: ${message}`, @@ -215,7 +220,7 @@ export class TopologyNetworkNode { async sendGroupMessageRandomPeer( group: string, protocols: string[], - message: string, + message: Message, ) { try { const peers = this._pubsub?.getSubscribers(group); @@ -224,7 +229,10 @@ export class TopologyNetworkNode { const connection = await this._node?.dial(peerId); const stream: Stream = (await connection?.newStream(protocols)) as Stream; - stringToStream(stream, message); + let messageBuffer = Message.encode(message).finish(); + stream.sink(lp.encode([messageBuffer])) + + // stringToStream(stream, message); console.log( `topology::network::sendMessageRandomTopicPeer: Successfuly sent message to peer: ${peerId} with message: ${message}`, diff --git a/packages/network/src/proto/messages.proto b/packages/network/src/proto/messages.proto new file mode 100644 index 00000000..6aa971fe --- /dev/null +++ b/packages/network/src/proto/messages.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package topology.network; + +message Message { + enum MessageType { + UPDATE = 0; + SYNC = 1; + SYNC_ACCEPT = 2; + SYNC_REJECT = 3; + CUSTOM = 4; + } + + string sender = 1; + MessageType type = 2; + bytes data = 3; +} diff --git a/packages/network/src/proto/messages_pb.ts b/packages/network/src/proto/messages_pb.ts new file mode 100644 index 00000000..da36bdfc --- /dev/null +++ b/packages/network/src/proto/messages_pb.ts @@ -0,0 +1,197 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v1.181.1 +// protoc unknown +// source: network/src/proto/messages.proto + +/* eslint-disable */ +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "topology.network"; + +export interface Message { + sender: string; + type: Message_MessageType; + data: Uint8Array; +} + +export enum Message_MessageType { + UPDATE = 0, + SYNC = 1, + SYNC_ACCEPT = 2, + SYNC_REJECT = 3, + CUSTOM = 4, + UNRECOGNIZED = -1, +} + +export function message_MessageTypeFromJSON(object: any): Message_MessageType { + switch (object) { + case 0: + case "UPDATE": + return Message_MessageType.UPDATE; + case 1: + case "SYNC": + return Message_MessageType.SYNC; + case 2: + case "SYNC_ACCEPT": + return Message_MessageType.SYNC_ACCEPT; + case 3: + case "SYNC_REJECT": + return Message_MessageType.SYNC_REJECT; + case 4: + case "CUSTOM": + return Message_MessageType.CUSTOM; + case -1: + case "UNRECOGNIZED": + default: + return Message_MessageType.UNRECOGNIZED; + } +} + +export function message_MessageTypeToJSON(object: Message_MessageType): string { + switch (object) { + case Message_MessageType.UPDATE: + return "UPDATE"; + case Message_MessageType.SYNC: + return "SYNC"; + case Message_MessageType.SYNC_ACCEPT: + return "SYNC_ACCEPT"; + case Message_MessageType.SYNC_REJECT: + return "SYNC_REJECT"; + case Message_MessageType.CUSTOM: + return "CUSTOM"; + case Message_MessageType.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + +function createBaseMessage(): Message { + return { sender: "", type: 0, data: new Uint8Array(0) }; +} + +export const Message = { + encode(message: Message, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.sender !== "") { + writer.uint32(10).string(message.sender); + } + if (message.type !== 0) { + writer.uint32(16).int32(message.type); + } + if (message.data.length !== 0) { + writer.uint32(26).bytes(message.data); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Message { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMessage(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.sender = reader.string(); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.type = reader.int32() as any; + continue; + case 3: + if (tag !== 26) { + break; + } + + message.data = reader.bytes(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Message { + return { + sender: isSet(object.sender) ? globalThis.String(object.sender) : "", + type: isSet(object.type) ? message_MessageTypeFromJSON(object.type) : 0, + data: isSet(object.data) ? bytesFromBase64(object.data) : new Uint8Array(0), + }; + }, + + toJSON(message: Message): unknown { + const obj: any = {}; + if (message.sender !== "") { + obj.sender = message.sender; + } + if (message.type !== 0) { + obj.type = message_MessageTypeToJSON(message.type); + } + if (message.data.length !== 0) { + obj.data = base64FromBytes(message.data); + } + return obj; + }, + + create, I>>(base?: I): Message { + return Message.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Message { + const message = createBaseMessage(); + message.sender = object.sender ?? ""; + message.type = object.type ?? 0; + message.data = object.data ?? new Uint8Array(0); + return message; + }, +}; + +function bytesFromBase64(b64: string): Uint8Array { + if ((globalThis as any).Buffer) { + return Uint8Array.from(globalThis.Buffer.from(b64, "base64")); + } else { + const bin = globalThis.atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; + } +} + +function base64FromBytes(arr: Uint8Array): string { + if ((globalThis as any).Buffer) { + return globalThis.Buffer.from(arr).toString("base64"); + } else { + const bin: string[] = []; + arr.forEach((byte) => { + bin.push(globalThis.String.fromCharCode(byte)); + }); + return globalThis.btoa(bin.join("")); + } +} + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts new file mode 100644 index 00000000..19ca06c2 --- /dev/null +++ b/packages/node/src/handlers.ts @@ -0,0 +1,67 @@ +import { Stream } from "@libp2p/interface"; +import * as lp from "it-length-prefixed"; +import { + Message, + Message_MessageType, +} from "@topology-foundation/network"; +import { TopologyNode } from "."; + +export async function topologyMessagesHandler(node: TopologyNode, stream: Stream) { + const buf = (await lp.decode(stream.source).return()).value; + const message = Message.decode(new Uint8Array(buf ? buf.subarray() : [])) + + switch (message.type) { + case Message_MessageType.UPDATE: + updateHandler(node, message.data); + break; + case Message_MessageType.SYNC: + syncHandler(node, stream.protocol ?? "", message.sender, message.data); + break; + case Message_MessageType.SYNC_ACCEPT: + syncAcceptHandler(node, message.data); + break; + case Message_MessageType.SYNC_REJECT: + syncRejectHandler(node, message.data); + break; + default: + console.error("topology::node::messageHandler", "Invalid operation"); + break; + } +} + +function updateHandler(node: TopologyNode, data: Uint8Array) { + // + /* + this.objectStore.put(object.id, object); + // not dialed, emitted through pubsub + const message = `{ + "type": "object_update", + "data": [${uint8ArrayFromString(update_data)}] + }`; + this.networkNode.broadcastMessage( + object.id, + uint8ArrayFromString(message), + ); + */ +} + +function syncHandler(node: TopologyNode, protocol: string, sender: string, data: Uint8Array) { + // Receive RBILT & send back + // (might send reject) <- TODO: when should we reject? + const message = Message.create({ + sender: node.networkNode.peerId, + type: Message_MessageType.SYNC_ACCEPT, + // add data here + data: new Uint8Array(0), + }); + + node.networkNode.sendMessage(sender, [protocol], message); +} + +function syncAcceptHandler(node: TopologyNode, data: Uint8Array) { + // Process RBILT +} + +function syncRejectHandler(node: TopologyNode, data: Uint8Array) { + // Ask sync from another peer +} diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 5e49367f..87c96e87 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,6 +1,8 @@ import { GossipsubMessage } from "@chainsafe/libp2p-gossipsub"; import { EventHandler, StreamHandler } from "@libp2p/interface"; import { + Message, + Message_MessageType, TopologyNetworkNode, TopologyNetworkNodeConfig, streamToString, @@ -9,7 +11,8 @@ import { TopologyObject } from "@topology-foundation/object"; import { TopologyObjectStore } from "./store"; import { fromString as uint8ArrayFromString } from "uint8arrays/from-string"; import { toString as uint8ArrayToString } from "uint8arrays/to-string"; -import { OPERATIONS } from "./operations.js"; +import * as lp from "it-length-prefixed"; +import { topologyMessagesHandler } from "./handlers"; export * from "./operations.js"; @@ -20,14 +23,14 @@ export interface TopologyNodeConfig { export class TopologyNode { private _config?: TopologyNodeConfig; - private _objectStore: TopologyObjectStore; + objectStore: TopologyObjectStore; networkNode: TopologyNetworkNode; constructor(config?: TopologyNodeConfig) { this._config = config; this.networkNode = new TopologyNetworkNode(config?.network_config); - this._objectStore = new TopologyObjectStore(); + this.objectStore = new TopologyObjectStore(); } async start(): Promise { @@ -35,140 +38,7 @@ export class TopologyNode { this.networkNode.addMessageHandler( ["/topology/message/0.0.1"], - async ({ stream }) => { - let input = await streamToString(stream); - if (!input) return; - - const message = JSON.parse(input); - switch (message["type"]) { - case "object_fetch": { - const objectId = uint8ArrayToString( - new Uint8Array(message["data"]), - ); - const object = this.getObject(objectId); - const object_message = `{ - "type": "object", - "data": [${uint8ArrayFromString(JSON.stringify(object, (_key, value) => (value instanceof Set ? [...value] : value)))}] - }`; - await this.networkNode.sendMessage( - message["sender"], - [stream.protocol], - object_message, - ); - // await stringToStream(stream, object_message); - break; - } - case "object": { - const object = JSON.parse( - uint8ArrayToString(new Uint8Array(message["data"])), - ); - this._objectStore.put(object["id"], object); - break; - } - case "object_sync": { - const objectId = uint8ArrayToString( - new Uint8Array(message["data"]), - ); - const object = this.getObject(objectId); - const object_message = `{ - "type": "object_merge", - "data": [${uint8ArrayFromString(JSON.stringify(object))}] - }`; - await this.networkNode.sendMessage( - message["sender"], - [stream.protocol], - object_message, - ); - break; - } - case "object_merge": { - const object = JSON.parse( - uint8ArrayToString(new Uint8Array(message["data"])), - ); - const local = this._objectStore.get(object["id"]); - if (local) { - local.merge(object); - this._objectStore.put(object["id"], local); - } - break; - } - default: { - return; - } - } - }, - ); - } - - createObject(object: TopologyObject) { - const objectId = object.getObjectId(); - this.networkNode.subscribe(objectId); - this._objectStore.put(objectId, object); - } - - /// Subscribe to the object's PubSub group - /// and fetch it from a peer - async subscribeObject(objectId: string, fetch = false, peerId = "") { - this.networkNode.subscribe(objectId); - if (!fetch) return; - const message = `{ - "type": "object_fetch", - "sender": "${this.networkNode.peerId}", - "data": [${uint8ArrayFromString(objectId)}] - }`; - - if (!peerId) { - await this.networkNode.sendGroupMessageRandomPeer( - objectId, - ["/topology/message/0.0.1"], - message, - ); - } else { - await this.networkNode.sendMessage( - peerId, - ["/topology/message/0.0.1"], - message, - ); - } - } - - async syncObject(objectId: string, peerId = "") { - const message = `{ - "type": "object_sync", - "sender": "${this.networkNode.peerId}", - "data": [${uint8ArrayFromString(objectId)}] - }`; - - if (!peerId) { - await this.networkNode.sendGroupMessageRandomPeer( - objectId, - ["/topology/message/0.0.1"], - message, - ); - } else { - await this.networkNode.sendMessage( - peerId, - ["/topology/message/0.0.1"], - message, - ); - } - } - - /// Get the object from the local Object Store - getObject(objectId: string) { - return this._objectStore.get(objectId); - } - - updateObject(object: TopologyObject, update_data: string) { - this._objectStore.put(object.getObjectId(), object); - // not dialed, emitted through pubsub - const message = `{ - "type": "object_update", - "data": [${uint8ArrayFromString(update_data)}] - }`; - this.networkNode.broadcastMessage( - object.getObjectId(), - uint8ArrayFromString(message), + async ({ stream }) => topologyMessagesHandler(this, stream) ); } @@ -176,17 +46,32 @@ export class TopologyNode { this.networkNode.subscribe(group); } - sendGroupMessage(group: string, message: Uint8Array) { - this.networkNode.broadcastMessage(group, message); - } - addCustomGroupMessageHandler( + group: string, handler: EventHandler>, ) { this.networkNode.addGroupMessageHandler(handler); } + sendGroupMessage(group: string, data: Uint8Array) { + const message = Message.create({ + sender: this.networkNode.peerId, + type: Message_MessageType.CUSTOM, + data, + }) + this.networkNode.broadcastMessage(group, message); + } + addCustomMessageHandler(protocol: string | string[], handler: StreamHandler) { this.networkNode.addMessageHandler(protocol, handler); } + + sendCustomMessage(peerId: string, protocol: string, data: Uint8Array) { + const message = Message.create({ + sender: this.networkNode.peerId, + type: Message_MessageType.CUSTOM, + data, + }); + this.networkNode.sendMessage(peerId, [protocol], message); + } } diff --git a/packages/node/src/operations.ts b/packages/node/src/operations.ts index 4b831b73..6c256542 100644 --- a/packages/node/src/operations.ts +++ b/packages/node/src/operations.ts @@ -1,24 +1,130 @@ -/* Object and P2P Network operations */ +import { TopologyObject } from "@topology-foundation/object"; +import { TopologyNode } from "."; +import { Message, Message_MessageType } from "@topology-foundation/network"; + +/* Object operations */ export enum OPERATIONS { - // TODO: Confirm if this needs a network message - // who to send to? /* Create a new CRO */ CREATE, /* Update operation on a CRO */ UPDATE, - // These two are not network messages /* Subscribe to a PubSub group (either CRO or custom) */ SUBSCRIBE, /* Unsubscribe from a PubSub group */ UNSUBSCRIBE, - /* Actively send the CRO RIBLT to a random peer */ - SYNC, - /* Accept the sync request and send the RIBLT - after processing the received RIBLT - */ - SYNC_ACCEPT, - /* Reject the sync request */ - SYNC_REJECT, + SYNC +} + +/* Utility function to execute object operations apart of calling the functions directly */ +export async function executeObjectOperation(node: TopologyNode, operation: OPERATIONS, data: Uint8Array) { + switch (operation) { + case OPERATIONS.CREATE: + // data = CRO + createObject(node, data); + break; + case OPERATIONS.UPDATE: + // data = [CRO_ID, OPERATION] + updateObject(node, data) + break; + case OPERATIONS.SUBSCRIBE: + // data = CRO_ID + await subscribeObject(node, data) + break; + case OPERATIONS.UNSUBSCRIBE: + // data = CRO_ID + unsubscribeObject(node, data) + break; + case OPERATIONS.SYNC: + // data = CRO + // TODO: data = [CRO_ID, RIBLT] + await syncObject(node, data) + break; + default: + console.error("topology::node::executeObjectOperation", "Invalid operation"); + break; + } +} + +export function createObject(node: TopologyNode, data: Uint8Array) { + const object = TopologyObject.decode(data) + node.networkNode.subscribe(object.id); + node.objectStore.put(object.id, object); +} + +export function updateObject(node: TopologyNode, data: Uint8Array) { + // TODO: should just send the object diff, not the full object + // this is handler, we want the action of sending + const object = TopologyObject.decode(data) + node.objectStore.put(object.id, object); + + const message = Message.create({ + type: Message_MessageType.UPDATE, + data: data + }); + + node.networkNode.broadcastMessage( + object.id, + message, + ); +} + +export async function subscribeObject(node: TopologyNode, data: Uint8Array, fetch?: boolean, peerId?: string) { + // process data as only the object id and not the full obj + // need to create the obj anyway to sync empty obj + const object = TopologyObject.decode(data) + node.networkNode.subscribe(object.id); + + if (!fetch) return; + const message = Message.create({ + sender: node.networkNode.peerId, + type: Message_MessageType.SYNC, + data + }); + + if (!peerId) { + await node.networkNode.sendGroupMessageRandomPeer( + object.id, + ["/topology/message/0.0.1"], + message, + ); + } else { + await node.networkNode.sendMessage( + peerId, + ["/topology/message/0.0.1"], + message, + ); + } +} + +export function unsubscribeObject(node: TopologyNode, data: Uint8Array) { + // process data as only the object id and not the full obj + const object = TopologyObject.decode(data) + node.networkNode.unsubscribe(object.id); +} + +export async function syncObject(node: TopologyNode, data: Uint8Array, peerId?: string) { + // Send sync request to a random peer + const object = TopologyObject.decode(data) + + const message = Message.create({ + type: Message_MessageType.SYNC, + data: data + }) + + // TODO: check how to do it better + if (!peerId) { + await node.networkNode.sendGroupMessageRandomPeer( + object.id, + ["/topology/message/0.0.1"], + message, + ); + } else { + await node.networkNode.sendMessage( + peerId, + ["/topology/message/0.0.1"], + message, + ); + } } diff --git a/packages/node/src/version.ts b/packages/node/src/version.ts index 85ceb686..2d05f320 100644 --- a/packages/node/src/version.ts +++ b/packages/node/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.0.22"; +export const VERSION = "0.0.23-5"; diff --git a/packages/object/package.json b/packages/object/package.json index 5a9c0316..83df32e0 100644 --- a/packages/object/package.json +++ b/packages/object/package.json @@ -24,6 +24,10 @@ "build": "tsc -b", "clean": "rm -rf dist/ node_modules/", "prepack": "tsc -b", + "proto-gen": "buf generate", "test": "vitest" + }, + "dependencies": { + "ts-proto": "^1.181.1" } } diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 380bc870..4ac2b96f 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -1,29 +1,20 @@ import * as crypto from "crypto"; +import { TopologyObject } from "./proto/object_pb.js"; -export abstract class TopologyObject { - // TODO generate functions from the abi - private abi?: string; - private id?: string; +export * from "./proto/object_pb.js"; - constructor(peerId: string) { - this.abi = ""; +/* Creates a new TopologyObject - // id = sha256(abi, peer_id, random_nonce) - this.id = crypto +*/ +export function newTopologyObject(peerId: string, id?: string, abi?: string, bytecode?: string): TopologyObject { + return { + id: id ?? crypto .createHash("sha256") - .update(this.abi) + .update(abi ?? "") .update(peerId) .update(Math.floor(Math.random() * Number.MAX_VALUE).toString()) - .digest("hex"); - } - - getObjectAbi(): string { - return this.abi ?? ""; - } - - getObjectId(): string { - return this.id ?? ""; - } - - abstract merge(other: TopologyObject): void; + .digest("hex"), + abi: abi ?? "", + bytecode: bytecode ?? "" + }; } diff --git a/packages/object/src/proto/object.proto b/packages/object/src/proto/object.proto new file mode 100644 index 00000000..8fbe1f39 --- /dev/null +++ b/packages/object/src/proto/object.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; +package topology.object; + +message TopologyObject { + string id = 1; + string abi = 2; + string bytecode = 3; +} diff --git a/packages/object/src/proto/object_pb.ts b/packages/object/src/proto/object_pb.ts new file mode 100644 index 00000000..cb4c589e --- /dev/null +++ b/packages/object/src/proto/object_pb.ts @@ -0,0 +1,121 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v1.181.1 +// protoc unknown +// source: object/src/proto/object.proto + +/* eslint-disable */ +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "topology.object"; + +export interface TopologyObject { + id: string; + abi: string; + bytecode: string; +} + +function createBaseTopologyObject(): TopologyObject { + return { id: "", abi: "", bytecode: "" }; +} + +export const TopologyObject = { + encode(message: TopologyObject, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.id !== "") { + writer.uint32(10).string(message.id); + } + if (message.abi !== "") { + writer.uint32(18).string(message.abi); + } + if (message.bytecode !== "") { + writer.uint32(26).string(message.bytecode); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): TopologyObject { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTopologyObject(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.id = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.abi = reader.string(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.bytecode = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): TopologyObject { + return { + id: isSet(object.id) ? globalThis.String(object.id) : "", + abi: isSet(object.abi) ? globalThis.String(object.abi) : "", + bytecode: isSet(object.bytecode) ? globalThis.String(object.bytecode) : "", + }; + }, + + toJSON(message: TopologyObject): unknown { + const obj: any = {}; + if (message.id !== "") { + obj.id = message.id; + } + if (message.abi !== "") { + obj.abi = message.abi; + } + if (message.bytecode !== "") { + obj.bytecode = message.bytecode; + } + return obj; + }, + + create, I>>(base?: I): TopologyObject { + return TopologyObject.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): TopologyObject { + const message = createBaseTopologyObject(); + message.id = object.id ?? ""; + message.abi = object.abi ?? ""; + message.bytecode = object.bytecode ?? ""; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/packages/object/tsconfig.json b/packages/object/tsconfig.json index 280f82e7..fe2df5cc 100644 --- a/packages/object/tsconfig.json +++ b/packages/object/tsconfig.json @@ -3,5 +3,7 @@ "compilerOptions": { "outDir": "dist" }, - "include": ["src/**/*.ts"] + "include": [ + "src/**/*.ts" + ] } diff --git a/yarn.lock b/yarn.lock index be093b57..7bc289a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -941,6 +941,59 @@ "@pnpm/network.ca-file" "^1.0.1" config-chain "^1.1.11" +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@release-it-plugins/workspaces@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@release-it-plugins/workspaces/-/workspaces-4.2.0.tgz#db4ce50c9f28ccb944b3fff5843d4637e372d2e7" @@ -1068,41 +1121,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== -"@topology-foundation/crdt@file:packages/crdt": - version "0.0.22" - -"@topology-foundation/network@file:packages/network": - version "0.0.22" - dependencies: - "@chainsafe/libp2p-gossipsub" "^13.1.0" - "@chainsafe/libp2p-noise" "^15.1.0" - "@chainsafe/libp2p-yamux" "^6.0.2" - "@libp2p/autonat" "^1.0.0" - "@libp2p/bootstrap" "^10.1.3" - "@libp2p/circuit-relay-v2" "^1.1.2" - "@libp2p/dcutr" "^1.1.0" - "@libp2p/identify" "^2.1.2" - "@libp2p/interface-pubsub" "^4.0.1" - "@libp2p/mdns" "^10.1.3" - "@libp2p/pubsub-peer-discovery" "^10.0.2" - "@libp2p/webrtc" "^4.1.3" - "@libp2p/websockets" "^8.1.2" - "@libp2p/webtransport" "^4.1.2" - "@multiformats/multiaddr" "^12.3.0" - it-pipe "^3.0.1" - libp2p "^1.8.2" - -"@topology-foundation/node@file:packages/node": - version "0.0.22" - dependencies: - "@topology-foundation/crdt" "0.0.22" - "@topology-foundation/network" "0.0.22" - "@topology-foundation/object" "0.0.22" - commander "^12.1.0" - -"@topology-foundation/object@file:packages/object": - version "0.0.22" - "@tsconfig/node10@^1.0.7": version "1.0.11" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" @@ -1267,6 +1285,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@>=13.7.0": + version "20.14.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.11.tgz#09b300423343460455043ddd4d0ded6ac579b74b" + integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA== + dependencies: + undici-types "~5.26.4" + "@types/qs@*": version "6.9.15" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" @@ -1979,6 +2004,11 @@ caniuse-lite@^1.0.30001640: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz#6aa6610eb24067c246d30c57f055a9d0a7f8d05f" integrity sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA== +case-anything@^2.1.13: + version "2.1.13" + resolved "https://registry.yarnpkg.com/case-anything/-/case-anything-2.1.13.tgz#0cdc16278cb29a7fcdeb072400da3f342ba329e9" + integrity sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng== + chai@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" @@ -2471,6 +2501,11 @@ detect-indent@^6.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + detect-libc@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" @@ -2514,6 +2549,13 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dprint-node@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/dprint-node/-/dprint-node-1.0.8.tgz#a02470722d8208a7d7eb3704328afda1d6758625" + integrity sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg== + dependencies: + detect-libc "^1.0.3" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -4037,6 +4079,11 @@ log-symbols@^6.0.0: chalk "^5.3.0" is-unicode-supported "^1.3.0" +long@^5.0.0, long@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + loupe@^3.1.0, loupe@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54" @@ -4795,6 +4842,24 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== +protobufjs@^7.2.4: + version "7.3.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.2.tgz#60f3b7624968868f6f739430cfbc8c9370e26df4" + integrity sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + protocols@^2.0.0, protocols@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" @@ -5791,6 +5856,31 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +ts-poet@^6.7.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-6.9.0.tgz#e63ac8d8a9e91a2e0e5d2bf0755db71346728bd2" + integrity sha512-roe6W6MeZmCjRmppyfOURklO5tQFQ6Sg7swURKkwYJvV7dbGCrK28um5+51iW3twdPRKtwarqFAVMU6G1mvnuQ== + dependencies: + dprint-node "^1.0.8" + +ts-proto-descriptors@1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.16.0.tgz#e9f15d5d23774088f8573fa5a2d75130c64a565a" + integrity sha512-3yKuzMLpltdpcyQji1PJZRfoo4OJjNieKTYkQY8pF7xGKsYz/RHe3aEe4KiRxcinoBmnEhmuI+yJTxLb922ULA== + dependencies: + long "^5.2.3" + protobufjs "^7.2.4" + +ts-proto@^1.181.1: + version "1.181.1" + resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.181.1.tgz#d96707087840870cae4d293f9cd6d52ed66bdede" + integrity sha512-lNmd/KEgqWtwDG9mIM3EpcxBx+URRVHkDP/EEJBgQJaQwmZFTk6VjHg56HNQswd114yXGfF+8pKQvJ2iH9KfWw== + dependencies: + case-anything "^2.1.13" + protobufjs "^7.2.4" + ts-poet "^6.7.0" + ts-proto-descriptors "1.16.0" + tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.1: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"