@agent-p2p/peer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/events.d.ts +12 -0
  2. package/dist/events.d.ts.map +1 -0
  3. package/dist/events.js +26 -0
  4. package/dist/events.js.map +1 -0
  5. package/dist/index.d.ts +8 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +8 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/permissions/index.d.ts +2 -0
  10. package/dist/permissions/index.d.ts.map +1 -0
  11. package/dist/permissions/index.js +2 -0
  12. package/dist/permissions/index.js.map +1 -0
  13. package/dist/permissions/room-permissions.d.ts +30 -0
  14. package/dist/permissions/room-permissions.d.ts.map +1 -0
  15. package/dist/permissions/room-permissions.js +105 -0
  16. package/dist/permissions/room-permissions.js.map +1 -0
  17. package/dist/radio-peer.d.ts +30 -0
  18. package/dist/radio-peer.d.ts.map +1 -0
  19. package/dist/radio-peer.js +153 -0
  20. package/dist/radio-peer.js.map +1 -0
  21. package/dist/transport/index.d.ts +3 -0
  22. package/dist/transport/index.d.ts.map +1 -0
  23. package/dist/transport/index.js +3 -0
  24. package/dist/transport/index.js.map +1 -0
  25. package/dist/transport/indexeddb.d.ts +4 -0
  26. package/dist/transport/indexeddb.d.ts.map +1 -0
  27. package/dist/transport/indexeddb.js +5 -0
  28. package/dist/transport/indexeddb.js.map +1 -0
  29. package/dist/transport/trystero-provider.d.ts +29 -0
  30. package/dist/transport/trystero-provider.d.ts.map +1 -0
  31. package/dist/transport/trystero-provider.js +141 -0
  32. package/dist/transport/trystero-provider.js.map +1 -0
  33. package/dist/transport/webrtc.d.ts +9 -0
  34. package/dist/transport/webrtc.d.ts.map +1 -0
  35. package/dist/transport/webrtc.js +10 -0
  36. package/dist/transport/webrtc.js.map +1 -0
  37. package/dist/types.d.ts +33 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/types.js +2 -0
  40. package/dist/types.js.map +1 -0
  41. package/dist/yjs/awareness.d.ts +9 -0
  42. package/dist/yjs/awareness.d.ts.map +1 -0
  43. package/dist/yjs/awareness.js +17 -0
  44. package/dist/yjs/awareness.js.map +1 -0
  45. package/dist/yjs/channel-array.d.ts +16 -0
  46. package/dist/yjs/channel-array.d.ts.map +1 -0
  47. package/dist/yjs/channel-array.js +50 -0
  48. package/dist/yjs/channel-array.js.map +1 -0
  49. package/dist/yjs/doc-manager.d.ts +13 -0
  50. package/dist/yjs/doc-manager.d.ts.map +1 -0
  51. package/dist/yjs/doc-manager.js +30 -0
  52. package/dist/yjs/doc-manager.js.map +1 -0
  53. package/dist/yjs/index.d.ts +4 -0
  54. package/dist/yjs/index.d.ts.map +1 -0
  55. package/dist/yjs/index.js +4 -0
  56. package/dist/yjs/index.js.map +1 -0
  57. package/package.json +31 -0
  58. package/src/events.ts +30 -0
  59. package/src/index.ts +23 -0
  60. package/src/permissions/index.ts +1 -0
  61. package/src/permissions/room-permissions.ts +125 -0
  62. package/src/radio-peer.ts +187 -0
  63. package/src/transport/index.ts +2 -0
  64. package/src/transport/indexeddb.ts +9 -0
  65. package/src/transport/trystero-provider.ts +173 -0
  66. package/src/types.ts +31 -0
  67. package/src/yjs/awareness.ts +25 -0
  68. package/src/yjs/channel-array.ts +60 -0
  69. package/src/yjs/doc-manager.ts +35 -0
  70. package/src/yjs/index.ts +3 -0
