@peerbit/shared-log-proxy 0.0.0-e209d2e
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.
- package/LICENSE +202 -0
- package/dist/src/auto.d.ts +18 -0
- package/dist/src/auto.d.ts.map +1 -0
- package/dist/src/auto.js +34 -0
- package/dist/src/auto.js.map +1 -0
- package/dist/src/client.d.ts +40 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +457 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/host.d.ts +19 -0
- package/dist/src/host.d.ts.map +1 -0
- package/dist/src/host.js +384 -0
- package/dist/src/host.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/protocol.d.ts +223 -0
- package/dist/src/protocol.d.ts.map +1 -0
- package/dist/src/protocol.js +1017 -0
- package/dist/src/protocol.js.map +1 -0
- package/package.json +100 -0
- package/src/auto.ts +40 -0
- package/src/client.ts +639 -0
- package/src/host.ts +472 -0
- package/src/index.ts +1 -0
- package/src/protocol.ts +617 -0
package/src/host.ts
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { deserialize, serialize } from "@dao-xyz/borsh";
|
|
2
|
+
import { type RpcTransport, bindService } from "@dao-xyz/borsh-rpc";
|
|
3
|
+
import {
|
|
4
|
+
type CanonicalChannel,
|
|
5
|
+
type CanonicalContext,
|
|
6
|
+
type CanonicalModule,
|
|
7
|
+
createMessagePortTransport,
|
|
8
|
+
} from "@peerbit/canonical-host";
|
|
9
|
+
import type { PublicSignKey } from "@peerbit/crypto";
|
|
10
|
+
import type { Query } from "@peerbit/indexer-interface";
|
|
11
|
+
import {
|
|
12
|
+
type FixedReplicationOptions,
|
|
13
|
+
type ReplicationOptions,
|
|
14
|
+
SharedLog,
|
|
15
|
+
type SharedLogEvents,
|
|
16
|
+
} from "@peerbit/shared-log";
|
|
17
|
+
import {
|
|
18
|
+
OpenSharedLogRequest,
|
|
19
|
+
SharedLogBytes,
|
|
20
|
+
SharedLogCoverageRequest,
|
|
21
|
+
SharedLogEntriesBatch,
|
|
22
|
+
SharedLogEntriesIteratorService,
|
|
23
|
+
SharedLogEvent,
|
|
24
|
+
SharedLogReplicateBool,
|
|
25
|
+
SharedLogReplicateFactor,
|
|
26
|
+
SharedLogReplicateFixed,
|
|
27
|
+
SharedLogReplicateFixedList,
|
|
28
|
+
SharedLogReplicateRequest,
|
|
29
|
+
SharedLogReplicateValue,
|
|
30
|
+
SharedLogReplicationBatch,
|
|
31
|
+
SharedLogReplicationCountRequest,
|
|
32
|
+
SharedLogReplicationIndexResult,
|
|
33
|
+
SharedLogReplicationIterateRequest,
|
|
34
|
+
SharedLogReplicationIteratorService,
|
|
35
|
+
SharedLogReplicationRange,
|
|
36
|
+
SharedLogService,
|
|
37
|
+
SharedLogUnreplicateRequest,
|
|
38
|
+
SharedLogWaitForReplicatorRequest,
|
|
39
|
+
SharedLogWaitForReplicatorsRequest,
|
|
40
|
+
} from "./protocol.js";
|
|
41
|
+
|
|
42
|
+
const ensureCustomEvent = () => {
|
|
43
|
+
if (typeof (globalThis as any).CustomEvent === "function") {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class CustomEventPolyfill<T = any> extends Event {
|
|
48
|
+
detail: T;
|
|
49
|
+
constructor(type: string, params?: CustomEventInit<T>) {
|
|
50
|
+
super(type, params);
|
|
51
|
+
this.detail = params?.detail as T;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
(globalThis as any).CustomEvent = CustomEventPolyfill;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const toHex = (bytes: Uint8Array): string => {
|
|
59
|
+
let out = "";
|
|
60
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const openLogs: Map<string, { program: SharedLog<any>; refs: number }> =
|
|
65
|
+
new Map();
|
|
66
|
+
|
|
67
|
+
export type SharedLogModuleStats = {
|
|
68
|
+
total: number;
|
|
69
|
+
entries: Array<{ key: string; refs: number }>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const getSharedLogModuleStats = (): SharedLogModuleStats => {
|
|
73
|
+
return {
|
|
74
|
+
total: openLogs.size,
|
|
75
|
+
entries: [...openLogs.entries()].map(([key, value]) => ({
|
|
76
|
+
key,
|
|
77
|
+
refs: value.refs,
|
|
78
|
+
})),
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const acquireSharedLog = async (properties: {
|
|
83
|
+
ctx: CanonicalContext;
|
|
84
|
+
id: Uint8Array;
|
|
85
|
+
}): Promise<{ program: SharedLog<any>; release: () => Promise<void> }> => {
|
|
86
|
+
const key = toHex(properties.id);
|
|
87
|
+
const existing = openLogs.get(key);
|
|
88
|
+
if (existing) {
|
|
89
|
+
existing.refs += 1;
|
|
90
|
+
return {
|
|
91
|
+
program: existing.program,
|
|
92
|
+
release: async () => releaseSharedLog(key),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const peer = await properties.ctx.peer();
|
|
97
|
+
const program = await peer.open(new SharedLog({ id: properties.id }), {
|
|
98
|
+
existing: "reuse",
|
|
99
|
+
args: { replicate: { factor: 1 } } as any,
|
|
100
|
+
});
|
|
101
|
+
openLogs.set(key, { program, refs: 1 });
|
|
102
|
+
return { program, release: async () => releaseSharedLog(key) };
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const releaseSharedLog = async (key: string): Promise<void> => {
|
|
106
|
+
const existing = openLogs.get(key);
|
|
107
|
+
if (!existing) return;
|
|
108
|
+
existing.refs -= 1;
|
|
109
|
+
if (existing.refs > 0) return;
|
|
110
|
+
openLogs.delete(key);
|
|
111
|
+
await existing.program.close();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const toFixedReplicationOptions = (
|
|
115
|
+
range: SharedLogReplicationRange,
|
|
116
|
+
): FixedReplicationOptions => {
|
|
117
|
+
const factor =
|
|
118
|
+
range.factorMode ?? (range.factor != null ? range.factor : undefined);
|
|
119
|
+
if (factor == null) {
|
|
120
|
+
throw new Error("Replication range missing factor");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const options: FixedReplicationOptions = {
|
|
124
|
+
factor: factor as any,
|
|
125
|
+
};
|
|
126
|
+
if (range.id) options.id = range.id;
|
|
127
|
+
if (range.offset != null) options.offset = range.offset;
|
|
128
|
+
if (range.normalized != null) options.normalized = range.normalized;
|
|
129
|
+
if (range.strict != null) options.strict = range.strict;
|
|
130
|
+
return options;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const toReplicationOptions = (
|
|
134
|
+
value?: SharedLogReplicateValue,
|
|
135
|
+
): ReplicationOptions<any> | undefined => {
|
|
136
|
+
if (!value) return undefined;
|
|
137
|
+
if (value instanceof SharedLogReplicateBool) {
|
|
138
|
+
return value.value;
|
|
139
|
+
}
|
|
140
|
+
if (value instanceof SharedLogReplicateFactor) {
|
|
141
|
+
return value.factor;
|
|
142
|
+
}
|
|
143
|
+
if (value instanceof SharedLogReplicateFixed) {
|
|
144
|
+
return toFixedReplicationOptions(value.range);
|
|
145
|
+
}
|
|
146
|
+
if (value instanceof SharedLogReplicateFixedList) {
|
|
147
|
+
return value.ranges.map((range) => toFixedReplicationOptions(range));
|
|
148
|
+
}
|
|
149
|
+
throw new Error("Unsupported replication value");
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const createSharedLogService = (
|
|
153
|
+
log: SharedLog<any>,
|
|
154
|
+
options?: { onClose?: () => Promise<void> | void },
|
|
155
|
+
): SharedLogService => {
|
|
156
|
+
ensureCustomEvent();
|
|
157
|
+
|
|
158
|
+
let closed = false;
|
|
159
|
+
const unsubscribe: Array<() => void> = [];
|
|
160
|
+
const waitControllers = new Map<string, AbortController>();
|
|
161
|
+
let service: SharedLogService;
|
|
162
|
+
|
|
163
|
+
service = new SharedLogService({
|
|
164
|
+
logGet: async (hash) => {
|
|
165
|
+
const entry = await log.log.get(hash);
|
|
166
|
+
return entry
|
|
167
|
+
? new SharedLogBytes({ value: serialize(entry as any) })
|
|
168
|
+
: undefined;
|
|
169
|
+
},
|
|
170
|
+
logHas: async (hash) => {
|
|
171
|
+
return log.log.has(hash);
|
|
172
|
+
},
|
|
173
|
+
logToArray: async () => {
|
|
174
|
+
const entries = await log.log.toArray();
|
|
175
|
+
return entries.map(
|
|
176
|
+
(entry: any) => new SharedLogBytes({ value: serialize(entry as any) }),
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
logGetHeads: async () => {
|
|
180
|
+
let iterator: any;
|
|
181
|
+
let done = false;
|
|
182
|
+
|
|
183
|
+
const closeIterator = async () => {
|
|
184
|
+
if (done) return;
|
|
185
|
+
done = true;
|
|
186
|
+
if (iterator) {
|
|
187
|
+
await iterator.close();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const updates = new SharedLogEntriesIteratorService({
|
|
192
|
+
next: async (amount) => {
|
|
193
|
+
if (!iterator) {
|
|
194
|
+
throw new Error("Shared log iterator not ready");
|
|
195
|
+
}
|
|
196
|
+
const items = await iterator.next(amount);
|
|
197
|
+
return new SharedLogEntriesBatch({
|
|
198
|
+
entries: (items ?? []).map(
|
|
199
|
+
(entry: any) =>
|
|
200
|
+
new SharedLogBytes({ value: serialize(entry as any) }),
|
|
201
|
+
),
|
|
202
|
+
done: iterator.done() ?? false,
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
pending: async () => undefined,
|
|
206
|
+
done: async () => {
|
|
207
|
+
if (!iterator) return false;
|
|
208
|
+
return iterator.done() ?? false;
|
|
209
|
+
},
|
|
210
|
+
close: async () => {
|
|
211
|
+
await closeIterator();
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
iterator = log.log.getHeads(true);
|
|
216
|
+
return updates;
|
|
217
|
+
},
|
|
218
|
+
logLength: async () => {
|
|
219
|
+
return BigInt(log.log.length);
|
|
220
|
+
},
|
|
221
|
+
logBlockHas: async (hash) => {
|
|
222
|
+
return log.log.blocks.has(hash);
|
|
223
|
+
},
|
|
224
|
+
replicationIterate: async (request: SharedLogReplicationIterateRequest) => {
|
|
225
|
+
const query = request.query.length
|
|
226
|
+
? (request.query as Query[])
|
|
227
|
+
: undefined;
|
|
228
|
+
const sort = request.sort.length ? request.sort : undefined;
|
|
229
|
+
let iterator: any;
|
|
230
|
+
let done = false;
|
|
231
|
+
|
|
232
|
+
const closeIterator = async () => {
|
|
233
|
+
if (done) return;
|
|
234
|
+
done = true;
|
|
235
|
+
if (iterator) {
|
|
236
|
+
await iterator.close();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const updates = new SharedLogReplicationIteratorService({
|
|
241
|
+
next: async (amount) => {
|
|
242
|
+
if (!iterator) {
|
|
243
|
+
throw new Error("Replication iterator not ready");
|
|
244
|
+
}
|
|
245
|
+
const items = await iterator.next(amount);
|
|
246
|
+
const results = (items ?? []).map(
|
|
247
|
+
(item: any) =>
|
|
248
|
+
new SharedLogReplicationIndexResult({
|
|
249
|
+
id: item.id,
|
|
250
|
+
value: new SharedLogBytes({
|
|
251
|
+
value: serialize(item.value as any),
|
|
252
|
+
}),
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
return new SharedLogReplicationBatch({
|
|
256
|
+
results,
|
|
257
|
+
done: iterator.done() ?? false,
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
pending: async () => {
|
|
261
|
+
if (!iterator) return undefined;
|
|
262
|
+
const pending = await iterator.pending();
|
|
263
|
+
return pending != null ? BigInt(pending) : undefined;
|
|
264
|
+
},
|
|
265
|
+
done: async () => {
|
|
266
|
+
if (!iterator) return false;
|
|
267
|
+
return iterator.done() ?? false;
|
|
268
|
+
},
|
|
269
|
+
close: async () => {
|
|
270
|
+
await closeIterator();
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
iterator = log.replicationIndex.iterate(
|
|
275
|
+
query || sort ? { query, sort } : undefined,
|
|
276
|
+
);
|
|
277
|
+
return updates;
|
|
278
|
+
},
|
|
279
|
+
replicationCount: async (request: SharedLogReplicationCountRequest) => {
|
|
280
|
+
const query = request.query.length ? request.query : undefined;
|
|
281
|
+
const count = await log.replicationIndex.count(
|
|
282
|
+
query ? { query } : undefined,
|
|
283
|
+
);
|
|
284
|
+
return BigInt(count);
|
|
285
|
+
},
|
|
286
|
+
getReplicators: async () => {
|
|
287
|
+
const replicators = await log.getReplicators();
|
|
288
|
+
return [...replicators];
|
|
289
|
+
},
|
|
290
|
+
waitForReplicator: async (request: SharedLogWaitForReplicatorRequest) => {
|
|
291
|
+
const requestId = request.requestId;
|
|
292
|
+
const controller = requestId ? new AbortController() : undefined;
|
|
293
|
+
if (requestId && controller) {
|
|
294
|
+
waitControllers.set(requestId, controller);
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
await log.waitForReplicator(request.publicKey, {
|
|
298
|
+
eager: request.eager,
|
|
299
|
+
timeout: request.timeoutMs,
|
|
300
|
+
roleAge: request.roleAgeMs,
|
|
301
|
+
signal: controller?.signal,
|
|
302
|
+
});
|
|
303
|
+
} finally {
|
|
304
|
+
if (requestId) waitControllers.delete(requestId);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
waitForReplicators: async (
|
|
308
|
+
request?: SharedLogWaitForReplicatorsRequest,
|
|
309
|
+
) => {
|
|
310
|
+
const requestId = request?.requestId;
|
|
311
|
+
const controller = requestId ? new AbortController() : undefined;
|
|
312
|
+
if (requestId && controller) {
|
|
313
|
+
waitControllers.set(requestId, controller);
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
await log.waitForReplicators({
|
|
317
|
+
timeout: request?.timeoutMs,
|
|
318
|
+
roleAge: request?.roleAgeMs,
|
|
319
|
+
coverageThreshold: request?.coverageThreshold,
|
|
320
|
+
waitForNewPeers: request?.waitForNewPeers,
|
|
321
|
+
signal: controller?.signal,
|
|
322
|
+
});
|
|
323
|
+
} finally {
|
|
324
|
+
if (requestId) waitControllers.delete(requestId);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
cancelWait: async (requestId: string) => {
|
|
328
|
+
const controller = waitControllers.get(requestId);
|
|
329
|
+
if (!controller) return;
|
|
330
|
+
waitControllers.delete(requestId);
|
|
331
|
+
try {
|
|
332
|
+
controller.abort(new Error("AbortError"));
|
|
333
|
+
} catch {
|
|
334
|
+
controller.abort();
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
replicate: async (request?: SharedLogReplicateRequest) => {
|
|
338
|
+
const range = toReplicationOptions(request?.value);
|
|
339
|
+
const hasOptions =
|
|
340
|
+
request?.reset != null ||
|
|
341
|
+
request?.checkDuplicates != null ||
|
|
342
|
+
request?.rebalance != null ||
|
|
343
|
+
request?.mergeSegments != null;
|
|
344
|
+
const options = hasOptions
|
|
345
|
+
? {
|
|
346
|
+
reset: request?.reset,
|
|
347
|
+
checkDuplicates: request?.checkDuplicates,
|
|
348
|
+
rebalance: request?.rebalance,
|
|
349
|
+
mergeSegments: request?.mergeSegments,
|
|
350
|
+
}
|
|
351
|
+
: undefined;
|
|
352
|
+
if (range === undefined) {
|
|
353
|
+
await log.replicate(undefined, options);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
await log.replicate(range, options);
|
|
357
|
+
},
|
|
358
|
+
unreplicate: async (request?: SharedLogUnreplicateRequest) => {
|
|
359
|
+
if (!request || request.ids.length === 0) {
|
|
360
|
+
await log.unreplicate();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
await log.unreplicate(request.ids.map((id) => ({ id })));
|
|
364
|
+
},
|
|
365
|
+
calculateCoverage: async (request?: SharedLogCoverageRequest) => {
|
|
366
|
+
return log.calculateCoverage({
|
|
367
|
+
start: request?.start as any,
|
|
368
|
+
end: request?.end as any,
|
|
369
|
+
roleAge: request?.roleAgeMs,
|
|
370
|
+
});
|
|
371
|
+
},
|
|
372
|
+
getMyReplicationSegments: async () => {
|
|
373
|
+
const ranges = await log.getMyReplicationSegments();
|
|
374
|
+
return ranges.map(
|
|
375
|
+
(range: any) => new SharedLogBytes({ value: serialize(range as any) }),
|
|
376
|
+
);
|
|
377
|
+
},
|
|
378
|
+
getAllReplicationSegments: async () => {
|
|
379
|
+
const ranges = await log.getAllReplicationSegments();
|
|
380
|
+
return ranges.map(
|
|
381
|
+
(range: any) => new SharedLogBytes({ value: serialize(range as any) }),
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
resolution: async () => {
|
|
385
|
+
return log.domain.resolution;
|
|
386
|
+
},
|
|
387
|
+
publicKey: async () => {
|
|
388
|
+
return log.node.identity.publicKey;
|
|
389
|
+
},
|
|
390
|
+
close: async () => {
|
|
391
|
+
if (closed) return;
|
|
392
|
+
closed = true;
|
|
393
|
+
for (const controller of waitControllers.values()) {
|
|
394
|
+
try {
|
|
395
|
+
controller.abort(new Error("SharedLogService closed"));
|
|
396
|
+
} catch {
|
|
397
|
+
controller.abort();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
waitControllers.clear();
|
|
401
|
+
for (const off of unsubscribe) {
|
|
402
|
+
off();
|
|
403
|
+
}
|
|
404
|
+
await options?.onClose?.();
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const forwardEvent = (type: string, key: PublicSignKey | undefined) => {
|
|
409
|
+
if (!key) return;
|
|
410
|
+
service.events.dispatchEvent(
|
|
411
|
+
new CustomEvent(type, {
|
|
412
|
+
detail: new SharedLogEvent({ publicKey: key }),
|
|
413
|
+
}),
|
|
414
|
+
);
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const attach = (type: keyof SharedLogEvents) => {
|
|
418
|
+
const handler = (event: any) => {
|
|
419
|
+
const detail = event?.detail;
|
|
420
|
+
const key = detail?.publicKey ?? detail;
|
|
421
|
+
forwardEvent(type, key as PublicSignKey | undefined);
|
|
422
|
+
};
|
|
423
|
+
log.events.addEventListener(type, handler as any);
|
|
424
|
+
unsubscribe.push(() =>
|
|
425
|
+
log.events.removeEventListener(type, handler as any),
|
|
426
|
+
);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
attach("join");
|
|
430
|
+
attach("leave");
|
|
431
|
+
attach("replicator:join");
|
|
432
|
+
attach("replicator:leave");
|
|
433
|
+
attach("replicator:mature");
|
|
434
|
+
attach("replication:change");
|
|
435
|
+
|
|
436
|
+
return service;
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
export const sharedLogModule: CanonicalModule = {
|
|
440
|
+
name: "@peerbit/shared-log",
|
|
441
|
+
open: async (
|
|
442
|
+
ctx: CanonicalContext,
|
|
443
|
+
port: CanonicalChannel,
|
|
444
|
+
payload: Uint8Array,
|
|
445
|
+
) => {
|
|
446
|
+
ensureCustomEvent();
|
|
447
|
+
|
|
448
|
+
const request = deserialize(payload, OpenSharedLogRequest);
|
|
449
|
+
const acquired = await acquireSharedLog({ ctx, id: request.id });
|
|
450
|
+
|
|
451
|
+
let unbind: (() => void) | undefined;
|
|
452
|
+
const transport: RpcTransport = createMessagePortTransport(port);
|
|
453
|
+
const service = createSharedLogService(acquired.program, {
|
|
454
|
+
onClose: async () => {
|
|
455
|
+
unbind?.();
|
|
456
|
+
await acquired.release();
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
unbind = bindService(SharedLogService, transport, service);
|
|
461
|
+
port.onClose?.(() => {
|
|
462
|
+
void service.close();
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
export const installSharedLogModule = (host: {
|
|
468
|
+
registerModule: (module: CanonicalModule) => void;
|
|
469
|
+
}): CanonicalModule => {
|
|
470
|
+
host.registerModule(sharedLogModule);
|
|
471
|
+
return sharedLogModule;
|
|
472
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./protocol.js";
|