@interopio/bridge 0.0.1-alpha
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/bin/bridge.js +9 -0
- package/gen/instance/GeneratedBuildInfo.ts +4 -0
- package/license.md +5 -0
- package/package.json +40 -0
- package/readme.md +10 -0
- package/src/cluster/Address.ts +57 -0
- package/src/cluster/Cluster.ts +13 -0
- package/src/cluster/Endpoint.ts +5 -0
- package/src/cluster/Member.ts +9 -0
- package/src/cluster/MembershipListener.ts +6 -0
- package/src/config/Config.ts +100 -0
- package/src/config/DiscoveryConfig.ts +21 -0
- package/src/config/Duration.ts +168 -0
- package/src/config/KubernetesConfig.ts +7 -0
- package/src/config/NamedDiscoveryConfig.ts +17 -0
- package/src/config/Properties.ts +49 -0
- package/src/config/index.ts +1 -0
- package/src/discovery/SimpleDiscoveryNode.ts +14 -0
- package/src/discovery/index.ts +207 -0
- package/src/discovery/multicast/MulticastDiscoveryStrategy.ts +141 -0
- package/src/discovery/multicast/MulticastDiscoveryStrategyFactory.ts +30 -0
- package/src/discovery/multicast/MulticastProperties.ts +4 -0
- package/src/discovery/settings.ts +37 -0
- package/src/error/RequestFailure.ts +48 -0
- package/src/gossip/ApplicationState.ts +48 -0
- package/src/gossip/EndpointState.ts +141 -0
- package/src/gossip/FailureDetector.ts +235 -0
- package/src/gossip/Gossiper.ts +1133 -0
- package/src/gossip/HeartbeatState.ts +66 -0
- package/src/gossip/Messenger.ts +130 -0
- package/src/gossip/VersionedValue.ts +59 -0
- package/src/index.ts +3 -0
- package/src/instance/AddressPicker.ts +245 -0
- package/src/instance/BridgeNode.ts +141 -0
- package/src/instance/ClusterTopologyIntentTracker.ts +4 -0
- package/src/io/VersionedSerializer.ts +230 -0
- package/src/io/util.ts +117 -0
- package/src/kubernetes/DnsEndpointResolver.ts +70 -0
- package/src/kubernetes/KubernetesApiEndpointResolver.ts +111 -0
- package/src/kubernetes/KubernetesApiProvider.ts +75 -0
- package/src/kubernetes/KubernetesClient.ts +264 -0
- package/src/kubernetes/KubernetesConfig.ts +130 -0
- package/src/kubernetes/KubernetesDiscoveryStrategy.ts +30 -0
- package/src/kubernetes/KubernetesDiscoveryStrategyFactory.ts +71 -0
- package/src/kubernetes/KubernetesEndpointResolver.ts +43 -0
- package/src/kubernetes/KubernetesProperties.ts +22 -0
- package/src/license/BridgeLicenseValidator.ts +19 -0
- package/src/license/LicenseValidator.ts +114 -0
- package/src/license/types.ts +40 -0
- package/src/logging.ts +22 -0
- package/src/main.mts +53 -0
- package/src/net/Action.ts +143 -0
- package/src/net/AddressSerializer.ts +44 -0
- package/src/net/ByteBufferAllocator.ts +27 -0
- package/src/net/FrameDecoder.ts +314 -0
- package/src/net/FrameEncoder.ts +138 -0
- package/src/net/HandshakeProtocol.ts +143 -0
- package/src/net/InboundConnection.ts +108 -0
- package/src/net/InboundConnectionInitiator.ts +150 -0
- package/src/net/InboundMessageHandler.ts +377 -0
- package/src/net/InboundSink.ts +38 -0
- package/src/net/Message.ts +428 -0
- package/src/net/OutboundConnection.ts +1141 -0
- package/src/net/OutboundConnectionInitiator.ts +76 -0
- package/src/net/RequestCallbacks.ts +148 -0
- package/src/net/ResponseHandler.ts +30 -0
- package/src/net/ShareableBytes.ts +125 -0
- package/src/net/internal/AsyncResourceExecutor.ts +464 -0
- package/src/net/internal/AsyncSocketPromise.ts +37 -0
- package/src/net/internal/channel/ChannelHandlerAdapter.ts +99 -0
- package/src/net/internal/channel/types.ts +188 -0
- package/src/utils/bigint.ts +23 -0
- package/src/utils/buffer.ts +434 -0
- package/src/utils/clock.ts +148 -0
- package/src/utils/collections.ts +283 -0
- package/src/utils/crc.ts +39 -0
- package/src/utils/internal/IpAddressUtil.ts +161 -0
- package/src/utils/memory/BufferPools.ts +40 -0
- package/src/utils/network.ts +130 -0
- package/src/utils/promise.ts +38 -0
- package/src/utils/uuid.ts +5 -0
- package/src/utils/vint.ts +238 -0
- package/src/version/MemberVersion.ts +42 -0
- package/src/version/Version.ts +12 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {createConnection, Socket} from 'node:net';
|
|
2
|
+
import {Initiate, Accept} from './HandshakeProtocol.ts';
|
|
3
|
+
import {deferred, type DeferredPromise} from '../utils/promise.ts';
|
|
4
|
+
import {type OutboundConnectionSettings} from './OutboundConnection.ts';
|
|
5
|
+
import getLogger from '../logging.ts';
|
|
6
|
+
import {writeAndFlush} from './internal/AsyncSocketPromise.ts';
|
|
7
|
+
import {adopt} from '../utils/buffer.ts';
|
|
8
|
+
import {encoder, type PayloadAllocator} from './FrameEncoder.ts';
|
|
9
|
+
|
|
10
|
+
const logger = getLogger('net.outbound.initiator');
|
|
11
|
+
|
|
12
|
+
export type MessagingSuccess = {outcome: 'success', socket: Socket, version: number, allocator: PayloadAllocator, settings: OutboundConnectionSettings}
|
|
13
|
+
export type OutboundSocketInitiatorResultSuccess = MessagingSuccess;
|
|
14
|
+
export type OutboundSocketInitiatorResult<Success extends OutboundSocketInitiatorResultSuccess>
|
|
15
|
+
= OutboundSocketInitiatorResultSuccess
|
|
16
|
+
| { outcome: 'retry', withVersion: number }
|
|
17
|
+
| { outcome: 'incompatible' };
|
|
18
|
+
|
|
19
|
+
function outboundSocketInitiator<Success extends OutboundSocketInitiatorResultSuccess>(result: DeferredPromise<OutboundSocketInitiatorResult<Success>>, settings: OutboundConnectionSettings): DeferredPromise<OutboundSocketInitiatorResult<Success>> {
|
|
20
|
+
if (logger.enabledFor('debug')) {
|
|
21
|
+
logger.debug(`creating outbound socket to ${settings}`);
|
|
22
|
+
}
|
|
23
|
+
const {host, port} = settings.connectTo;
|
|
24
|
+
const socket = createConnection({port, host, keepAlive: true, noDelay: settings.tcpNoDelay});
|
|
25
|
+
const d = deferred<OutboundSocketInitiatorResult<Success>>();
|
|
26
|
+
socket
|
|
27
|
+
.on('connect', () => {
|
|
28
|
+
|
|
29
|
+
const initiate = new Initiate(settings.from, 'CRC', settings.acceptVersions);
|
|
30
|
+
logger.debug(`starting handshake to ${settings.connectToId}`);
|
|
31
|
+
const listener = (data: Buffer) => {
|
|
32
|
+
const accept = Accept.maybeDecode(adopt(data));
|
|
33
|
+
if (!accept) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let result: OutboundSocketInitiatorResult<Success>;
|
|
38
|
+
|
|
39
|
+
if (accept.useMessagingVersion < settings.acceptVersions.min || accept.useMessagingVersion > settings.acceptVersions.max) {
|
|
40
|
+
result = {outcome: 'incompatible'};
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
socket['encoder'] = encoder;
|
|
44
|
+
result = {
|
|
45
|
+
outcome: 'success',
|
|
46
|
+
socket,
|
|
47
|
+
version: accept.useMessagingVersion,
|
|
48
|
+
allocator: encoder.allocator,
|
|
49
|
+
settings
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (result.outcome === 'success') {
|
|
54
|
+
socket.off('data', listener);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
socket.destroy();
|
|
58
|
+
}
|
|
59
|
+
d.success(result);
|
|
60
|
+
};
|
|
61
|
+
socket.on('data', listener);
|
|
62
|
+
const promise = writeAndFlush(socket, {selfContained: true, buffer: initiate.encode()});
|
|
63
|
+
})
|
|
64
|
+
.on('error', (err: Error) => {
|
|
65
|
+
socket.destroy();
|
|
66
|
+
d.failure(err)
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
d.promise.then(result.success).catch(result.failure);
|
|
70
|
+
return d;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function initiateMessaging(settings: Required<OutboundConnectionSettings>, result: DeferredPromise<OutboundSocketInitiatorResult<MessagingSuccess>>): DeferredPromise<OutboundSocketInitiatorResult<MessagingSuccess>> {
|
|
74
|
+
const bootstrap = outboundSocketInitiator(result, settings);
|
|
75
|
+
return bootstrap;
|
|
76
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {type Address, addressAsString} from '../cluster/Address.ts';
|
|
2
|
+
import type {Messenger} from '../gossip/Messenger.ts';
|
|
3
|
+
import type {Message, MessageId} from './Message.ts';
|
|
4
|
+
import {ObjectMap} from '../utils/collections.ts';
|
|
5
|
+
import type {RequestFailureReason} from "../error/RequestFailure";
|
|
6
|
+
import {preciseClock} from '../utils/clock.ts';
|
|
7
|
+
import getLogger from '../logging.ts';
|
|
8
|
+
import type {OutboundMessageCallbacks} from "./OutboundConnection";
|
|
9
|
+
|
|
10
|
+
const logger = getLogger('net.callbacks');
|
|
11
|
+
|
|
12
|
+
type CallbackKey = Readonly<{id: MessageId, peer: Address}>;
|
|
13
|
+
|
|
14
|
+
class CallbackInfo {
|
|
15
|
+
readonly createdAtNanos: bigint;
|
|
16
|
+
readonly expiresAtNanos: bigint;
|
|
17
|
+
readonly peer: Address;
|
|
18
|
+
readonly callback: RequestCallback<unknown>;
|
|
19
|
+
constructor(message: Message<unknown>, peer: Address, callback: RequestCallback<unknown>) {
|
|
20
|
+
this.createdAtNanos = message.header.createdAtNanos;
|
|
21
|
+
this.expiresAtNanos = message.header.expiresAtNanos;
|
|
22
|
+
this.peer = peer;
|
|
23
|
+
this.callback = callback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
invokeOnFailure(): boolean {
|
|
27
|
+
return this.callback.onFailure !== undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
readyToDie(atNano: bigint) {
|
|
31
|
+
return atNano > this.expiresAtNanos;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
toString() {
|
|
35
|
+
return `{peer: "${addressAsString(this.peer)}", callback: ${this.callback}, invokeOnFailure: ${this.invokeOnFailure()}}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface RequestCallback<T> {
|
|
40
|
+
(message: Message<T>): void;
|
|
41
|
+
onFailure?(from: Address, failureReason: RequestFailureReason): void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type {CallbackInfo};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
export class RequestCallbacks implements OutboundMessageCallbacks {
|
|
48
|
+
private messaging: Messenger;
|
|
49
|
+
private readonly callbacks = new ObjectMap<CallbackKey, CallbackInfo>();
|
|
50
|
+
private readonly expireIntervalId: ReturnType<typeof setInterval>;
|
|
51
|
+
constructor(messaging: Messenger) {
|
|
52
|
+
this.messaging = messaging;
|
|
53
|
+
this.expireIntervalId = setInterval(() => {
|
|
54
|
+
this.expire();
|
|
55
|
+
}, 1000);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private removeAndExpire(id: MessageId, from: Address): void {
|
|
59
|
+
const callbackInfo = this.remove(id, from);
|
|
60
|
+
if (callbackInfo) {
|
|
61
|
+
this.onExpiredCallback(callbackInfo);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
private expire(): void {
|
|
65
|
+
const start = preciseClock.now();
|
|
66
|
+
let n = 0;
|
|
67
|
+
for (const [key, value] of this.callbacks) {
|
|
68
|
+
if (value.readyToDie(start)) {
|
|
69
|
+
if (this.callbacks.delete(key)) {
|
|
70
|
+
n++;
|
|
71
|
+
this.onExpiredCallback(value);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (logger.enabledFor('trace')) {
|
|
76
|
+
logger.debug(`Expired ${n} callbacks`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private forceExpire(): void {
|
|
81
|
+
for (const [key, value] of this.callbacks) {
|
|
82
|
+
if (this.callbacks.delete(key)) {
|
|
83
|
+
this.onExpiredCallback(value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private onExpiredCallback(info: CallbackInfo): void {
|
|
89
|
+
this.messaging.markExpiredCallback(info.peer);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
remove(id: MessageId, from: Address): CallbackInfo | undefined {
|
|
93
|
+
const key: CallbackKey = {id, peer: from};
|
|
94
|
+
const callbackInfo = this.callbacks.get(key);
|
|
95
|
+
if (callbackInfo) {
|
|
96
|
+
this.callbacks.delete(key);
|
|
97
|
+
}
|
|
98
|
+
return callbackInfo;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
addWithExpiration(callback: RequestCallback<unknown>, message: Message<unknown>, to: Address) {
|
|
102
|
+
const key: CallbackKey = {id: message.id, peer: to};
|
|
103
|
+
if (this.callbacks.has(key)) {
|
|
104
|
+
throw new Error(`Callback already exists for id ${message.id}/${addressAsString(to)}! (${this.callbacks.get(key)})`);
|
|
105
|
+
}
|
|
106
|
+
this.callbacks.set(key, new CallbackInfo(message, to, callback));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
shutdownNow(expire = false): void {
|
|
110
|
+
if (this.expireIntervalId) {
|
|
111
|
+
clearInterval(this.expireIntervalId);
|
|
112
|
+
}
|
|
113
|
+
if (expire) {
|
|
114
|
+
this.forceExpire();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
shutdown(): void {
|
|
119
|
+
this.expire();
|
|
120
|
+
if (this.callbacks.size !== 0) {
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
this.shutdown();
|
|
123
|
+
}, 100);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
clearInterval(this.expireIntervalId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
onExpired(message: Message<unknown>, peer: Address) {
|
|
132
|
+
this.removeAndExpireMessage(message, peer);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
onFailedSerialize(message: Message<unknown>, peer: Address, bytesWritten: number, error: Error) {
|
|
136
|
+
this.removeAndExpireMessage(message, peer);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onDiscardOnClose(message: Message<unknown>, peer: Address) {
|
|
140
|
+
this.removeAndExpireMessage(message, peer);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private removeAndExpireMessage(message: Message<unknown>, peer: Address): void {
|
|
144
|
+
this.removeAndExpire(message.header.seq, peer)
|
|
145
|
+
// todo forward info
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {fromId, type VerbHandler, type VerbHandlerContext} from './Action.ts';
|
|
2
|
+
import type {Message} from './Message.ts';
|
|
3
|
+
import {approximateClock} from '../utils/clock.ts';
|
|
4
|
+
import type {RequestFailureReason} from '../error/RequestFailure.ts';
|
|
5
|
+
import getLogger from '../logging.ts';
|
|
6
|
+
|
|
7
|
+
const logger = getLogger('net.response');
|
|
8
|
+
|
|
9
|
+
export function responseHandler<T>(ctx: VerbHandlerContext): VerbHandler<T> {
|
|
10
|
+
return (new class implements VerbHandler<T> {
|
|
11
|
+
doVerb(message: Message<T>): void {
|
|
12
|
+
const {id, from} = message;
|
|
13
|
+
const callbackInfo = ctx.messaging().callbacks.remove(id, from);
|
|
14
|
+
if (!callbackInfo) {
|
|
15
|
+
if (logger.enabledFor('debug')) {
|
|
16
|
+
logger.debug(`No callback (already removed?) for message ${fromId(message.header.verb().id)}@${id} from ${from}`);
|
|
17
|
+
}
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const latency = approximateClock().now() - callbackInfo.createdAtNanos;
|
|
21
|
+
const callback = callbackInfo.callback;
|
|
22
|
+
if (message.failureResponse && callbackInfo.invokeOnFailure()) {
|
|
23
|
+
callback.onFailure(from, message.payload as RequestFailureReason);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
callback(message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type {ByteBuffer} from '../utils/buffer.ts';
|
|
2
|
+
import {forNetworking} from '../utils/memory/BufferPools.ts';
|
|
3
|
+
|
|
4
|
+
class ShareableBytes {
|
|
5
|
+
private readonly bytes: ByteBuffer;
|
|
6
|
+
private readonly owner: ShareableBytes;
|
|
7
|
+
private count: number;
|
|
8
|
+
|
|
9
|
+
private static readonly UNSHARED = -1;
|
|
10
|
+
private static readonly RELEASED = 0;
|
|
11
|
+
|
|
12
|
+
constructor(bytes: ByteBuffer, owner?: ShareableBytes) {
|
|
13
|
+
if (owner === undefined) {
|
|
14
|
+
this.owner = this;
|
|
15
|
+
this.count = ShareableBytes.UNSHARED;
|
|
16
|
+
} else {
|
|
17
|
+
this.owner = owner;
|
|
18
|
+
this.count = ShareableBytes.RELEASED;
|
|
19
|
+
}
|
|
20
|
+
this.bytes = bytes;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public get(): ByteBuffer {
|
|
24
|
+
if (this.owner.count === ShareableBytes.RELEASED) {
|
|
25
|
+
throw new Error("assertion failed: ShareableBytes is released");
|
|
26
|
+
}
|
|
27
|
+
return this.bytes;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public get hasRemaining(): boolean {
|
|
31
|
+
return this.bytes.hasRemaining;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public get remaining(): number {
|
|
35
|
+
return this.bytes.remaining;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**/
|
|
39
|
+
skipBytes(n: number): void {
|
|
40
|
+
this.bytes.position += n;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**/
|
|
44
|
+
consume() {
|
|
45
|
+
this.bytes.position = this.bytes.limit;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public share() {
|
|
49
|
+
const count = this.owner.count;
|
|
50
|
+
if (count < 0) {
|
|
51
|
+
this.owner.count = -count;
|
|
52
|
+
}
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private retain() {
|
|
57
|
+
this.owner.doRetain();
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private doRetain() {
|
|
62
|
+
let count = this.count;
|
|
63
|
+
if (count < 0) {
|
|
64
|
+
this.count = count - 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
while (true) {
|
|
68
|
+
if (count === ShareableBytes.RELEASED) {
|
|
69
|
+
throw new Error("Attempted to reference an already released SharedBytes");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (this.count === count) {
|
|
73
|
+
this.count++;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
count = this.count;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public release() {
|
|
81
|
+
this.owner.doRelease();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private doRelease() {
|
|
85
|
+
let count = this.count;
|
|
86
|
+
if (count < 0) {
|
|
87
|
+
this.count = count += 1;
|
|
88
|
+
} else if (count > 0) {
|
|
89
|
+
count = --this.count;
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error("Attempted to release an already released SharedBytes");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (count === ShareableBytes.RELEASED) {
|
|
95
|
+
forNetworking().put(this.bytes);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**/
|
|
100
|
+
get released(): boolean {
|
|
101
|
+
return this.owner.count === ShareableBytes.RELEASED;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public sliceAndConsume(length: number): ShareableBytes {
|
|
105
|
+
const begin = this.bytes.position;
|
|
106
|
+
const end = begin + length;
|
|
107
|
+
const result = this.slice(begin, end);
|
|
108
|
+
this.bytes.position = end;
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**/
|
|
113
|
+
slice(begin: number, end: number): ShareableBytes {
|
|
114
|
+
const slice = this.bytes.duplicate();
|
|
115
|
+
slice.position = begin;
|
|
116
|
+
slice.limit = end;
|
|
117
|
+
return new ShareableBytes(slice, this.owner.retain());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type {ShareableBytes};
|
|
122
|
+
|
|
123
|
+
export function shareableBytes(bytes: ByteBuffer): ShareableBytes {
|
|
124
|
+
return new ShareableBytes(bytes);
|
|
125
|
+
}
|