@@ -0,0 +1,187 @@
1
+ import {
2
+ type SignalEnvelope,
3
+ type Keypair,
4
+ generateKeypair,
5
+ peerId,
6
+ SignalBuilder,
7
+ } from "@agent-p2p/core";
8
+ import type { IndexeddbPersistence } from "y-indexeddb";
9
+ import { TypedEmitter } from "./events.js";
10
+ import type { PeerEventMap, PeerInfo, RadioPeerOptions } from "./types.js";
11
+ import { DocManager } from "./yjs/doc-manager.js";
12
+ import { setLocalPeer, getConnectedPeers } from "./yjs/awareness.js";
13
+ import { TrysteroProvider } from "./transport/trystero-provider.js";
14
+ import { createIndexedDBProvider } from "./transport/indexeddb.js";
15
+ import { RoomPermissions } from "./permissions/room-permissions.js";
16
+
17
+ const DEFAULT_OPTIONS: Required<Omit<RadioPeerOptions, "keypair">> = {
18
+ password: "",
19
+ maxSignalsPerChannel: 1000,
20
+ enablePersistence: true,
21
+ transport: { backend: "torrent" },
22
+ };
23
+
24
+ export class RadioPeer extends TypedEmitter<PeerEventMap> {
25
+ readonly stationId: string;
26
+ private _keypair!: Keypair;
27
+ private _peerId!: string;
28
+ private _opts: Required<Omit<RadioPeerOptions, "keypair">> & { keypair?: Keypair };
29
+ private _docManager!: DocManager;
30
+ private _provider!: TrysteroProvider;
31
+ private _idb: IndexeddbPersistence | null = null;
32
+ private _subscriptions = new Map<string, () => void>();
33
+ private _readyPromise: Promise<void>;
34
+ private _knownPeers = new Set<string>();
35
+ private _permissions!: RoomPermissions;
36
+ private _unsubRoles: (() => void) | null = null;
37
+
38
+ constructor(stationId: string, opts: RadioPeerOptions = {}) {
39
+ super();
40
+ this.stationId = stationId;
41
+ const { keypair, ...rest } = opts;
42
+ this._opts = { ...DEFAULT_OPTIONS, ...rest, keypair };
43
+ this._readyPromise = this._init();
44
+ }
45
+
46
+ private async _init(): Promise<void> {
47
+ this.emit("status", "connecting");
48
+
49
+ if (this._opts.keypair) {
50
+ this._keypair = this._opts.keypair;
51
+ } else {
52
+ this._keypair = await generateKeypair();
53
+ }
54
+ this._peerId = peerId(this._keypair.publicKey);
55
+
56
+ const guard = (pid: string, topic: string) => {
57
+ return this._permissions.canPublish(pid, topic);
58
+ };
59
+
60
+ this._docManager = new DocManager(this._opts.maxSignalsPerChannel, guard);
61
+
62
+ this._permissions = new RoomPermissions(this._docManager.doc);
63
+
64
+ const roomName = `agent-p2p:${this.stationId}`;
65
+
66
+ const transport = this._opts.transport;
67
+ this._provider = new TrysteroProvider(this._docManager.doc, roomName, {
68
+ backend: transport.backend,
69
+ password: this._opts.password || undefined,
70
+ relayUrls: transport.relayUrls,
71
+ });
72
+
73
+ await this._provider.connected;
74
+
75
+ if (this._opts.enablePersistence) {
76
+ try {
77
+ this._idb = createIndexedDBProvider(this._docManager.doc, roomName);
78
+ } catch {
79
+ // IndexedDB not available (e.g. Node.js) — skip
80
+ }
81
+ }
82
+
83
+ setLocalPeer(this._provider.awareness, this._peerId);
84
+
85
+ // TOFU: first peer becomes owner, later peers become members
86
+ if (!this._permissions.isInitialized) {
87
+ this._permissions.initializeAsOwner(this._peerId);
88
+ } else {
89
+ this._permissions.acceptExisting(this._peerId);
90
+ }
91
+
92
+ // Forward role changes as events
93
+ this._unsubRoles = this._permissions.observeRoles((pid, role) => {
94
+ this.emit("roles:changed", { peerId: pid, role });
95
+ });
96
+
97
+ this._provider.awareness.on("change", () => {
98
+ const current = getConnectedPeers(this._provider.awareness);
99
+ const currentIds = new Set(current.map((p) => p.peerId));
100
+
101
+ for (const p of current) {
102
+ if (!this._knownPeers.has(p.peerId)) {
103
+ this._knownPeers.add(p.peerId);
104
+ this.emit("peer:join", p);
105
+ }
106
+ }
107
+ for (const id of this._knownPeers) {
108
+ if (!currentIds.has(id)) {
109
+ this._knownPeers.delete(id);
110
+ this.emit("peer:leave", { peerId: id, joinedAt: 0 });
111
+ }
112
+ }
113
+ });
114
+
115
+ this.emit("status", "connected");
116
+ }
117
+
118
+ async ready(): Promise<void> {
119
+ return this._readyPromise;
120
+ }
121
+
122
+ get peerId(): string {
123
+ return this._peerId;
124
+ }
125
+
126
+ get keypair(): Keypair {
127
+ return this._keypair;
128
+ }
129
+
130
+ get permissions(): RoomPermissions {
131
+ return this._permissions;
132
+ }
133
+
134
+ async publish(topic: string, body: unknown): Promise<SignalEnvelope> {
135
+ await this._readyPromise;
136
+
137
+ if (!this._permissions.canPublish(this._peerId, topic)) {
138
+ this.emit("permission:denied", { topic, action: "publish" });
139
+ throw new Error(`Permission denied: cannot publish to "${topic}"`);
140
+ }
141
+
142
+ const signal = await new SignalBuilder()
143
+ .channel(topic)
144
+ .peer(this._peerId)
145
+ .body(body)
146
+ .buildSigned(this._keypair.privateKey, this._keypair.publicKey);
147
+
148
+ this._docManager.getChannel(topic).push(signal);
149
+ return signal;
150
+ }
151
+
152
+ subscribe(topic: string, fn: (signal: SignalEnvelope) => void): () => void {
153
+ const channel = this._docManager.getChannel(topic);
154
+ const readFilter = (signal: SignalEnvelope) => {
155
+ return this._permissions.canRead(this._peerId, signal.channel_topic);
156
+ };
157
+ const unsub = channel.observe((signals) => {
158
+ for (const s of signals) {
159
+ fn(s);
160
+ this.emit("signal", s);
161
+ }
162
+ }, readFilter);
163
+ this._subscriptions.set(topic, unsub);
164
+ return unsub;
165
+ }
166
+
167
+ getHistory(topic: string): SignalEnvelope[] {
168
+ return this._docManager.getChannel(topic).getAll();
169
+ }
170
+
171
+ getPeers(): PeerInfo[] {
172
+ return getConnectedPeers(this._provider.awareness);
173
+ }
174
+
175
+ disconnect(): void {
176
+ this.emit("status", "disconnected");
177
+ for (const unsub of this._subscriptions.values()) {
178
+ unsub();
179
+ }
180
+ this._subscriptions.clear();
181
+ this._unsubRoles?.();
182
+ this._provider.destroy();
183
+ this._idb?.destroy();
184
+ this._docManager.destroy();
185
+ this.removeAllListeners();
186
+ }
187
+ }
@@ -0,0 +1,2 @@
1
+ export { TrysteroProvider, type TrysteroConfig } from "./trystero-provider.js";
2
+ export { createIndexedDBProvider } from "./indexeddb.js";
@@ -0,0 +1,9 @@
1
+ import { IndexeddbPersistence } from "y-indexeddb";
2
+ import type * as Y from "yjs";
3
+
4
+ export function createIndexedDBProvider(
5
+ doc: Y.Doc,
6
+ roomName: string,
7
+ ): IndexeddbPersistence {
8
+ return new IndexeddbPersistence(roomName, doc);
9
+ }
@@ -0,0 +1,173 @@
1
+ import * as Y from "yjs";
2
+ import { Awareness, applyAwarenessUpdate, encodeAwarenessUpdate, removeAwarenessStates } from "y-protocols/awareness";
3
+ import * as syncProtocol from "y-protocols/sync";
4
+ import * as encoding from "lib0/encoding";
5
+ import * as decoding from "lib0/decoding";
6
+ import type { BaseRoomConfig, RelayConfig, Room, ActionSender, ActionReceiver } from "trystero";
7
+ import type { SignalingBackend } from "../types.js";
8
+
9
+ const MSG_SYNC = 0;
10
+ const MSG_AWARENESS = 1;
11
+
12
+ export interface TrysteroConfig {
13
+ backend?: SignalingBackend;
14
+ password?: string;
15
+ appId?: string;
16
+ relayUrls?: string[];
17
+ }
18
+
19
+ type JoinRoomFn = (config: BaseRoomConfig & RelayConfig, roomId: string) => Room;
20
+
21
+ export class TrysteroProvider {
22
+ readonly doc: Y.Doc;
23
+ readonly awareness: Awareness;
24
+ readonly roomName: string;
25
+ readonly connected: Promise<void>;
26
+
27
+ private _room: Room | null = null;
28
+ private _send: ActionSender<ArrayBuffer> | null = null;
29
+ private _peers = new Set<string>();
30
+ private _destroyed = false;
31
+ private _onDocUpdate: (update: Uint8Array, origin: unknown) => void;
32
+
33
+ constructor(doc: Y.Doc, roomName: string, config: TrysteroConfig = {}) {
34
+ this.doc = doc;
35
+ this.roomName = roomName;
36
+ this.awareness = new Awareness(doc);
37
+
38
+ this._onDocUpdate = (update: Uint8Array, origin: unknown) => {
39
+ if (origin === this) return;
40
+ const encoder = encoding.createEncoder();
41
+ encoding.writeVarUint(encoder, MSG_SYNC);
42
+ syncProtocol.writeUpdate(encoder, update);
43
+ this._broadcast(encoding.toUint8Array(encoder));
44
+ };
45
+
46
+ this.connected = this._connect(config);
47
+ }
48
+
49
+ private async _connect(config: TrysteroConfig): Promise<void> {
50
+ const backend = config.backend ?? "torrent";
51
+
52
+ let joinRoom: JoinRoomFn;
53
+ switch (backend) {
54
+ case "torrent": {
55
+ const mod = await import("trystero/torrent");
56
+ joinRoom = mod.joinRoom;
57
+ break;
58
+ }
59
+ case "nostr": {
60
+ const mod = await import("trystero/nostr");
61
+ joinRoom = mod.joinRoom;
62
+ break;
63
+ }
64
+ case "mqtt": {
65
+ const mod = await import("trystero/mqtt");
66
+ joinRoom = mod.joinRoom;
67
+ break;
68
+ }
69
+ }
70
+
71
+ const roomConfig: BaseRoomConfig & RelayConfig = {
72
+ appId: config.appId ?? "agent-p2p",
73
+ };
74
+ if (config.password) roomConfig.password = config.password;
75
+ if (config.relayUrls) roomConfig.relayUrls = config.relayUrls;
76
+
77
+ if (this._destroyed) return;
78
+
79
+ const room = joinRoom(roomConfig, this.roomName);
80
+ this._room = room;
81
+
82
+ const [send, receive] = room.makeAction<ArrayBuffer>("yjs-sync");
83
+ this._send = send;
84
+
85
+ (receive as ActionReceiver<ArrayBuffer>)((data: ArrayBuffer, peerId: string) => {
86
+ this._handleMessage(new Uint8Array(data), peerId);
87
+ });
88
+
89
+ room.onPeerJoin((peerId) => {
90
+ this._peers.add(peerId);
91
+ this._sendSyncStep1(peerId);
92
+ this._sendAwareness(peerId);
93
+ });
94
+
95
+ room.onPeerLeave((peerId) => {
96
+ this._peers.delete(peerId);
97
+ });
98
+
99
+ this.doc.on("update", this._onDocUpdate);
100
+
101
+ this.awareness.on(
102
+ "update",
103
+ ({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }) => {
104
+ const changedClients = [...added, ...updated, ...removed];
105
+ const encoder = encoding.createEncoder();
106
+ encoding.writeVarUint(encoder, MSG_AWARENESS);
107
+ encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, changedClients));
108
+ this._broadcast(encoding.toUint8Array(encoder));
109
+ },
110
+ );
111
+ }
112
+
113
+ private _sendSyncStep1(peerId: string): void {
114
+ const encoder = encoding.createEncoder();
115
+ encoding.writeVarUint(encoder, MSG_SYNC);
116
+ syncProtocol.writeSyncStep1(encoder, this.doc);
117
+ this._sendTo(encoding.toUint8Array(encoder), peerId);
118
+ }
119
+
120
+ private _sendAwareness(peerId: string): void {
121
+ const clients = Array.from(this.awareness.getStates().keys());
122
+ if (clients.length === 0) return;
123
+ const encoder = encoding.createEncoder();
124
+ encoding.writeVarUint(encoder, MSG_AWARENESS);
125
+ encoding.writeVarUint8Array(encoder, encodeAwarenessUpdate(this.awareness, clients));
126
+ this._sendTo(encoding.toUint8Array(encoder), peerId);
127
+ }
128
+
129
+ private _handleMessage(data: Uint8Array, peerId: string): void {
130
+ const decoder = decoding.createDecoder(data);
131
+ const msgType = decoding.readVarUint(decoder);
132
+
133
+ switch (msgType) {
134
+ case MSG_SYNC: {
135
+ const encoder = encoding.createEncoder();
136
+ encoding.writeVarUint(encoder, MSG_SYNC);
137
+ syncProtocol.readSyncMessage(decoder, encoder, this.doc, this);
138
+ if (encoding.length(encoder) > 1) {
139
+ this._sendTo(encoding.toUint8Array(encoder), peerId);
140
+ }
141
+ break;
142
+ }
143
+ case MSG_AWARENESS: {
144
+ const update = decoding.readVarUint8Array(decoder);
145
+ applyAwarenessUpdate(this.awareness, update, peerId);
146
+ break;
147
+ }
148
+ }
149
+ }
150
+
151
+ private _broadcast(data: Uint8Array): void {
152
+ if (this._send && this._peers.size > 0) {
153
+ this._send(data.buffer as ArrayBuffer);
154
+ }
155
+ }
156
+
157
+ private _sendTo(data: Uint8Array, peerId: string): void {
158
+ if (this._send) {
159
+ this._send(data.buffer as ArrayBuffer, peerId);
160
+ }
161
+ }
162
+
163
+ destroy(): void {
164
+ if (this._destroyed) return;
165
+ this._destroyed = true;
166
+ removeAwarenessStates(this.awareness, [this.doc.clientID], "provider destroyed");
167
+ this.doc.off("update", this._onDocUpdate);
168
+ this._room?.leave();
169
+ this._room = null;
170
+ this._send = null;
171
+ this._peers.clear();
172
+ }
173
+ }
package/src/types.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { SignalEnvelope, Keypair } from "@agent-p2p/core";
2
+
3
+ export type SignalingBackend = "torrent" | "nostr" | "mqtt";
4
+
5
+ export interface TransportConfig {
6
+ backend?: SignalingBackend;
7
+ /** Relay/tracker URLs (BitTorrent trackers, Nostr relays, or MQTT brokers) */
8
+ relayUrls?: string[];
9
+ }
10
+
11
+ export interface RadioPeerOptions {
12
+ password?: string;
13
+ maxSignalsPerChannel?: number;
14
+ enablePersistence?: boolean;
15
+ keypair?: Keypair;
16
+ transport?: TransportConfig;
17
+ }
18
+
19
+ export interface PeerInfo {
20
+ peerId: string;
21
+ joinedAt: number;
22
+ }
23
+
24
+ export type PeerEventMap = {
25
+ signal: SignalEnvelope;
26
+ "peer:join": PeerInfo;
27
+ "peer:leave": PeerInfo;
28
+ "permission:denied": { topic: string; action: "publish" | "read" };
29
+ "roles:changed": { peerId: string; role: string };
30
+ status: "connecting" | "connected" | "disconnected";
31
+ };
@@ -0,0 +1,25 @@
1
+ import type { PeerInfo } from "../types.js";
2
+
3
+ export interface AwarenessLike {
4
+ setLocalStateField(field: string, value: unknown): void;
5
+ getStates(): Map<number, Record<string, unknown>>;
6
+ on(event: string, fn: (...args: unknown[]) => void): void;
7
+ }
8
+
9
+ export function setLocalPeer(awareness: AwarenessLike, peerId: string): void {
10
+ awareness.setLocalStateField("peer", {
11
+ peerId,
12
+ joinedAt: Date.now(),
13
+ });
14
+ }
15
+
16
+ export function getConnectedPeers(awareness: AwarenessLike): PeerInfo[] {
17
+ const peers: PeerInfo[] = [];
18
+ for (const [, state] of awareness.getStates()) {
19
+ const peer = state.peer as PeerInfo | undefined;
20
+ if (peer) {
21
+ peers.push(peer);
22
+ }
23
+ }
24
+ return peers;
25
+ }
@@ -0,0 +1,60 @@
1
+ import * as Y from "yjs";
2
+ import type { SignalEnvelope } from "@agent-p2p/core";
3
+
4
+ export type PublishGuard = (peerId: string, topic: string) => boolean;
5
+
6
+ export class ChannelArray {
7
+ readonly array: Y.Array<SignalEnvelope>;
8
+ readonly topic: string;
9
+ private _maxSignals: number;
10
+ private _guard?: PublishGuard;
11
+
12
+ constructor(array: Y.Array<SignalEnvelope>, topic: string, maxSignals = 1000, guard?: PublishGuard) {
13
+ this.array = array;
14
+ this.topic = topic;
15
+ this._maxSignals = maxSignals;
16
+ this._guard = guard;
17
+ }
18
+
19
+ push(signal: SignalEnvelope): void {
20
+ if (this._guard && !this._guard(signal.provenance.peer_id, this.topic)) {
21
+ throw new Error(`Permission denied: cannot publish to "${this.topic}"`);
22
+ }
23
+ this.array.push([signal]);
24
+ this.trim();
25
+ }
26
+
27
+ getAll(): SignalEnvelope[] {
28
+ return this.array.toArray();
29
+ }
30
+
31
+ get length(): number {
32
+ return this.array.length;
33
+ }
34
+
35
+ observe(fn: (signals: SignalEnvelope[]) => void, filter?: (signal: SignalEnvelope) => boolean): () => void {
36
+ const handler = (event: Y.YArrayEvent<SignalEnvelope>) => {
37
+ const added: SignalEnvelope[] = [];
38
+ for (const item of event.changes.added) {
39
+ for (const content of item.content.getContent()) {
40
+ const signal = content as SignalEnvelope;
41
+ if (!filter || filter(signal)) {
42
+ added.push(signal);
43
+ }
44
+ }
45
+ }
46
+ if (added.length > 0) {
47
+ fn(added);
48
+ }
49
+ };
50
+ this.array.observe(handler);
51
+ return () => this.array.unobserve(handler);
52
+ }
53
+
54
+ private trim(): void {
55
+ if (this.array.length > this._maxSignals) {
56
+ const excess = this.array.length - this._maxSignals;
57
+ this.array.delete(0, excess);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,35 @@
1
+ import * as Y from "yjs";
2
+ import type { SignalEnvelope } from "@agent-p2p/core";
3
+ import { ChannelArray, type PublishGuard } from "./channel-array.js";
4
+
5
+ export class DocManager {
6
+ readonly doc: Y.Doc;
7
+ private _channels = new Map<string, ChannelArray>();
8
+ private _maxSignals: number;
9
+ private _guard?: PublishGuard;
10
+
11
+ constructor(maxSignals = 1000, guard?: PublishGuard) {
12
+ this.doc = new Y.Doc();
13
+ this._maxSignals = maxSignals;
14
+ this._guard = guard;
15
+ }
16
+
17
+ getChannel(topic: string): ChannelArray {
18
+ let ch = this._channels.get(topic);
19
+ if (!ch) {
20
+ const arr = this.doc.getArray<SignalEnvelope>(`ch:${topic}`);
21
+ ch = new ChannelArray(arr, topic, this._maxSignals, this._guard);
22
+ this._channels.set(topic, ch);
23
+ }
24
+ return ch;
25
+ }
26
+
27
+ getTopics(): string[] {
28
+ return Array.from(this._channels.keys());
29
+ }
30
+
31
+ destroy(): void {
32
+ this._channels.clear();
33
+ this.doc.destroy();
34
+ }
35
+ }
@@ -0,0 +1,3 @@
1
+ export { DocManager } from "./doc-manager.js";
2
+ export { ChannelArray, type PublishGuard } from "./channel-array.js";
3
+ export { setLocalPeer, getConnectedPeers } from "./awareness.js";