@naeemo/capnp 0.2.0 → 0.3.0
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/README.md +5 -1
- package/README.zh.md +5 -1
- package/dist/codegen/cli-v3.js +206 -25
- package/dist/codegen/cli-v3.js.map +1 -1
- package/dist/index.cjs +2204 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2176 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -145,6 +145,12 @@ var Segment = class Segment {
|
|
|
145
145
|
return new Uint8Array(this.buffer, 0, this._size);
|
|
146
146
|
}
|
|
147
147
|
/**
|
|
148
|
+
* 获取底层 ArrayBuffer
|
|
149
|
+
*/
|
|
150
|
+
getArrayBuffer() {
|
|
151
|
+
return this.buffer;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
148
154
|
* 获取字数量
|
|
149
155
|
*/
|
|
150
156
|
get wordCount() {
|
|
@@ -825,21 +831,2219 @@ function createUnionBuilder(struct, tagOffset) {
|
|
|
825
831
|
}
|
|
826
832
|
|
|
827
833
|
//#endregion
|
|
834
|
+
//#region src/rpc/message-serializer.ts
|
|
835
|
+
/**
|
|
836
|
+
* RPC Message Serialization
|
|
837
|
+
*
|
|
838
|
+
* Implements full serialization/deserialization of RPC messages using
|
|
839
|
+
* the existing MessageBuilder/MessageReader infrastructure.
|
|
840
|
+
*
|
|
841
|
+
* Reference: rpc.capnp schema
|
|
842
|
+
*/
|
|
843
|
+
const MSG_UNIMPLEMENTED = 0;
|
|
844
|
+
const MSG_ABORT = 1;
|
|
845
|
+
const MSG_BOOTSTRAP = 8;
|
|
846
|
+
const MSG_CALL = 2;
|
|
847
|
+
const MSG_RETURN = 3;
|
|
848
|
+
const MSG_FINISH = 4;
|
|
849
|
+
const MSG_RESOLVE = 5;
|
|
850
|
+
const MSG_RELEASE = 6;
|
|
851
|
+
const MSG_DISEMBARGO = 13;
|
|
852
|
+
const MSG_PROVIDE = 10;
|
|
853
|
+
const MSG_ACCEPT = 11;
|
|
854
|
+
const MSG_JOIN = 12;
|
|
855
|
+
const RET_RESULTS = 0;
|
|
856
|
+
const RET_EXCEPTION = 1;
|
|
857
|
+
const RET_CANCELED = 2;
|
|
858
|
+
const RET_RESULTS_SENT_ELSEWHERE = 3;
|
|
859
|
+
const RET_TAKE_FROM_OTHER_QUESTION = 4;
|
|
860
|
+
const RET_ACCEPT_FROM_THIRD_PARTY = 5;
|
|
861
|
+
const SEND_TO_CALLER = 0;
|
|
862
|
+
const SEND_TO_YOURSELF = 1;
|
|
863
|
+
const SEND_TO_THIRD_PARTY = 2;
|
|
864
|
+
const TARGET_IMPORTED_CAP = 0;
|
|
865
|
+
const TARGET_PROMISED_ANSWER = 1;
|
|
866
|
+
const CAP_NONE = 0;
|
|
867
|
+
const CAP_SENDER_HOSTED = 1;
|
|
868
|
+
const CAP_SENDER_PROMISE = 2;
|
|
869
|
+
const CAP_RECEIVER_HOSTED = 3;
|
|
870
|
+
const CAP_RECEIVER_ANSWER = 4;
|
|
871
|
+
const CAP_THIRD_PARTY_HOSTED = 5;
|
|
872
|
+
const RESOLVE_CAP = 0;
|
|
873
|
+
const RESOLVE_EXCEPTION = 1;
|
|
874
|
+
const DISEMBARGO_SENDER_LOOPBACK = 0;
|
|
875
|
+
const DISEMBARGO_RECEIVER_LOOPBACK = 1;
|
|
876
|
+
const DISEMBARGO_ACCEPT = 2;
|
|
877
|
+
const DISEMBARGO_PROVIDE = 3;
|
|
878
|
+
const OP_NOOP = 0;
|
|
879
|
+
const OP_GET_POINTER_FIELD = 1;
|
|
880
|
+
const EXC_FAILED = 0;
|
|
881
|
+
const EXC_OVERLOADED = 1;
|
|
882
|
+
const EXC_DISCONNECTED = 2;
|
|
883
|
+
const EXC_UNIMPLEMENTED = 3;
|
|
884
|
+
function serializeRpcMessage(message) {
|
|
885
|
+
const builder = new MessageBuilder();
|
|
886
|
+
const root = builder.initRoot(6, 1);
|
|
887
|
+
switch (message.type) {
|
|
888
|
+
case "unimplemented":
|
|
889
|
+
serializeUnimplemented(root, message.message);
|
|
890
|
+
break;
|
|
891
|
+
case "abort":
|
|
892
|
+
serializeAbort(root, message.exception);
|
|
893
|
+
break;
|
|
894
|
+
case "bootstrap":
|
|
895
|
+
serializeBootstrap(root, message.bootstrap);
|
|
896
|
+
break;
|
|
897
|
+
case "call":
|
|
898
|
+
serializeCall(root, message.call);
|
|
899
|
+
break;
|
|
900
|
+
case "return":
|
|
901
|
+
serializeReturn(root, message.return);
|
|
902
|
+
break;
|
|
903
|
+
case "finish":
|
|
904
|
+
serializeFinish(root, message.finish);
|
|
905
|
+
break;
|
|
906
|
+
case "resolve":
|
|
907
|
+
serializeResolve(root, message.resolve);
|
|
908
|
+
break;
|
|
909
|
+
case "release":
|
|
910
|
+
serializeRelease(root, message.release);
|
|
911
|
+
break;
|
|
912
|
+
case "disembargo":
|
|
913
|
+
serializeDisembargo(root, message.disembargo);
|
|
914
|
+
break;
|
|
915
|
+
case "provide":
|
|
916
|
+
serializeProvide(root, message.provide);
|
|
917
|
+
break;
|
|
918
|
+
case "accept":
|
|
919
|
+
serializeAccept(root, message.accept);
|
|
920
|
+
break;
|
|
921
|
+
case "join":
|
|
922
|
+
serializeJoin(root, message.join);
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
return new Uint8Array(builder.toArrayBuffer());
|
|
926
|
+
}
|
|
927
|
+
function serializeUnimplemented(root, message) {
|
|
928
|
+
root.setUint16(0, MSG_UNIMPLEMENTED);
|
|
929
|
+
serializeRpcMessage(message);
|
|
930
|
+
root.initStruct(0, 0, 1).initStruct(0, 0, 0);
|
|
931
|
+
}
|
|
932
|
+
function serializeAbort(root, exception) {
|
|
933
|
+
root.setUint16(0, MSG_ABORT);
|
|
934
|
+
serializeException(root, 0, exception);
|
|
935
|
+
}
|
|
936
|
+
function serializeBootstrap(root, bootstrap) {
|
|
937
|
+
root.setUint16(0, MSG_BOOTSTRAP);
|
|
938
|
+
root.setUint32(8, bootstrap.questionId);
|
|
939
|
+
}
|
|
940
|
+
function serializeCall(root, call) {
|
|
941
|
+
root.setUint16(0, MSG_CALL);
|
|
942
|
+
root.setUint32(8, call.questionId);
|
|
943
|
+
root.setUint64(16, call.interfaceId);
|
|
944
|
+
root.setUint16(24, call.methodId);
|
|
945
|
+
root.setBool(208, call.allowThirdPartyTailCall);
|
|
946
|
+
root.setBool(209, call.noPromisePipelining);
|
|
947
|
+
root.setBool(210, call.onlyPromisePipeline);
|
|
948
|
+
serializeMessageTarget(root.initStruct(0, 2, 1), call.target);
|
|
949
|
+
serializePayload(root.initStruct(1, 2, 2), call.params);
|
|
950
|
+
serializeSendResultsTo(root.initStruct(2, 2, 1), call.sendResultsTo);
|
|
951
|
+
}
|
|
952
|
+
function serializeReturn(root, ret) {
|
|
953
|
+
root.setUint16(0, MSG_RETURN);
|
|
954
|
+
root.setUint32(8, ret.answerId);
|
|
955
|
+
root.setBool(192, ret.releaseParamCaps);
|
|
956
|
+
root.setBool(193, ret.noFinishNeeded);
|
|
957
|
+
switch (ret.result.type) {
|
|
958
|
+
case "results":
|
|
959
|
+
root.setUint16(2, RET_RESULTS);
|
|
960
|
+
serializePayload(root.initStruct(0, 2, 2), ret.result.payload);
|
|
961
|
+
break;
|
|
962
|
+
case "exception":
|
|
963
|
+
root.setUint16(2, RET_EXCEPTION);
|
|
964
|
+
serializeException(root, 0, ret.result.exception);
|
|
965
|
+
break;
|
|
966
|
+
case "canceled":
|
|
967
|
+
root.setUint16(2, RET_CANCELED);
|
|
968
|
+
break;
|
|
969
|
+
case "resultsSentElsewhere":
|
|
970
|
+
root.setUint16(2, RET_RESULTS_SENT_ELSEWHERE);
|
|
971
|
+
break;
|
|
972
|
+
case "takeFromOtherQuestion":
|
|
973
|
+
root.setUint16(2, RET_TAKE_FROM_OTHER_QUESTION);
|
|
974
|
+
root.setUint32(12, ret.result.questionId);
|
|
975
|
+
break;
|
|
976
|
+
case "acceptFromThirdParty":
|
|
977
|
+
root.setUint16(2, RET_ACCEPT_FROM_THIRD_PARTY);
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function serializeFinish(root, finish) {
|
|
982
|
+
root.setUint16(0, MSG_FINISH);
|
|
983
|
+
root.setUint32(8, finish.questionId);
|
|
984
|
+
root.setBool(192, finish.releaseResultCaps);
|
|
985
|
+
root.setBool(193, finish.requireEarlyCancellationWorkaround);
|
|
986
|
+
}
|
|
987
|
+
function serializeResolve(root, resolve) {
|
|
988
|
+
root.setUint16(0, MSG_RESOLVE);
|
|
989
|
+
root.setUint32(8, resolve.promiseId);
|
|
990
|
+
switch (resolve.resolution.type) {
|
|
991
|
+
case "cap":
|
|
992
|
+
root.setUint16(2, RESOLVE_CAP);
|
|
993
|
+
serializeCapDescriptor(root.initStruct(0, 2, 1), resolve.resolution.cap);
|
|
994
|
+
break;
|
|
995
|
+
case "exception":
|
|
996
|
+
root.setUint16(2, RESOLVE_EXCEPTION);
|
|
997
|
+
serializeException(root, 0, resolve.resolution.exception);
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function serializeRelease(root, release) {
|
|
1002
|
+
root.setUint16(0, MSG_RELEASE);
|
|
1003
|
+
root.setUint32(8, release.id);
|
|
1004
|
+
root.setUint32(12, release.referenceCount);
|
|
1005
|
+
}
|
|
1006
|
+
function serializeDisembargo(root, disembargo) {
|
|
1007
|
+
root.setUint16(0, MSG_DISEMBARGO);
|
|
1008
|
+
serializeMessageTarget(root.initStruct(0, 2, 1), disembargo.target);
|
|
1009
|
+
switch (disembargo.context.type) {
|
|
1010
|
+
case "senderLoopback":
|
|
1011
|
+
root.setUint16(2, DISEMBARGO_SENDER_LOOPBACK);
|
|
1012
|
+
root.setUint32(12, disembargo.context.embargoId);
|
|
1013
|
+
break;
|
|
1014
|
+
case "receiverLoopback":
|
|
1015
|
+
root.setUint16(2, DISEMBARGO_RECEIVER_LOOPBACK);
|
|
1016
|
+
root.setUint32(12, disembargo.context.embargoId);
|
|
1017
|
+
break;
|
|
1018
|
+
case "accept":
|
|
1019
|
+
root.setUint16(2, DISEMBARGO_ACCEPT);
|
|
1020
|
+
break;
|
|
1021
|
+
case "provide":
|
|
1022
|
+
root.setUint16(2, DISEMBARGO_PROVIDE);
|
|
1023
|
+
root.setUint32(12, disembargo.context.questionId);
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function serializeProvide(root, provide) {
|
|
1028
|
+
root.setUint16(0, MSG_PROVIDE);
|
|
1029
|
+
root.setUint32(8, provide.questionId);
|
|
1030
|
+
serializeMessageTarget(root.initStruct(0, 2, 1), provide.target);
|
|
1031
|
+
}
|
|
1032
|
+
function serializeAccept(_root, _accept) {}
|
|
1033
|
+
function serializeJoin(_root, _join) {}
|
|
1034
|
+
function serializeMessageTarget(builder, target) {
|
|
1035
|
+
switch (target.type) {
|
|
1036
|
+
case "importedCap":
|
|
1037
|
+
builder.setUint16(0, TARGET_IMPORTED_CAP);
|
|
1038
|
+
builder.setUint32(8, target.importId);
|
|
1039
|
+
break;
|
|
1040
|
+
case "promisedAnswer":
|
|
1041
|
+
builder.setUint16(0, TARGET_PROMISED_ANSWER);
|
|
1042
|
+
serializePromisedAnswer(builder.initStruct(0, 2, 1), target.promisedAnswer);
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function serializePromisedAnswer(builder, promisedAnswer) {
|
|
1047
|
+
builder.setUint32(0, promisedAnswer.questionId);
|
|
1048
|
+
if (promisedAnswer.transform.length > 0) {
|
|
1049
|
+
const listBuilder = builder.initList(0, ElementSize.INLINE_COMPOSITE, promisedAnswer.transform.length, {
|
|
1050
|
+
dataWords: 2,
|
|
1051
|
+
pointerCount: 0
|
|
1052
|
+
});
|
|
1053
|
+
for (let i = 0; i < promisedAnswer.transform.length; i++) serializePromisedAnswerOp(listBuilder.getStruct(i), promisedAnswer.transform[i]);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function serializePromisedAnswerOp(builder, op) {
|
|
1057
|
+
switch (op.type) {
|
|
1058
|
+
case "noop":
|
|
1059
|
+
builder.setUint16(0, OP_NOOP);
|
|
1060
|
+
break;
|
|
1061
|
+
case "getPointerField":
|
|
1062
|
+
builder.setUint16(0, OP_GET_POINTER_FIELD);
|
|
1063
|
+
builder.setUint16(8, op.fieldIndex);
|
|
1064
|
+
break;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function serializePayload(builder, payload) {
|
|
1068
|
+
if (payload.content.length > 0) builder.initStruct(0, Math.ceil(payload.content.length / 8), 0);
|
|
1069
|
+
if (payload.capTable.length > 0) {
|
|
1070
|
+
const listBuilder = builder.initList(1, ElementSize.EIGHT_BYTES, payload.capTable.length, {
|
|
1071
|
+
dataWords: 2,
|
|
1072
|
+
pointerCount: 1
|
|
1073
|
+
});
|
|
1074
|
+
for (let i = 0; i < payload.capTable.length; i++) serializeCapDescriptor(listBuilder.getStruct(i), payload.capTable[i]);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
function serializeCapDescriptor(builder, cap) {
|
|
1078
|
+
switch (cap.type) {
|
|
1079
|
+
case "none":
|
|
1080
|
+
builder.setUint16(0, CAP_NONE);
|
|
1081
|
+
break;
|
|
1082
|
+
case "senderHosted":
|
|
1083
|
+
builder.setUint16(0, CAP_SENDER_HOSTED);
|
|
1084
|
+
builder.setUint32(8, cap.exportId);
|
|
1085
|
+
break;
|
|
1086
|
+
case "senderPromise":
|
|
1087
|
+
builder.setUint16(0, CAP_SENDER_PROMISE);
|
|
1088
|
+
builder.setUint32(8, cap.exportId);
|
|
1089
|
+
break;
|
|
1090
|
+
case "receiverHosted":
|
|
1091
|
+
builder.setUint16(0, CAP_RECEIVER_HOSTED);
|
|
1092
|
+
builder.setUint32(8, cap.importId);
|
|
1093
|
+
break;
|
|
1094
|
+
case "receiverAnswer":
|
|
1095
|
+
builder.setUint16(0, CAP_RECEIVER_ANSWER);
|
|
1096
|
+
serializePromisedAnswer(builder.initStruct(0, 2, 1), cap.promisedAnswer);
|
|
1097
|
+
break;
|
|
1098
|
+
case "thirdPartyHosted":
|
|
1099
|
+
builder.setUint16(0, CAP_THIRD_PARTY_HOSTED);
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function serializeSendResultsTo(builder, sendTo) {
|
|
1104
|
+
switch (sendTo.type) {
|
|
1105
|
+
case "caller":
|
|
1106
|
+
builder.setUint16(0, SEND_TO_CALLER);
|
|
1107
|
+
break;
|
|
1108
|
+
case "yourself":
|
|
1109
|
+
builder.setUint16(0, SEND_TO_YOURSELF);
|
|
1110
|
+
break;
|
|
1111
|
+
case "thirdParty":
|
|
1112
|
+
builder.setUint16(0, SEND_TO_THIRD_PARTY);
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function serializeException(builder, pointerIndex, exception) {
|
|
1117
|
+
const excBuilder = builder.initStruct(pointerIndex, 2, 1);
|
|
1118
|
+
excBuilder.setText(0, exception.reason);
|
|
1119
|
+
switch (exception.type) {
|
|
1120
|
+
case "failed":
|
|
1121
|
+
excBuilder.setUint16(0, EXC_FAILED);
|
|
1122
|
+
break;
|
|
1123
|
+
case "overloaded":
|
|
1124
|
+
excBuilder.setUint16(0, EXC_OVERLOADED);
|
|
1125
|
+
break;
|
|
1126
|
+
case "disconnected":
|
|
1127
|
+
excBuilder.setUint16(0, EXC_DISCONNECTED);
|
|
1128
|
+
break;
|
|
1129
|
+
case "unimplemented":
|
|
1130
|
+
excBuilder.setUint16(0, EXC_UNIMPLEMENTED);
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function deserializeRpcMessage(data) {
|
|
1135
|
+
const root = new MessageReader(data).getRoot(6, 1);
|
|
1136
|
+
const unionTag = root.getUint16(0);
|
|
1137
|
+
switch (unionTag) {
|
|
1138
|
+
case MSG_UNIMPLEMENTED: return {
|
|
1139
|
+
type: "unimplemented",
|
|
1140
|
+
message: deserializeUnimplemented(root)
|
|
1141
|
+
};
|
|
1142
|
+
case MSG_ABORT: return {
|
|
1143
|
+
type: "abort",
|
|
1144
|
+
exception: deserializeException(root, 0)
|
|
1145
|
+
};
|
|
1146
|
+
case MSG_BOOTSTRAP: return {
|
|
1147
|
+
type: "bootstrap",
|
|
1148
|
+
bootstrap: deserializeBootstrap(root)
|
|
1149
|
+
};
|
|
1150
|
+
case MSG_CALL: return {
|
|
1151
|
+
type: "call",
|
|
1152
|
+
call: deserializeCall(root)
|
|
1153
|
+
};
|
|
1154
|
+
case MSG_RETURN: return {
|
|
1155
|
+
type: "return",
|
|
1156
|
+
return: deserializeReturn(root)
|
|
1157
|
+
};
|
|
1158
|
+
case MSG_FINISH: return {
|
|
1159
|
+
type: "finish",
|
|
1160
|
+
finish: deserializeFinish(root)
|
|
1161
|
+
};
|
|
1162
|
+
case MSG_RESOLVE: return {
|
|
1163
|
+
type: "resolve",
|
|
1164
|
+
resolve: deserializeResolve(root)
|
|
1165
|
+
};
|
|
1166
|
+
case MSG_RELEASE: return {
|
|
1167
|
+
type: "release",
|
|
1168
|
+
release: deserializeRelease(root)
|
|
1169
|
+
};
|
|
1170
|
+
case MSG_DISEMBARGO: return {
|
|
1171
|
+
type: "disembargo",
|
|
1172
|
+
disembargo: deserializeDisembargo(root)
|
|
1173
|
+
};
|
|
1174
|
+
case MSG_PROVIDE: return {
|
|
1175
|
+
type: "provide",
|
|
1176
|
+
provide: deserializeProvide(root)
|
|
1177
|
+
};
|
|
1178
|
+
case MSG_ACCEPT: return {
|
|
1179
|
+
type: "accept",
|
|
1180
|
+
accept: deserializeAccept(root)
|
|
1181
|
+
};
|
|
1182
|
+
case MSG_JOIN: return {
|
|
1183
|
+
type: "join",
|
|
1184
|
+
join: deserializeJoin(root)
|
|
1185
|
+
};
|
|
1186
|
+
default: throw new Error(`Unknown message union tag: ${unionTag}`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function deserializeUnimplemented(_root) {
|
|
1190
|
+
return {
|
|
1191
|
+
type: "abort",
|
|
1192
|
+
exception: {
|
|
1193
|
+
reason: "Unimplemented message received",
|
|
1194
|
+
type: "unimplemented"
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function deserializeBootstrap(root) {
|
|
1199
|
+
return { questionId: root.getUint32(8) };
|
|
1200
|
+
}
|
|
1201
|
+
function deserializeCall(root) {
|
|
1202
|
+
const targetStruct = root.getStruct(0, 2, 1);
|
|
1203
|
+
const paramsStruct = root.getStruct(1, 2, 2);
|
|
1204
|
+
const sendToStruct = root.getStruct(2, 2, 1);
|
|
1205
|
+
return {
|
|
1206
|
+
questionId: root.getUint32(8),
|
|
1207
|
+
interfaceId: root.getUint64(16),
|
|
1208
|
+
methodId: root.getUint16(24),
|
|
1209
|
+
allowThirdPartyTailCall: root.getBool(208),
|
|
1210
|
+
noPromisePipelining: root.getBool(209),
|
|
1211
|
+
onlyPromisePipeline: root.getBool(210),
|
|
1212
|
+
target: targetStruct ? deserializeMessageTarget(targetStruct) : {
|
|
1213
|
+
type: "importedCap",
|
|
1214
|
+
importId: 0
|
|
1215
|
+
},
|
|
1216
|
+
params: paramsStruct ? deserializePayload(paramsStruct) : {
|
|
1217
|
+
content: new Uint8Array(0),
|
|
1218
|
+
capTable: []
|
|
1219
|
+
},
|
|
1220
|
+
sendResultsTo: sendToStruct ? deserializeSendResultsTo(sendToStruct) : { type: "caller" }
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function deserializeReturn(root) {
|
|
1224
|
+
const resultTag = root.getUint16(2);
|
|
1225
|
+
let result;
|
|
1226
|
+
switch (resultTag) {
|
|
1227
|
+
case RET_RESULTS:
|
|
1228
|
+
result = {
|
|
1229
|
+
type: "results",
|
|
1230
|
+
payload: root.getStruct(0, 2, 2) ? deserializePayload(root.getStruct(0, 2, 2)) : {
|
|
1231
|
+
content: new Uint8Array(0),
|
|
1232
|
+
capTable: []
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
break;
|
|
1236
|
+
case RET_EXCEPTION:
|
|
1237
|
+
result = {
|
|
1238
|
+
type: "exception",
|
|
1239
|
+
exception: deserializeException(root, 0)
|
|
1240
|
+
};
|
|
1241
|
+
break;
|
|
1242
|
+
case RET_CANCELED:
|
|
1243
|
+
result = { type: "canceled" };
|
|
1244
|
+
break;
|
|
1245
|
+
case RET_RESULTS_SENT_ELSEWHERE:
|
|
1246
|
+
result = { type: "resultsSentElsewhere" };
|
|
1247
|
+
break;
|
|
1248
|
+
case RET_TAKE_FROM_OTHER_QUESTION:
|
|
1249
|
+
result = {
|
|
1250
|
+
type: "takeFromOtherQuestion",
|
|
1251
|
+
questionId: root.getUint32(12)
|
|
1252
|
+
};
|
|
1253
|
+
break;
|
|
1254
|
+
case RET_ACCEPT_FROM_THIRD_PARTY:
|
|
1255
|
+
result = {
|
|
1256
|
+
type: "acceptFromThirdParty",
|
|
1257
|
+
thirdPartyCapId: { id: new Uint8Array(0) }
|
|
1258
|
+
};
|
|
1259
|
+
break;
|
|
1260
|
+
default: result = { type: "canceled" };
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
answerId: root.getUint32(8),
|
|
1264
|
+
releaseParamCaps: root.getBool(192),
|
|
1265
|
+
noFinishNeeded: root.getBool(193),
|
|
1266
|
+
result
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function deserializeFinish(root) {
|
|
1270
|
+
return {
|
|
1271
|
+
questionId: root.getUint32(8),
|
|
1272
|
+
releaseResultCaps: root.getBool(192),
|
|
1273
|
+
requireEarlyCancellationWorkaround: root.getBool(193)
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function deserializeResolve(root) {
|
|
1277
|
+
const resolutionTag = root.getUint16(2);
|
|
1278
|
+
let resolution;
|
|
1279
|
+
switch (resolutionTag) {
|
|
1280
|
+
case RESOLVE_CAP:
|
|
1281
|
+
resolution = {
|
|
1282
|
+
type: "cap",
|
|
1283
|
+
cap: root.getStruct(0, 2, 1) ? deserializeCapDescriptor(root.getStruct(0, 2, 1)) : { type: "none" }
|
|
1284
|
+
};
|
|
1285
|
+
break;
|
|
1286
|
+
case RESOLVE_EXCEPTION:
|
|
1287
|
+
resolution = {
|
|
1288
|
+
type: "exception",
|
|
1289
|
+
exception: deserializeException(root, 0)
|
|
1290
|
+
};
|
|
1291
|
+
break;
|
|
1292
|
+
default: resolution = {
|
|
1293
|
+
type: "exception",
|
|
1294
|
+
exception: {
|
|
1295
|
+
reason: "Unknown resolution type",
|
|
1296
|
+
type: "failed"
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
return {
|
|
1301
|
+
promiseId: root.getUint32(8),
|
|
1302
|
+
resolution
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function deserializeRelease(root) {
|
|
1306
|
+
return {
|
|
1307
|
+
id: root.getUint32(8),
|
|
1308
|
+
referenceCount: root.getUint32(12)
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function deserializeDisembargo(root) {
|
|
1312
|
+
const targetStruct = root.getStruct(0, 2, 1);
|
|
1313
|
+
const contextTag = root.getUint16(2);
|
|
1314
|
+
let context;
|
|
1315
|
+
switch (contextTag) {
|
|
1316
|
+
case DISEMBARGO_SENDER_LOOPBACK:
|
|
1317
|
+
context = {
|
|
1318
|
+
type: "senderLoopback",
|
|
1319
|
+
embargoId: root.getUint32(12)
|
|
1320
|
+
};
|
|
1321
|
+
break;
|
|
1322
|
+
case DISEMBARGO_RECEIVER_LOOPBACK:
|
|
1323
|
+
context = {
|
|
1324
|
+
type: "receiverLoopback",
|
|
1325
|
+
embargoId: root.getUint32(12)
|
|
1326
|
+
};
|
|
1327
|
+
break;
|
|
1328
|
+
case DISEMBARGO_ACCEPT:
|
|
1329
|
+
context = { type: "accept" };
|
|
1330
|
+
break;
|
|
1331
|
+
case DISEMBARGO_PROVIDE:
|
|
1332
|
+
context = {
|
|
1333
|
+
type: "provide",
|
|
1334
|
+
questionId: root.getUint32(12)
|
|
1335
|
+
};
|
|
1336
|
+
break;
|
|
1337
|
+
default: context = { type: "accept" };
|
|
1338
|
+
}
|
|
1339
|
+
return {
|
|
1340
|
+
target: targetStruct ? deserializeMessageTarget(targetStruct) : {
|
|
1341
|
+
type: "importedCap",
|
|
1342
|
+
importId: 0
|
|
1343
|
+
},
|
|
1344
|
+
context
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
function deserializeProvide(root) {
|
|
1348
|
+
const targetStruct = root.getStruct(0, 2, 1);
|
|
1349
|
+
return {
|
|
1350
|
+
questionId: root.getUint32(8),
|
|
1351
|
+
target: targetStruct ? deserializeMessageTarget(targetStruct) : {
|
|
1352
|
+
type: "importedCap",
|
|
1353
|
+
importId: 0
|
|
1354
|
+
},
|
|
1355
|
+
recipient: { id: new Uint8Array(0) }
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
function deserializeAccept(_root) {
|
|
1359
|
+
return {
|
|
1360
|
+
questionId: 0,
|
|
1361
|
+
provision: { id: new Uint8Array(0) },
|
|
1362
|
+
embargo: false
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function deserializeJoin(_root) {
|
|
1366
|
+
return {
|
|
1367
|
+
questionId: 0,
|
|
1368
|
+
target: {
|
|
1369
|
+
type: "importedCap",
|
|
1370
|
+
importId: 0
|
|
1371
|
+
},
|
|
1372
|
+
otherCap: {
|
|
1373
|
+
type: "importedCap",
|
|
1374
|
+
importId: 0
|
|
1375
|
+
},
|
|
1376
|
+
joinId: 0
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
function deserializeMessageTarget(root) {
|
|
1380
|
+
switch (root.getUint16(0)) {
|
|
1381
|
+
case TARGET_IMPORTED_CAP: return {
|
|
1382
|
+
type: "importedCap",
|
|
1383
|
+
importId: root.getUint32(8)
|
|
1384
|
+
};
|
|
1385
|
+
case TARGET_PROMISED_ANSWER: {
|
|
1386
|
+
const promisedAnswerStruct = root.getStruct(0, 2, 1);
|
|
1387
|
+
return {
|
|
1388
|
+
type: "promisedAnswer",
|
|
1389
|
+
promisedAnswer: promisedAnswerStruct ? deserializePromisedAnswer(promisedAnswerStruct) : {
|
|
1390
|
+
questionId: 0,
|
|
1391
|
+
transform: []
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
default: return {
|
|
1396
|
+
type: "importedCap",
|
|
1397
|
+
importId: 0
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function deserializePromisedAnswer(root) {
|
|
1402
|
+
const transformList = root.getList(0, ElementSize.INLINE_COMPOSITE, {
|
|
1403
|
+
dataWords: 2,
|
|
1404
|
+
pointerCount: 0
|
|
1405
|
+
});
|
|
1406
|
+
const transform = [];
|
|
1407
|
+
if (transformList) for (let i = 0; i < transformList.length; i++) transform.push(deserializePromisedAnswerOp(transformList.getStruct(i)));
|
|
1408
|
+
return {
|
|
1409
|
+
questionId: root.getUint32(0),
|
|
1410
|
+
transform
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
function deserializePromisedAnswerOp(root) {
|
|
1414
|
+
switch (root.getUint16(0)) {
|
|
1415
|
+
case OP_NOOP: return { type: "noop" };
|
|
1416
|
+
case OP_GET_POINTER_FIELD: return {
|
|
1417
|
+
type: "getPointerField",
|
|
1418
|
+
fieldIndex: root.getUint16(8)
|
|
1419
|
+
};
|
|
1420
|
+
default: return { type: "noop" };
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function deserializePayload(root) {
|
|
1424
|
+
const capTableList = root.getList(1, ElementSize.EIGHT_BYTES, {
|
|
1425
|
+
dataWords: 2,
|
|
1426
|
+
pointerCount: 1
|
|
1427
|
+
});
|
|
1428
|
+
const capTable = [];
|
|
1429
|
+
if (capTableList) for (let i = 0; i < capTableList.length; i++) capTable.push(deserializeCapDescriptor(capTableList.getStruct(i)));
|
|
1430
|
+
return {
|
|
1431
|
+
content: new Uint8Array(0),
|
|
1432
|
+
capTable
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function deserializeCapDescriptor(root) {
|
|
1436
|
+
switch (root.getUint16(0)) {
|
|
1437
|
+
case CAP_NONE: return { type: "none" };
|
|
1438
|
+
case CAP_SENDER_HOSTED: return {
|
|
1439
|
+
type: "senderHosted",
|
|
1440
|
+
exportId: root.getUint32(8)
|
|
1441
|
+
};
|
|
1442
|
+
case CAP_SENDER_PROMISE: return {
|
|
1443
|
+
type: "senderPromise",
|
|
1444
|
+
exportId: root.getUint32(8)
|
|
1445
|
+
};
|
|
1446
|
+
case CAP_RECEIVER_HOSTED: return {
|
|
1447
|
+
type: "receiverHosted",
|
|
1448
|
+
importId: root.getUint32(8)
|
|
1449
|
+
};
|
|
1450
|
+
case CAP_RECEIVER_ANSWER: {
|
|
1451
|
+
const promisedAnswerStruct = root.getStruct(0, 2, 1);
|
|
1452
|
+
return {
|
|
1453
|
+
type: "receiverAnswer",
|
|
1454
|
+
promisedAnswer: promisedAnswerStruct ? deserializePromisedAnswer(promisedAnswerStruct) : {
|
|
1455
|
+
questionId: 0,
|
|
1456
|
+
transform: []
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
case CAP_THIRD_PARTY_HOSTED: return {
|
|
1461
|
+
type: "thirdPartyHosted",
|
|
1462
|
+
thirdPartyCapId: { id: new Uint8Array(0) }
|
|
1463
|
+
};
|
|
1464
|
+
default: return { type: "none" };
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function deserializeSendResultsTo(root) {
|
|
1468
|
+
switch (root.getUint16(0)) {
|
|
1469
|
+
case SEND_TO_CALLER: return { type: "caller" };
|
|
1470
|
+
case SEND_TO_YOURSELF: return { type: "yourself" };
|
|
1471
|
+
case SEND_TO_THIRD_PARTY: return {
|
|
1472
|
+
type: "thirdParty",
|
|
1473
|
+
recipientId: { id: new Uint8Array(0) }
|
|
1474
|
+
};
|
|
1475
|
+
default: return { type: "caller" };
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
function deserializeException(root, pointerIndex) {
|
|
1479
|
+
const excStruct = root.getStruct(pointerIndex, 2, 1);
|
|
1480
|
+
if (!excStruct) return {
|
|
1481
|
+
reason: "Unknown error",
|
|
1482
|
+
type: "failed"
|
|
1483
|
+
};
|
|
1484
|
+
const typeTag = excStruct.getUint16(0);
|
|
1485
|
+
let type;
|
|
1486
|
+
switch (typeTag) {
|
|
1487
|
+
case EXC_FAILED:
|
|
1488
|
+
type = "failed";
|
|
1489
|
+
break;
|
|
1490
|
+
case EXC_OVERLOADED:
|
|
1491
|
+
type = "overloaded";
|
|
1492
|
+
break;
|
|
1493
|
+
case EXC_DISCONNECTED:
|
|
1494
|
+
type = "disconnected";
|
|
1495
|
+
break;
|
|
1496
|
+
case EXC_UNIMPLEMENTED:
|
|
1497
|
+
type = "unimplemented";
|
|
1498
|
+
break;
|
|
1499
|
+
default: type = "failed";
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
reason: excStruct.getText(0),
|
|
1503
|
+
type
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
//#endregion
|
|
1508
|
+
//#region src/rpc/websocket-transport.ts
|
|
1509
|
+
/**
|
|
1510
|
+
* WebSocket Transport Implementation
|
|
1511
|
+
*
|
|
1512
|
+
* Implements RpcTransport over WebSocket for browser and Node.js compatibility.
|
|
1513
|
+
*/
|
|
1514
|
+
var WebSocketTransport = class WebSocketTransport {
|
|
1515
|
+
ws = null;
|
|
1516
|
+
messageQueue = [];
|
|
1517
|
+
receiveQueue = [];
|
|
1518
|
+
_connected = false;
|
|
1519
|
+
pendingBuffer = null;
|
|
1520
|
+
pendingLength = 0;
|
|
1521
|
+
onClose;
|
|
1522
|
+
onError;
|
|
1523
|
+
constructor(url, options = {}) {
|
|
1524
|
+
this.options = options;
|
|
1525
|
+
this.connect(url);
|
|
1526
|
+
}
|
|
1527
|
+
static async connect(url, options) {
|
|
1528
|
+
const transport = new WebSocketTransport(url, options);
|
|
1529
|
+
await transport.waitForConnection();
|
|
1530
|
+
return transport;
|
|
1531
|
+
}
|
|
1532
|
+
static fromWebSocket(ws, options) {
|
|
1533
|
+
const transport = new WebSocketTransport("internal", options);
|
|
1534
|
+
transport.attachWebSocket(ws);
|
|
1535
|
+
return transport;
|
|
1536
|
+
}
|
|
1537
|
+
get connected() {
|
|
1538
|
+
return this._connected;
|
|
1539
|
+
}
|
|
1540
|
+
connect(url) {
|
|
1541
|
+
this.ws = new WebSocket(url);
|
|
1542
|
+
this.ws.binaryType = this.options.binaryType ?? "arraybuffer";
|
|
1543
|
+
this.ws.onopen = () => {
|
|
1544
|
+
this._connected = true;
|
|
1545
|
+
};
|
|
1546
|
+
this.ws.onmessage = (event) => {
|
|
1547
|
+
this.handleMessage(event.data);
|
|
1548
|
+
};
|
|
1549
|
+
this.ws.onclose = () => {
|
|
1550
|
+
this._connected = false;
|
|
1551
|
+
this.flushReceiveQueue(null);
|
|
1552
|
+
this.onClose?.();
|
|
1553
|
+
};
|
|
1554
|
+
this.ws.onerror = (_error) => {
|
|
1555
|
+
const err = /* @__PURE__ */ new Error("WebSocket error");
|
|
1556
|
+
this.onError?.(err);
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
attachWebSocket(ws) {
|
|
1560
|
+
this.ws = ws;
|
|
1561
|
+
this.ws.binaryType = this.options.binaryType ?? "arraybuffer";
|
|
1562
|
+
this._connected = ws.readyState === WebSocket.OPEN;
|
|
1563
|
+
this.ws.onmessage = (event) => {
|
|
1564
|
+
this.handleMessage(event.data);
|
|
1565
|
+
};
|
|
1566
|
+
this.ws.onclose = () => {
|
|
1567
|
+
this._connected = false;
|
|
1568
|
+
this.flushReceiveQueue(null);
|
|
1569
|
+
this.onClose?.();
|
|
1570
|
+
};
|
|
1571
|
+
this.ws.onerror = (_error) => {
|
|
1572
|
+
const err = /* @__PURE__ */ new Error("WebSocket error");
|
|
1573
|
+
this.onError?.(err);
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
waitForConnection() {
|
|
1577
|
+
return new Promise((resolve, reject) => {
|
|
1578
|
+
if (this._connected) {
|
|
1579
|
+
resolve();
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
const timeout = setTimeout(() => {
|
|
1583
|
+
reject(/* @__PURE__ */ new Error("Connection timeout"));
|
|
1584
|
+
}, this.options.connectTimeoutMs ?? 1e4);
|
|
1585
|
+
const checkConnection = () => {
|
|
1586
|
+
if (this._connected) {
|
|
1587
|
+
clearTimeout(timeout);
|
|
1588
|
+
resolve();
|
|
1589
|
+
} else if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
|
|
1590
|
+
clearTimeout(timeout);
|
|
1591
|
+
reject(/* @__PURE__ */ new Error("Connection failed"));
|
|
1592
|
+
} else setTimeout(checkConnection, 10);
|
|
1593
|
+
};
|
|
1594
|
+
checkConnection();
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
handleMessage(data) {
|
|
1598
|
+
if (data instanceof ArrayBuffer) this.processBinaryMessage(new Uint8Array(data));
|
|
1599
|
+
else {
|
|
1600
|
+
const reader = new FileReader();
|
|
1601
|
+
reader.onload = () => {
|
|
1602
|
+
this.processBinaryMessage(new Uint8Array(reader.result));
|
|
1603
|
+
};
|
|
1604
|
+
reader.readAsArrayBuffer(data);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
processBinaryMessage(data) {
|
|
1608
|
+
let offset = 0;
|
|
1609
|
+
while (offset < data.length) if (this.pendingBuffer === null) {
|
|
1610
|
+
if (offset + 4 > data.length) {
|
|
1611
|
+
this.pendingBuffer = data.slice(offset);
|
|
1612
|
+
this.pendingLength = -1;
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1615
|
+
const length = new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true);
|
|
1616
|
+
offset += 4;
|
|
1617
|
+
if (offset + length > data.length) {
|
|
1618
|
+
this.pendingBuffer = data.slice(offset - 4);
|
|
1619
|
+
this.pendingLength = length;
|
|
1620
|
+
break;
|
|
1621
|
+
}
|
|
1622
|
+
const messageData = data.slice(offset, offset + length);
|
|
1623
|
+
offset += length;
|
|
1624
|
+
this.handleRpcMessage(messageData);
|
|
1625
|
+
} else if (this.pendingLength === -1) {
|
|
1626
|
+
const needed = 4 - this.pendingBuffer.length;
|
|
1627
|
+
if (data.length - offset < needed) {
|
|
1628
|
+
this.pendingBuffer = new Uint8Array([...this.pendingBuffer, ...data.slice(offset)]);
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
const tempBuffer = new Uint8Array(this.pendingBuffer.length + needed);
|
|
1632
|
+
tempBuffer.set(this.pendingBuffer);
|
|
1633
|
+
tempBuffer.set(data.slice(offset, offset + needed), this.pendingBuffer.length);
|
|
1634
|
+
this.pendingLength = new DataView(tempBuffer.buffer, 0, 4).getUint32(0, true);
|
|
1635
|
+
this.pendingBuffer = null;
|
|
1636
|
+
offset += needed;
|
|
1637
|
+
} else {
|
|
1638
|
+
const needed = this.pendingLength - this.pendingBuffer.length;
|
|
1639
|
+
if (data.length - offset < needed) {
|
|
1640
|
+
this.pendingBuffer = new Uint8Array([...this.pendingBuffer, ...data.slice(offset)]);
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
const messageData = new Uint8Array(this.pendingLength);
|
|
1644
|
+
messageData.set(this.pendingBuffer);
|
|
1645
|
+
messageData.set(data.slice(offset, offset + needed), this.pendingBuffer.length);
|
|
1646
|
+
offset += needed;
|
|
1647
|
+
this.pendingBuffer = null;
|
|
1648
|
+
this.handleRpcMessage(messageData);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
handleRpcMessage(data) {
|
|
1652
|
+
const message = this.deserializeMessage(data);
|
|
1653
|
+
if (this.receiveQueue.length > 0) {
|
|
1654
|
+
const { resolve } = this.receiveQueue.shift();
|
|
1655
|
+
resolve(message);
|
|
1656
|
+
} else this.messageQueue.push(message);
|
|
1657
|
+
}
|
|
1658
|
+
deserializeMessage(data) {
|
|
1659
|
+
return deserializeRpcMessage(data);
|
|
1660
|
+
}
|
|
1661
|
+
serializeMessage(message) {
|
|
1662
|
+
return serializeRpcMessage(message);
|
|
1663
|
+
}
|
|
1664
|
+
async send(message) {
|
|
1665
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error("WebSocket not connected");
|
|
1666
|
+
const data = this.serializeMessage(message);
|
|
1667
|
+
const frame = new Uint8Array(4 + data.length);
|
|
1668
|
+
new DataView(frame.buffer).setUint32(0, data.length, true);
|
|
1669
|
+
frame.set(data, 4);
|
|
1670
|
+
this.ws.send(frame);
|
|
1671
|
+
}
|
|
1672
|
+
async receive() {
|
|
1673
|
+
if (this.messageQueue.length > 0) return this.messageQueue.shift();
|
|
1674
|
+
if (!this._connected) return null;
|
|
1675
|
+
return new Promise((resolve, reject) => {
|
|
1676
|
+
this.receiveQueue.push({
|
|
1677
|
+
resolve,
|
|
1678
|
+
reject
|
|
1679
|
+
});
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
close(reason) {
|
|
1683
|
+
this._connected = false;
|
|
1684
|
+
this.ws?.close();
|
|
1685
|
+
this.flushReceiveQueue(null);
|
|
1686
|
+
this.onClose?.(reason);
|
|
1687
|
+
}
|
|
1688
|
+
flushReceiveQueue(value) {
|
|
1689
|
+
while (this.receiveQueue.length > 0) {
|
|
1690
|
+
const { resolve } = this.receiveQueue.shift();
|
|
1691
|
+
resolve(value);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
//#endregion
|
|
1697
|
+
//#region src/rpc/four-tables.ts
|
|
1698
|
+
/** Manages the question table for outbound calls */
|
|
1699
|
+
var QuestionTable = class {
|
|
1700
|
+
questions = /* @__PURE__ */ new Map();
|
|
1701
|
+
nextId = 1;
|
|
1702
|
+
/** Create a new question entry */
|
|
1703
|
+
create() {
|
|
1704
|
+
const id = this.allocateId();
|
|
1705
|
+
let resolveCompletion;
|
|
1706
|
+
let rejectCompletion;
|
|
1707
|
+
const question = {
|
|
1708
|
+
id,
|
|
1709
|
+
isComplete: false,
|
|
1710
|
+
finishSent: false,
|
|
1711
|
+
completionPromise: new Promise((resolve, reject) => {
|
|
1712
|
+
resolveCompletion = resolve;
|
|
1713
|
+
rejectCompletion = reject;
|
|
1714
|
+
}),
|
|
1715
|
+
resolveCompletion,
|
|
1716
|
+
rejectCompletion
|
|
1717
|
+
};
|
|
1718
|
+
this.questions.set(id, question);
|
|
1719
|
+
return question;
|
|
1720
|
+
}
|
|
1721
|
+
/** Get a question by ID */
|
|
1722
|
+
get(id) {
|
|
1723
|
+
return this.questions.get(id);
|
|
1724
|
+
}
|
|
1725
|
+
/** Mark a question as complete */
|
|
1726
|
+
complete(id, result) {
|
|
1727
|
+
const question = this.questions.get(id);
|
|
1728
|
+
if (question && !question.isComplete) {
|
|
1729
|
+
question.isComplete = true;
|
|
1730
|
+
question.resolveCompletion(result);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
/** Mark a question as canceled */
|
|
1734
|
+
cancel(id, error) {
|
|
1735
|
+
const question = this.questions.get(id);
|
|
1736
|
+
if (question && !question.isComplete) {
|
|
1737
|
+
question.isComplete = true;
|
|
1738
|
+
question.rejectCompletion(error);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
/** Mark that Finish has been sent for a question */
|
|
1742
|
+
markFinishSent(id) {
|
|
1743
|
+
const question = this.questions.get(id);
|
|
1744
|
+
if (question) question.finishSent = true;
|
|
1745
|
+
}
|
|
1746
|
+
/** Remove a question from the table (when both sides are done) */
|
|
1747
|
+
remove(id) {
|
|
1748
|
+
const question = this.questions.get(id);
|
|
1749
|
+
if (question?.isComplete && question.finishSent) this.questions.delete(id);
|
|
1750
|
+
}
|
|
1751
|
+
/** Clean up all questions (e.g., on disconnect) */
|
|
1752
|
+
clear() {
|
|
1753
|
+
for (const question of this.questions.values()) if (!question.isComplete) question.rejectCompletion(/* @__PURE__ */ new Error("Connection closed"));
|
|
1754
|
+
this.questions.clear();
|
|
1755
|
+
this.nextId = 1;
|
|
1756
|
+
}
|
|
1757
|
+
allocateId() {
|
|
1758
|
+
return this.nextId++;
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
/** Manages the answer table for inbound calls */
|
|
1762
|
+
var AnswerTable = class {
|
|
1763
|
+
answers = /* @__PURE__ */ new Map();
|
|
1764
|
+
/** Create a new answer entry */
|
|
1765
|
+
create(id) {
|
|
1766
|
+
const answer = {
|
|
1767
|
+
id,
|
|
1768
|
+
isComplete: false,
|
|
1769
|
+
returnSent: false,
|
|
1770
|
+
finishReceived: false
|
|
1771
|
+
};
|
|
1772
|
+
this.answers.set(id, answer);
|
|
1773
|
+
return answer;
|
|
1774
|
+
}
|
|
1775
|
+
/** Get an answer by ID */
|
|
1776
|
+
get(id) {
|
|
1777
|
+
return this.answers.get(id);
|
|
1778
|
+
}
|
|
1779
|
+
/** Mark that Return has been sent */
|
|
1780
|
+
markReturnSent(id) {
|
|
1781
|
+
const answer = this.answers.get(id);
|
|
1782
|
+
if (answer) answer.returnSent = true;
|
|
1783
|
+
}
|
|
1784
|
+
/** Mark that Finish has been received */
|
|
1785
|
+
markFinishReceived(id) {
|
|
1786
|
+
const answer = this.answers.get(id);
|
|
1787
|
+
if (answer) answer.finishReceived = true;
|
|
1788
|
+
}
|
|
1789
|
+
/** Remove an answer from the table (when both sides are done) */
|
|
1790
|
+
remove(id) {
|
|
1791
|
+
const answer = this.answers.get(id);
|
|
1792
|
+
if (answer?.returnSent && answer.finishReceived) this.answers.delete(id);
|
|
1793
|
+
}
|
|
1794
|
+
/** Clean up all answers (e.g., on disconnect) */
|
|
1795
|
+
clear() {
|
|
1796
|
+
this.answers.clear();
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
/** Manages the import table for capabilities received from remote */
|
|
1800
|
+
var ImportTable = class {
|
|
1801
|
+
imports = /* @__PURE__ */ new Map();
|
|
1802
|
+
/** Add a new import */
|
|
1803
|
+
add(id, isPromise) {
|
|
1804
|
+
const importEntry = {
|
|
1805
|
+
id,
|
|
1806
|
+
refCount: 1,
|
|
1807
|
+
isPromise
|
|
1808
|
+
};
|
|
1809
|
+
this.imports.set(id, importEntry);
|
|
1810
|
+
return importEntry;
|
|
1811
|
+
}
|
|
1812
|
+
/** Get an import by ID */
|
|
1813
|
+
get(id) {
|
|
1814
|
+
return this.imports.get(id);
|
|
1815
|
+
}
|
|
1816
|
+
/** Increment reference count */
|
|
1817
|
+
addRef(id) {
|
|
1818
|
+
const importEntry = this.imports.get(id);
|
|
1819
|
+
if (importEntry) importEntry.refCount++;
|
|
1820
|
+
}
|
|
1821
|
+
/** Decrement reference count, returns true if refCount reached 0 */
|
|
1822
|
+
release(id, count) {
|
|
1823
|
+
const importEntry = this.imports.get(id);
|
|
1824
|
+
if (importEntry) {
|
|
1825
|
+
importEntry.refCount -= count;
|
|
1826
|
+
if (importEntry.refCount <= 0) {
|
|
1827
|
+
this.imports.delete(id);
|
|
1828
|
+
return true;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
return false;
|
|
1832
|
+
}
|
|
1833
|
+
/** Mark a promise as resolved */
|
|
1834
|
+
markResolved(id) {
|
|
1835
|
+
const importEntry = this.imports.get(id);
|
|
1836
|
+
if (importEntry) importEntry.isPromise = false;
|
|
1837
|
+
}
|
|
1838
|
+
/** Clean up all imports (e.g., on disconnect) */
|
|
1839
|
+
clear() {
|
|
1840
|
+
this.imports.clear();
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
/** Manages the export table for capabilities sent to remote */
|
|
1844
|
+
var ExportTable = class {
|
|
1845
|
+
exports = /* @__PURE__ */ new Map();
|
|
1846
|
+
nextId = 1;
|
|
1847
|
+
/** Add a new export */
|
|
1848
|
+
add(capability, isPromise) {
|
|
1849
|
+
const id = this.allocateId();
|
|
1850
|
+
const exportEntry = {
|
|
1851
|
+
id,
|
|
1852
|
+
refCount: 1,
|
|
1853
|
+
isPromise,
|
|
1854
|
+
capability
|
|
1855
|
+
};
|
|
1856
|
+
this.exports.set(id, exportEntry);
|
|
1857
|
+
return exportEntry;
|
|
1858
|
+
}
|
|
1859
|
+
/** Get an export by ID */
|
|
1860
|
+
get(id) {
|
|
1861
|
+
return this.exports.get(id);
|
|
1862
|
+
}
|
|
1863
|
+
/** Increment reference count */
|
|
1864
|
+
addRef(id) {
|
|
1865
|
+
const exportEntry = this.exports.get(id);
|
|
1866
|
+
if (exportEntry) exportEntry.refCount++;
|
|
1867
|
+
}
|
|
1868
|
+
/** Decrement reference count, returns true if refCount reached 0 */
|
|
1869
|
+
release(id, count) {
|
|
1870
|
+
const exportEntry = this.exports.get(id);
|
|
1871
|
+
if (exportEntry) {
|
|
1872
|
+
exportEntry.refCount -= count;
|
|
1873
|
+
if (exportEntry.refCount <= 0) {
|
|
1874
|
+
this.exports.delete(id);
|
|
1875
|
+
return true;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
return false;
|
|
1879
|
+
}
|
|
1880
|
+
/** Mark a promise as resolved */
|
|
1881
|
+
markResolved(id) {
|
|
1882
|
+
const exportEntry = this.exports.get(id);
|
|
1883
|
+
if (exportEntry) exportEntry.isPromise = false;
|
|
1884
|
+
}
|
|
1885
|
+
/** Clean up all exports (e.g., on disconnect) */
|
|
1886
|
+
clear() {
|
|
1887
|
+
this.exports.clear();
|
|
1888
|
+
this.nextId = 1;
|
|
1889
|
+
}
|
|
1890
|
+
allocateId() {
|
|
1891
|
+
return this.nextId++;
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
|
|
1895
|
+
//#endregion
|
|
1896
|
+
//#region src/rpc/pipeline.ts
|
|
1897
|
+
/**
|
|
1898
|
+
* Tracks a chain of operations to apply to a promised answer.
|
|
1899
|
+
* This forms the "transform" field in PromisedAnswer.
|
|
1900
|
+
*/
|
|
1901
|
+
var PipelineOpTracker = class PipelineOpTracker {
|
|
1902
|
+
ops = [];
|
|
1903
|
+
/**
|
|
1904
|
+
* Add a no-op (use the result as-is)
|
|
1905
|
+
*/
|
|
1906
|
+
addNoop() {
|
|
1907
|
+
this.ops.push({ type: "noop" });
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Add a pointer field access operation
|
|
1911
|
+
*/
|
|
1912
|
+
addGetPointerField(fieldIndex) {
|
|
1913
|
+
this.ops.push({
|
|
1914
|
+
type: "getPointerField",
|
|
1915
|
+
fieldIndex
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Get the current transform chain
|
|
1920
|
+
*/
|
|
1921
|
+
getTransform() {
|
|
1922
|
+
return [...this.ops];
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Clone this tracker (for creating derived pipelines)
|
|
1926
|
+
*/
|
|
1927
|
+
clone() {
|
|
1928
|
+
const cloned = new PipelineOpTracker();
|
|
1929
|
+
cloned.ops = [...this.ops];
|
|
1930
|
+
return cloned;
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
/**
|
|
1934
|
+
* Symbol used to identify pipeline clients internally
|
|
1935
|
+
*/
|
|
1936
|
+
const PIPELINE_CLIENT_SYMBOL = Symbol("PipelineClient");
|
|
1937
|
+
/**
|
|
1938
|
+
* Creates a PipelineClient using JavaScript Proxy.
|
|
1939
|
+
* The proxy intercepts property accesses to build up the transform chain.
|
|
1940
|
+
*/
|
|
1941
|
+
function createPipelineClient(options) {
|
|
1942
|
+
const { connection, questionId, opTracker = new PipelineOpTracker() } = options;
|
|
1943
|
+
return {
|
|
1944
|
+
[PIPELINE_CLIENT_SYMBOL]: true,
|
|
1945
|
+
connection,
|
|
1946
|
+
questionId,
|
|
1947
|
+
opTracker,
|
|
1948
|
+
call(interfaceId, methodId, params) {
|
|
1949
|
+
return makePipelinedCall(connection, questionId, opTracker.getTransform(), interfaceId, methodId, params);
|
|
1950
|
+
},
|
|
1951
|
+
getPointerField(fieldIndex) {
|
|
1952
|
+
const newTracker = opTracker.clone();
|
|
1953
|
+
newTracker.addGetPointerField(fieldIndex);
|
|
1954
|
+
return createPipelineClient({
|
|
1955
|
+
connection,
|
|
1956
|
+
questionId,
|
|
1957
|
+
opTracker: newTracker
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Check if a value is a PipelineClient
|
|
1964
|
+
*/
|
|
1965
|
+
function isPipelineClient(value) {
|
|
1966
|
+
return typeof value === "object" && value !== null && PIPELINE_CLIENT_SYMBOL in value;
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Makes a call on a promised answer (pipeline call).
|
|
1970
|
+
* This sends a Call message with target.type = 'promisedAnswer'.
|
|
1971
|
+
*/
|
|
1972
|
+
async function makePipelinedCall(connection, questionId, transform, interfaceId, methodId, params) {
|
|
1973
|
+
const newQuestionId = connection.createQuestion();
|
|
1974
|
+
const call = {
|
|
1975
|
+
questionId: newQuestionId,
|
|
1976
|
+
target: {
|
|
1977
|
+
type: "promisedAnswer",
|
|
1978
|
+
promisedAnswer: {
|
|
1979
|
+
questionId,
|
|
1980
|
+
transform
|
|
1981
|
+
}
|
|
1982
|
+
},
|
|
1983
|
+
interfaceId,
|
|
1984
|
+
methodId,
|
|
1985
|
+
allowThirdPartyTailCall: false,
|
|
1986
|
+
noPromisePipelining: false,
|
|
1987
|
+
onlyPromisePipeline: false,
|
|
1988
|
+
params,
|
|
1989
|
+
sendResultsTo: { type: "caller" }
|
|
1990
|
+
};
|
|
1991
|
+
await connection.sendCall(call);
|
|
1992
|
+
return connection.waitForAnswer(newQuestionId);
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Manages calls that were made on a pipeline client before the answer arrived.
|
|
1996
|
+
* When the answer arrives, these calls are dispatched to the actual capability.
|
|
1997
|
+
*/
|
|
1998
|
+
var QueuedCallManager = class {
|
|
1999
|
+
queuedCalls = /* @__PURE__ */ new Map();
|
|
2000
|
+
/**
|
|
2001
|
+
* Queue a call for when the promise resolves
|
|
2002
|
+
*/
|
|
2003
|
+
queueCall(questionId, call) {
|
|
2004
|
+
const calls = this.queuedCalls.get(questionId) ?? [];
|
|
2005
|
+
calls.push(call);
|
|
2006
|
+
this.queuedCalls.set(questionId, calls);
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Get and clear all queued calls for a question
|
|
2010
|
+
*/
|
|
2011
|
+
dequeueCalls(questionId) {
|
|
2012
|
+
const calls = this.queuedCalls.get(questionId) ?? [];
|
|
2013
|
+
this.queuedCalls.delete(questionId);
|
|
2014
|
+
return calls;
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Check if there are queued calls for a question
|
|
2018
|
+
*/
|
|
2019
|
+
hasQueuedCalls(questionId) {
|
|
2020
|
+
return (this.queuedCalls.get(questionId)?.length ?? 0) > 0;
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Clear all queued calls (e.g., on disconnect)
|
|
2024
|
+
*/
|
|
2025
|
+
clear() {
|
|
2026
|
+
for (const calls of this.queuedCalls.values()) for (const call of calls) call.reject(/* @__PURE__ */ new Error("Connection closed"));
|
|
2027
|
+
this.queuedCalls.clear();
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
/**
|
|
2031
|
+
* Tracks pending pipeline resolutions
|
|
2032
|
+
*/
|
|
2033
|
+
var PipelineResolutionTracker = class {
|
|
2034
|
+
pendingResolutions = /* @__PURE__ */ new Map();
|
|
2035
|
+
/**
|
|
2036
|
+
* Mark a question as resolved to a capability
|
|
2037
|
+
*/
|
|
2038
|
+
resolveToCapability(questionId, importId) {
|
|
2039
|
+
this.pendingResolutions.set(questionId, {
|
|
2040
|
+
type: "capability",
|
|
2041
|
+
importId
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Mark a question as resolved to an exception
|
|
2046
|
+
*/
|
|
2047
|
+
resolveToException(questionId, reason) {
|
|
2048
|
+
this.pendingResolutions.set(questionId, {
|
|
2049
|
+
type: "exception",
|
|
2050
|
+
reason
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Get the resolution for a question (if available)
|
|
2055
|
+
*/
|
|
2056
|
+
getResolution(questionId) {
|
|
2057
|
+
return this.pendingResolutions.get(questionId);
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Check if a question has been resolved
|
|
2061
|
+
*/
|
|
2062
|
+
isResolved(questionId) {
|
|
2063
|
+
return this.pendingResolutions.has(questionId);
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Remove a resolution entry
|
|
2067
|
+
*/
|
|
2068
|
+
remove(questionId) {
|
|
2069
|
+
this.pendingResolutions.delete(questionId);
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Clear all resolutions
|
|
2073
|
+
*/
|
|
2074
|
+
clear() {
|
|
2075
|
+
this.pendingResolutions.clear();
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2079
|
+
//#endregion
|
|
2080
|
+
//#region src/rpc/rpc-connection.ts
|
|
2081
|
+
/**
|
|
2082
|
+
* RpcConnection
|
|
2083
|
+
*
|
|
2084
|
+
* Manages a single RPC connection, handling message routing and the Four Tables.
|
|
2085
|
+
* This is the core of the RPC implementation.
|
|
2086
|
+
*
|
|
2087
|
+
* Phase 2 Updates:
|
|
2088
|
+
* - Added Promise Pipelining support
|
|
2089
|
+
* - Added capability passing
|
|
2090
|
+
* - Added Resolve/Release/Disembargo message handling
|
|
2091
|
+
*/
|
|
2092
|
+
var RpcConnection = class {
|
|
2093
|
+
transport;
|
|
2094
|
+
options;
|
|
2095
|
+
questions = new QuestionTable();
|
|
2096
|
+
answers = new AnswerTable();
|
|
2097
|
+
imports = new ImportTable();
|
|
2098
|
+
exports = new ExportTable();
|
|
2099
|
+
queuedCalls = new QueuedCallManager();
|
|
2100
|
+
pipelineResolutions = new PipelineResolutionTracker();
|
|
2101
|
+
running = false;
|
|
2102
|
+
messageHandler;
|
|
2103
|
+
constructor(transport, options = {}) {
|
|
2104
|
+
this.transport = transport;
|
|
2105
|
+
this.options = options;
|
|
2106
|
+
this.transport.onClose = (reason) => {
|
|
2107
|
+
this.handleDisconnect(reason);
|
|
2108
|
+
};
|
|
2109
|
+
this.transport.onError = (error) => {
|
|
2110
|
+
this.handleError(error);
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
/** Start processing messages */
|
|
2114
|
+
async start() {
|
|
2115
|
+
if (this.running) return;
|
|
2116
|
+
this.running = true;
|
|
2117
|
+
this.messageHandler = this.messageLoop();
|
|
2118
|
+
}
|
|
2119
|
+
/** Stop the connection */
|
|
2120
|
+
async stop() {
|
|
2121
|
+
this.running = false;
|
|
2122
|
+
this.transport.close();
|
|
2123
|
+
if (this.messageHandler) try {
|
|
2124
|
+
await this.messageHandler;
|
|
2125
|
+
} catch {}
|
|
2126
|
+
}
|
|
2127
|
+
/** Send a bootstrap request and return the bootstrap capability */
|
|
2128
|
+
async bootstrap() {
|
|
2129
|
+
const question = this.questions.create();
|
|
2130
|
+
const bootstrapMsg = {
|
|
2131
|
+
type: "bootstrap",
|
|
2132
|
+
bootstrap: { questionId: question.id }
|
|
2133
|
+
};
|
|
2134
|
+
await this.transport.send(bootstrapMsg);
|
|
2135
|
+
await question.completionPromise;
|
|
2136
|
+
return {};
|
|
2137
|
+
}
|
|
2138
|
+
/** Make a call to a remote capability */
|
|
2139
|
+
async call(target, interfaceId, methodId, params) {
|
|
2140
|
+
if (isPipelineClient(target)) return target.call(interfaceId, methodId, params);
|
|
2141
|
+
const question = this.questions.create();
|
|
2142
|
+
const callMsg = {
|
|
2143
|
+
type: "call",
|
|
2144
|
+
call: {
|
|
2145
|
+
questionId: question.id,
|
|
2146
|
+
target: {
|
|
2147
|
+
type: "importedCap",
|
|
2148
|
+
importId: target
|
|
2149
|
+
},
|
|
2150
|
+
interfaceId,
|
|
2151
|
+
methodId,
|
|
2152
|
+
allowThirdPartyTailCall: false,
|
|
2153
|
+
noPromisePipelining: false,
|
|
2154
|
+
onlyPromisePipeline: false,
|
|
2155
|
+
params,
|
|
2156
|
+
sendResultsTo: { type: "caller" }
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
await this.transport.send(callMsg);
|
|
2160
|
+
return question.completionPromise;
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Make a call that returns a PipelineClient for promise pipelining.
|
|
2164
|
+
* This allows making calls on the result before it arrives.
|
|
2165
|
+
*/
|
|
2166
|
+
async callPipelined(target, interfaceId, methodId, params) {
|
|
2167
|
+
const question = this.questions.create();
|
|
2168
|
+
const callMsg = {
|
|
2169
|
+
type: "call",
|
|
2170
|
+
call: {
|
|
2171
|
+
questionId: question.id,
|
|
2172
|
+
target: {
|
|
2173
|
+
type: "importedCap",
|
|
2174
|
+
importId: target
|
|
2175
|
+
},
|
|
2176
|
+
interfaceId,
|
|
2177
|
+
methodId,
|
|
2178
|
+
allowThirdPartyTailCall: false,
|
|
2179
|
+
noPromisePipelining: false,
|
|
2180
|
+
onlyPromisePipeline: false,
|
|
2181
|
+
params,
|
|
2182
|
+
sendResultsTo: { type: "caller" }
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
await this.transport.send(callMsg);
|
|
2186
|
+
return createPipelineClient({
|
|
2187
|
+
connection: this,
|
|
2188
|
+
questionId: question.id
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
/** Send a finish message to release a question */
|
|
2192
|
+
async finish(questionId, releaseResultCaps = true) {
|
|
2193
|
+
if (!this.questions.get(questionId)) return;
|
|
2194
|
+
const finishMsg = {
|
|
2195
|
+
type: "finish",
|
|
2196
|
+
finish: {
|
|
2197
|
+
questionId,
|
|
2198
|
+
releaseResultCaps,
|
|
2199
|
+
requireEarlyCancellationWorkaround: false
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
await this.transport.send(finishMsg);
|
|
2203
|
+
this.questions.markFinishSent(questionId);
|
|
2204
|
+
this.questions.remove(questionId);
|
|
2205
|
+
}
|
|
2206
|
+
/** Send a release message for an imported capability */
|
|
2207
|
+
async release(importId, referenceCount = 1) {
|
|
2208
|
+
const releaseMsg = {
|
|
2209
|
+
type: "release",
|
|
2210
|
+
release: {
|
|
2211
|
+
id: importId,
|
|
2212
|
+
referenceCount
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
await this.transport.send(releaseMsg);
|
|
2216
|
+
}
|
|
2217
|
+
/** Send a resolve message to indicate a promise has resolved */
|
|
2218
|
+
async resolve(promiseId, cap) {
|
|
2219
|
+
const resolveMsg = {
|
|
2220
|
+
type: "resolve",
|
|
2221
|
+
resolve: {
|
|
2222
|
+
promiseId,
|
|
2223
|
+
resolution: {
|
|
2224
|
+
type: "cap",
|
|
2225
|
+
cap
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
await this.transport.send(resolveMsg);
|
|
2230
|
+
}
|
|
2231
|
+
/** Send a resolve message indicating a promise was broken */
|
|
2232
|
+
async resolveException(promiseId, reason) {
|
|
2233
|
+
const resolveMsg = {
|
|
2234
|
+
type: "resolve",
|
|
2235
|
+
resolve: {
|
|
2236
|
+
promiseId,
|
|
2237
|
+
resolution: {
|
|
2238
|
+
type: "exception",
|
|
2239
|
+
exception: {
|
|
2240
|
+
reason,
|
|
2241
|
+
type: "failed"
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
await this.transport.send(resolveMsg);
|
|
2247
|
+
}
|
|
2248
|
+
/** Internal method: Create a new question (used by pipeline) */
|
|
2249
|
+
createQuestion() {
|
|
2250
|
+
return this.questions.create().id;
|
|
2251
|
+
}
|
|
2252
|
+
/** Internal method: Send a call message (used by pipeline) */
|
|
2253
|
+
async sendCall(call) {
|
|
2254
|
+
const callMsg = {
|
|
2255
|
+
type: "call",
|
|
2256
|
+
call
|
|
2257
|
+
};
|
|
2258
|
+
await this.transport.send(callMsg);
|
|
2259
|
+
}
|
|
2260
|
+
/** Internal method: Wait for an answer (used by pipeline) */
|
|
2261
|
+
async waitForAnswer(questionId) {
|
|
2262
|
+
const question = this.questions.get(questionId);
|
|
2263
|
+
if (!question) throw new Error(`Question ${questionId} not found`);
|
|
2264
|
+
return question.completionPromise;
|
|
2265
|
+
}
|
|
2266
|
+
/** Main message processing loop */
|
|
2267
|
+
async messageLoop() {
|
|
2268
|
+
while (this.running) try {
|
|
2269
|
+
const message = await this.transport.receive();
|
|
2270
|
+
if (message === null) break;
|
|
2271
|
+
await this.handleMessage(message);
|
|
2272
|
+
} catch (error) {
|
|
2273
|
+
if (this.running) this.handleError(error);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
/** Handle incoming messages */
|
|
2277
|
+
async handleMessage(message) {
|
|
2278
|
+
switch (message.type) {
|
|
2279
|
+
case "bootstrap":
|
|
2280
|
+
await this.handleBootstrap(message.bootstrap);
|
|
2281
|
+
break;
|
|
2282
|
+
case "call":
|
|
2283
|
+
await this.handleCall(message.call);
|
|
2284
|
+
break;
|
|
2285
|
+
case "return":
|
|
2286
|
+
await this.handleReturn(message.return);
|
|
2287
|
+
break;
|
|
2288
|
+
case "finish":
|
|
2289
|
+
await this.handleFinish(message.finish);
|
|
2290
|
+
break;
|
|
2291
|
+
case "resolve":
|
|
2292
|
+
await this.handleResolve(message.resolve);
|
|
2293
|
+
break;
|
|
2294
|
+
case "release":
|
|
2295
|
+
await this.handleRelease(message.release);
|
|
2296
|
+
break;
|
|
2297
|
+
case "disembargo":
|
|
2298
|
+
await this.handleDisembargo(message.disembargo);
|
|
2299
|
+
break;
|
|
2300
|
+
case "abort":
|
|
2301
|
+
this.handleAbort(message.exception.reason);
|
|
2302
|
+
break;
|
|
2303
|
+
case "unimplemented": break;
|
|
2304
|
+
default: await this.sendUnimplemented(message);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
/** Handle bootstrap request */
|
|
2308
|
+
async handleBootstrap(bootstrap) {
|
|
2309
|
+
this.answers.create(bootstrap.questionId);
|
|
2310
|
+
const returnMsg = {
|
|
2311
|
+
type: "return",
|
|
2312
|
+
return: {
|
|
2313
|
+
answerId: bootstrap.questionId,
|
|
2314
|
+
releaseParamCaps: true,
|
|
2315
|
+
noFinishNeeded: false,
|
|
2316
|
+
result: {
|
|
2317
|
+
type: "results",
|
|
2318
|
+
payload: {
|
|
2319
|
+
content: new Uint8Array(0),
|
|
2320
|
+
capTable: []
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
await this.transport.send(returnMsg);
|
|
2326
|
+
this.answers.markReturnSent(bootstrap.questionId);
|
|
2327
|
+
}
|
|
2328
|
+
/** Handle incoming call */
|
|
2329
|
+
async handleCall(call) {
|
|
2330
|
+
this.answers.create(call.questionId);
|
|
2331
|
+
const returnMsg = {
|
|
2332
|
+
type: "return",
|
|
2333
|
+
return: {
|
|
2334
|
+
answerId: call.questionId,
|
|
2335
|
+
releaseParamCaps: true,
|
|
2336
|
+
noFinishNeeded: false,
|
|
2337
|
+
result: {
|
|
2338
|
+
type: "exception",
|
|
2339
|
+
exception: {
|
|
2340
|
+
reason: "Method not implemented",
|
|
2341
|
+
type: "unimplemented"
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
await this.transport.send(returnMsg);
|
|
2347
|
+
this.answers.markReturnSent(call.questionId);
|
|
2348
|
+
}
|
|
2349
|
+
/** Handle return message */
|
|
2350
|
+
async handleReturn(ret) {
|
|
2351
|
+
if (!this.questions.get(ret.answerId)) return;
|
|
2352
|
+
if (ret.result.type === "results") {
|
|
2353
|
+
const capTable = ret.result.payload.capTable;
|
|
2354
|
+
if (capTable.length > 0) {
|
|
2355
|
+
const cap = capTable[0];
|
|
2356
|
+
if (cap.type === "receiverHosted") this.pipelineResolutions.resolveToCapability(ret.answerId, cap.importId);
|
|
2357
|
+
}
|
|
2358
|
+
} else if (ret.result.type === "exception") this.pipelineResolutions.resolveToException(ret.answerId, ret.result.exception.reason);
|
|
2359
|
+
switch (ret.result.type) {
|
|
2360
|
+
case "results":
|
|
2361
|
+
this.questions.complete(ret.answerId, ret.result.payload);
|
|
2362
|
+
break;
|
|
2363
|
+
case "exception":
|
|
2364
|
+
this.questions.cancel(ret.answerId, new Error(ret.result.exception.reason));
|
|
2365
|
+
break;
|
|
2366
|
+
case "canceled":
|
|
2367
|
+
this.questions.cancel(ret.answerId, /* @__PURE__ */ new Error("Call canceled"));
|
|
2368
|
+
break;
|
|
2369
|
+
default: this.questions.cancel(ret.answerId, /* @__PURE__ */ new Error("Unknown return type"));
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
/** Handle finish message */
|
|
2373
|
+
async handleFinish(finish) {
|
|
2374
|
+
this.answers.markFinishReceived(finish.questionId);
|
|
2375
|
+
this.answers.remove(finish.questionId);
|
|
2376
|
+
}
|
|
2377
|
+
/** Handle resolve message (Level 1) */
|
|
2378
|
+
async handleResolve(resolve) {
|
|
2379
|
+
const { promiseId, resolution } = resolve;
|
|
2380
|
+
switch (resolution.type) {
|
|
2381
|
+
case "cap":
|
|
2382
|
+
this.imports.markResolved(promiseId);
|
|
2383
|
+
break;
|
|
2384
|
+
case "exception":
|
|
2385
|
+
console.warn(`Promise ${promiseId} broken: ${resolution.exception.reason}`);
|
|
2386
|
+
break;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
/** Handle release message (Level 1) */
|
|
2390
|
+
async handleRelease(release) {
|
|
2391
|
+
const { id, referenceCount } = release;
|
|
2392
|
+
if (this.exports.release(id, referenceCount)) console.log(`Export ${id} fully released`);
|
|
2393
|
+
}
|
|
2394
|
+
/** Handle disembargo message (Level 1) */
|
|
2395
|
+
async handleDisembargo(disembargo) {
|
|
2396
|
+
const { target, context } = disembargo;
|
|
2397
|
+
if (context.type === "senderLoopback") {
|
|
2398
|
+
const echoMsg = {
|
|
2399
|
+
type: "disembargo",
|
|
2400
|
+
disembargo: {
|
|
2401
|
+
target,
|
|
2402
|
+
context: {
|
|
2403
|
+
type: "receiverLoopback",
|
|
2404
|
+
embargoId: context.embargoId
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
await this.transport.send(echoMsg);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
/** Handle abort message */
|
|
2412
|
+
handleAbort(_reason) {
|
|
2413
|
+
this.running = false;
|
|
2414
|
+
this.questions.clear();
|
|
2415
|
+
this.answers.clear();
|
|
2416
|
+
this.imports.clear();
|
|
2417
|
+
this.exports.clear();
|
|
2418
|
+
this.queuedCalls.clear();
|
|
2419
|
+
this.pipelineResolutions.clear();
|
|
2420
|
+
}
|
|
2421
|
+
/** Handle disconnect */
|
|
2422
|
+
handleDisconnect(_reason) {
|
|
2423
|
+
this.running = false;
|
|
2424
|
+
this.questions.clear();
|
|
2425
|
+
this.answers.clear();
|
|
2426
|
+
this.imports.clear();
|
|
2427
|
+
this.exports.clear();
|
|
2428
|
+
this.queuedCalls.clear();
|
|
2429
|
+
this.pipelineResolutions.clear();
|
|
2430
|
+
}
|
|
2431
|
+
/** Handle error */
|
|
2432
|
+
handleError(error) {
|
|
2433
|
+
console.error("RPC error:", error);
|
|
2434
|
+
}
|
|
2435
|
+
/** Send unimplemented response */
|
|
2436
|
+
async sendUnimplemented(originalMessage) {
|
|
2437
|
+
const msg = {
|
|
2438
|
+
type: "unimplemented",
|
|
2439
|
+
message: originalMessage
|
|
2440
|
+
};
|
|
2441
|
+
await this.transport.send(msg);
|
|
2442
|
+
}
|
|
2443
|
+
/** Import a capability from the remote peer */
|
|
2444
|
+
importCapability(importId, isPromise = false) {
|
|
2445
|
+
this.imports.add(importId, isPromise);
|
|
2446
|
+
}
|
|
2447
|
+
/** Export a capability to the remote peer */
|
|
2448
|
+
exportCapability(capability, isPromise = false) {
|
|
2449
|
+
return this.exports.add(capability, isPromise).id;
|
|
2450
|
+
}
|
|
2451
|
+
/** Get an imported capability */
|
|
2452
|
+
getImport(importId) {
|
|
2453
|
+
return this.imports.get(importId);
|
|
2454
|
+
}
|
|
2455
|
+
/** Get an exported capability */
|
|
2456
|
+
getExport(exportId) {
|
|
2457
|
+
return this.exports.get(exportId);
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
|
|
2461
|
+
//#endregion
|
|
2462
|
+
//#region src/rpc/capability-client.ts
|
|
2463
|
+
/** Base class for capability client implementations */
|
|
2464
|
+
var BaseCapabilityClient = class {
|
|
2465
|
+
constructor(connection, importId) {
|
|
2466
|
+
this.connection = connection;
|
|
2467
|
+
this.importId = importId;
|
|
2468
|
+
}
|
|
2469
|
+
isValid() {
|
|
2470
|
+
return true;
|
|
2471
|
+
}
|
|
2472
|
+
release() {
|
|
2473
|
+
if (this.importId !== void 0) this.connection.release(this.importId, 1);
|
|
2474
|
+
}
|
|
2475
|
+
/** Make a method call on this capability and return a PipelineClient */
|
|
2476
|
+
_call(_interfaceId, _methodId, params) {
|
|
2477
|
+
if (!this.importId) throw new Error("Cannot call method on capability without import ID");
|
|
2478
|
+
this.serializeParams(params);
|
|
2479
|
+
throw new Error("Use _callAsync instead for async call support");
|
|
2480
|
+
}
|
|
2481
|
+
/** Make an async method call on this capability */
|
|
2482
|
+
async _callAsync(interfaceId, methodId, params) {
|
|
2483
|
+
if (!this.importId) throw new Error("Cannot call method on capability without import ID");
|
|
2484
|
+
const payload = this.serializeParams(params);
|
|
2485
|
+
return this.connection.callPipelined(this.importId, interfaceId, methodId, payload);
|
|
2486
|
+
}
|
|
2487
|
+
/** Serialize parameters to Payload */
|
|
2488
|
+
serializeParams(_params) {
|
|
2489
|
+
return {
|
|
2490
|
+
content: new Uint8Array(),
|
|
2491
|
+
capTable: []
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
|
|
2496
|
+
//#endregion
|
|
2497
|
+
//#region src/rpc/sturdyrefs.ts
|
|
2498
|
+
/**
|
|
2499
|
+
* Manages SturdyRefs on the server side.
|
|
2500
|
+
* Stores the mapping between SturdyRef tokens and live capabilities.
|
|
2501
|
+
*/
|
|
2502
|
+
var SturdyRefManager = class {
|
|
2503
|
+
vatId;
|
|
2504
|
+
storedRefs = /* @__PURE__ */ new Map();
|
|
2505
|
+
localIdCounter = 0;
|
|
2506
|
+
constructor(vatId) {
|
|
2507
|
+
this.vatId = vatId;
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Save a capability as a SturdyRef
|
|
2511
|
+
*/
|
|
2512
|
+
saveCapability(capability, exportId, options) {
|
|
2513
|
+
const localId = options?.localId ?? this.generateLocalId();
|
|
2514
|
+
const now = Date.now();
|
|
2515
|
+
const ref = {
|
|
2516
|
+
vatId: this.vatId,
|
|
2517
|
+
localId,
|
|
2518
|
+
version: 1,
|
|
2519
|
+
expiresAt: options?.ttlMs ? now + options.ttlMs : void 0,
|
|
2520
|
+
metadata: options?.metadata
|
|
2521
|
+
};
|
|
2522
|
+
const stored = {
|
|
2523
|
+
ref,
|
|
2524
|
+
exportId,
|
|
2525
|
+
capability,
|
|
2526
|
+
createdAt: now,
|
|
2527
|
+
lastAccessedAt: now
|
|
2528
|
+
};
|
|
2529
|
+
this.storedRefs.set(localId, stored);
|
|
2530
|
+
return ref;
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Restore a capability from a SturdyRef token
|
|
2534
|
+
*/
|
|
2535
|
+
restoreCapability(ref) {
|
|
2536
|
+
if (ref.vatId !== this.vatId) {
|
|
2537
|
+
console.warn(`SturdyRef vatId mismatch: ${ref.vatId} !== ${this.vatId}`);
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
const stored = this.storedRefs.get(ref.localId);
|
|
2541
|
+
if (!stored) {
|
|
2542
|
+
console.warn(`SturdyRef not found: ${ref.localId}`);
|
|
2543
|
+
return null;
|
|
2544
|
+
}
|
|
2545
|
+
if (stored.ref.expiresAt && Date.now() > stored.ref.expiresAt) {
|
|
2546
|
+
console.warn(`SturdyRef expired: ${ref.localId}`);
|
|
2547
|
+
this.storedRefs.delete(ref.localId);
|
|
2548
|
+
return null;
|
|
2549
|
+
}
|
|
2550
|
+
stored.lastAccessedAt = Date.now();
|
|
2551
|
+
return {
|
|
2552
|
+
capability: stored.capability,
|
|
2553
|
+
exportId: stored.exportId
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Drop a SturdyRef
|
|
2558
|
+
*/
|
|
2559
|
+
dropSturdyRef(localId) {
|
|
2560
|
+
return this.storedRefs.delete(localId);
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Get all active SturdyRefs
|
|
2564
|
+
*/
|
|
2565
|
+
getActiveRefs() {
|
|
2566
|
+
const now = Date.now();
|
|
2567
|
+
const active = [];
|
|
2568
|
+
for (const [localId, stored] of this.storedRefs) {
|
|
2569
|
+
if (stored.ref.expiresAt && now > stored.ref.expiresAt) {
|
|
2570
|
+
this.storedRefs.delete(localId);
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
active.push(stored.ref);
|
|
2574
|
+
}
|
|
2575
|
+
return active;
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Clean up expired SturdyRefs
|
|
2579
|
+
*/
|
|
2580
|
+
cleanupExpired() {
|
|
2581
|
+
const now = Date.now();
|
|
2582
|
+
let cleaned = 0;
|
|
2583
|
+
for (const [localId, stored] of this.storedRefs) if (stored.ref.expiresAt && now > stored.ref.expiresAt) {
|
|
2584
|
+
this.storedRefs.delete(localId);
|
|
2585
|
+
cleaned++;
|
|
2586
|
+
}
|
|
2587
|
+
return cleaned;
|
|
2588
|
+
}
|
|
2589
|
+
generateLocalId() {
|
|
2590
|
+
return `ref-${++this.localIdCounter}-${Date.now()}`;
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
/**
|
|
2594
|
+
* Handles Restore messages on the client side.
|
|
2595
|
+
* Manages reconnecting to capabilities after disconnections.
|
|
2596
|
+
*/
|
|
2597
|
+
var RestoreHandler = class {
|
|
2598
|
+
connection;
|
|
2599
|
+
pendingRestores = /* @__PURE__ */ new Map();
|
|
2600
|
+
questionIdCounter = 0;
|
|
2601
|
+
constructor(connection) {
|
|
2602
|
+
this.connection = connection;
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Send a Restore message to restore a capability from a SturdyRef
|
|
2606
|
+
*/
|
|
2607
|
+
async restore(ref, options) {
|
|
2608
|
+
const questionId = ++this.questionIdCounter;
|
|
2609
|
+
const timeoutMs = options?.timeoutMs ?? 3e4;
|
|
2610
|
+
return new Promise((resolve, reject) => {
|
|
2611
|
+
const timeout = setTimeout(() => {
|
|
2612
|
+
this.pendingRestores.delete(questionId);
|
|
2613
|
+
reject(/* @__PURE__ */ new Error(`Restore timeout after ${timeoutMs}ms`));
|
|
2614
|
+
}, timeoutMs);
|
|
2615
|
+
this.pendingRestores.set(questionId, {
|
|
2616
|
+
resolve,
|
|
2617
|
+
reject,
|
|
2618
|
+
timeout
|
|
2619
|
+
});
|
|
2620
|
+
this.sendRestoreMessage(questionId, ref).catch((error) => {
|
|
2621
|
+
clearTimeout(timeout);
|
|
2622
|
+
this.pendingRestores.delete(questionId);
|
|
2623
|
+
reject(error);
|
|
2624
|
+
});
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Handle a Restore response
|
|
2629
|
+
*/
|
|
2630
|
+
handleRestoreResponse(questionId, importId) {
|
|
2631
|
+
const pending = this.pendingRestores.get(questionId);
|
|
2632
|
+
if (pending) {
|
|
2633
|
+
clearTimeout(pending.timeout);
|
|
2634
|
+
this.pendingRestores.delete(questionId);
|
|
2635
|
+
pending.resolve(importId);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Handle a Restore failure
|
|
2640
|
+
*/
|
|
2641
|
+
handleRestoreFailure(questionId, reason) {
|
|
2642
|
+
const pending = this.pendingRestores.get(questionId);
|
|
2643
|
+
if (pending) {
|
|
2644
|
+
clearTimeout(pending.timeout);
|
|
2645
|
+
this.pendingRestores.delete(questionId);
|
|
2646
|
+
pending.reject(/* @__PURE__ */ new Error(`Restore failed: ${reason}`));
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Cancel all pending restores (e.g., on disconnect)
|
|
2651
|
+
*/
|
|
2652
|
+
cancelAll(reason) {
|
|
2653
|
+
for (const [_questionId, pending] of this.pendingRestores) {
|
|
2654
|
+
clearTimeout(pending.timeout);
|
|
2655
|
+
pending.reject(/* @__PURE__ */ new Error(`Restore canceled: ${reason}`));
|
|
2656
|
+
}
|
|
2657
|
+
this.pendingRestores.clear();
|
|
2658
|
+
}
|
|
2659
|
+
async sendRestoreMessage(questionId, ref) {
|
|
2660
|
+
const refData = JSON.stringify(ref);
|
|
2661
|
+
const restoreMsg = {
|
|
2662
|
+
type: "call",
|
|
2663
|
+
call: {
|
|
2664
|
+
questionId,
|
|
2665
|
+
target: {
|
|
2666
|
+
type: "importedCap",
|
|
2667
|
+
importId: 0
|
|
2668
|
+
},
|
|
2669
|
+
interfaceId: BigInt("0xffffffffffffffff"),
|
|
2670
|
+
methodId: 0,
|
|
2671
|
+
allowThirdPartyTailCall: false,
|
|
2672
|
+
noPromisePipelining: false,
|
|
2673
|
+
onlyPromisePipeline: false,
|
|
2674
|
+
params: {
|
|
2675
|
+
content: new TextEncoder().encode(refData),
|
|
2676
|
+
capTable: []
|
|
2677
|
+
},
|
|
2678
|
+
sendResultsTo: { type: "caller" }
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
console.log("Sending restore message:", restoreMsg);
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
/**
|
|
2685
|
+
* Serialize a SturdyRef to a string for storage
|
|
2686
|
+
*/
|
|
2687
|
+
function serializeSturdyRef(ref) {
|
|
2688
|
+
return JSON.stringify(ref);
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Deserialize a SturdyRef from a string
|
|
2692
|
+
*/
|
|
2693
|
+
function deserializeSturdyRef(data) {
|
|
2694
|
+
try {
|
|
2695
|
+
const parsed = JSON.parse(data);
|
|
2696
|
+
if (typeof parsed.vatId !== "string" || typeof parsed.localId !== "string") return null;
|
|
2697
|
+
return {
|
|
2698
|
+
vatId: parsed.vatId,
|
|
2699
|
+
localId: parsed.localId,
|
|
2700
|
+
version: parsed.version,
|
|
2701
|
+
expiresAt: parsed.expiresAt,
|
|
2702
|
+
metadata: parsed.metadata
|
|
2703
|
+
};
|
|
2704
|
+
} catch {
|
|
2705
|
+
return null;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Check if a SturdyRef is valid (not expired)
|
|
2710
|
+
*/
|
|
2711
|
+
function isSturdyRefValid(ref) {
|
|
2712
|
+
if (ref.expiresAt && Date.now() > ref.expiresAt) return false;
|
|
2713
|
+
return true;
|
|
2714
|
+
}
|
|
2715
|
+
/**
|
|
2716
|
+
* Create a SturdyRef from components
|
|
2717
|
+
*/
|
|
2718
|
+
function createSturdyRef(vatId, localId, options) {
|
|
2719
|
+
return {
|
|
2720
|
+
vatId,
|
|
2721
|
+
localId,
|
|
2722
|
+
version: 1,
|
|
2723
|
+
expiresAt: options?.ttlMs ? Date.now() + options.ttlMs : void 0,
|
|
2724
|
+
metadata: options?.metadata
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
//#endregion
|
|
2729
|
+
//#region src/rpc/performance.ts
|
|
2730
|
+
/**
|
|
2731
|
+
* Performance Optimizations for RPC
|
|
2732
|
+
*
|
|
2733
|
+
* Phase 3: Performance improvements
|
|
2734
|
+
* - Multi-segment message support
|
|
2735
|
+
* - Memory pooling
|
|
2736
|
+
* - Zero-copy paths where possible
|
|
2737
|
+
*/
|
|
2738
|
+
/**
|
|
2739
|
+
* Memory pool for reusing ArrayBuffers
|
|
2740
|
+
* Reduces GC pressure for frequent allocations
|
|
2741
|
+
*/
|
|
2742
|
+
var MemoryPool = class {
|
|
2743
|
+
pools = /* @__PURE__ */ new Map();
|
|
2744
|
+
maxPoolSize;
|
|
2745
|
+
maxBufferAge;
|
|
2746
|
+
constructor(options) {
|
|
2747
|
+
this.maxPoolSize = options?.maxPoolSize ?? 100;
|
|
2748
|
+
this.maxBufferAge = options?.maxBufferAgeMs ?? 6e4;
|
|
2749
|
+
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Acquire a buffer of at least the requested size
|
|
2752
|
+
*/
|
|
2753
|
+
acquire(size) {
|
|
2754
|
+
const pooledSize = this.roundUpSize(size);
|
|
2755
|
+
const pool = this.pools.get(pooledSize);
|
|
2756
|
+
if (pool && pool.length > 0) {
|
|
2757
|
+
const now = Date.now();
|
|
2758
|
+
const index = pool.findIndex((b) => now - b.lastUsed < this.maxBufferAge);
|
|
2759
|
+
if (index >= 0) return pool.splice(index, 1)[0].buffer;
|
|
2760
|
+
}
|
|
2761
|
+
return new ArrayBuffer(pooledSize);
|
|
2762
|
+
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Release a buffer back to the pool
|
|
2765
|
+
*/
|
|
2766
|
+
release(buffer) {
|
|
2767
|
+
const size = buffer.byteLength;
|
|
2768
|
+
if (size < 64 || size > 1024 * 1024) return;
|
|
2769
|
+
let pool = this.pools.get(size);
|
|
2770
|
+
if (!pool) {
|
|
2771
|
+
pool = [];
|
|
2772
|
+
this.pools.set(size, pool);
|
|
2773
|
+
}
|
|
2774
|
+
if (pool.length < this.maxPoolSize) pool.push({
|
|
2775
|
+
buffer,
|
|
2776
|
+
size,
|
|
2777
|
+
lastUsed: Date.now()
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Clear all pooled buffers
|
|
2782
|
+
*/
|
|
2783
|
+
clear() {
|
|
2784
|
+
this.pools.clear();
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Get pool statistics
|
|
2788
|
+
*/
|
|
2789
|
+
getStats() {
|
|
2790
|
+
let totalBuffers = 0;
|
|
2791
|
+
let totalBytes = 0;
|
|
2792
|
+
const sizes = [];
|
|
2793
|
+
for (const [size, pool] of this.pools) {
|
|
2794
|
+
totalBuffers += pool.length;
|
|
2795
|
+
totalBytes += size * pool.length;
|
|
2796
|
+
sizes.push(size);
|
|
2797
|
+
}
|
|
2798
|
+
return {
|
|
2799
|
+
totalBuffers,
|
|
2800
|
+
totalBytes,
|
|
2801
|
+
sizes
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
roundUpSize(size) {
|
|
2805
|
+
if (size <= 64) return 64;
|
|
2806
|
+
if (size <= 128) return 128;
|
|
2807
|
+
if (size <= 256) return 256;
|
|
2808
|
+
if (size <= 512) return 512;
|
|
2809
|
+
if (size <= 1024) return 1024;
|
|
2810
|
+
if (size <= 2048) return 2048;
|
|
2811
|
+
if (size <= 4096) return 4096;
|
|
2812
|
+
if (size <= 8192) return 8192;
|
|
2813
|
+
if (size <= 16384) return 16384;
|
|
2814
|
+
if (size <= 32768) return 32768;
|
|
2815
|
+
if (size <= 65536) return 65536;
|
|
2816
|
+
return size;
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
/**
|
|
2820
|
+
* Builder for multi-segment messages
|
|
2821
|
+
* Optimizes memory usage for large messages
|
|
2822
|
+
*/
|
|
2823
|
+
var MultiSegmentMessageBuilder = class {
|
|
2824
|
+
segments = [];
|
|
2825
|
+
options;
|
|
2826
|
+
currentSegment;
|
|
2827
|
+
totalSize = 0;
|
|
2828
|
+
constructor(options) {
|
|
2829
|
+
this.options = {
|
|
2830
|
+
initialSegmentSize: options?.initialSegmentSize ?? 8192,
|
|
2831
|
+
maxSegmentSize: options?.maxSegmentSize ?? 65536,
|
|
2832
|
+
allowMultipleSegments: options?.allowMultipleSegments ?? true
|
|
2833
|
+
};
|
|
2834
|
+
this.currentSegment = new Segment(this.options.initialSegmentSize);
|
|
2835
|
+
this.segments.push(this.currentSegment);
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Allocate space in the message
|
|
2839
|
+
*/
|
|
2840
|
+
allocate(size) {
|
|
2841
|
+
const alignedBytes = size + 7 & -8;
|
|
2842
|
+
const words = alignedBytes / 8;
|
|
2843
|
+
if (this.currentSegment.byteLength - this.currentSegment.wordCount * 8 >= alignedBytes) {
|
|
2844
|
+
const wordOffset = this.currentSegment.allocate(words);
|
|
2845
|
+
this.totalSize += alignedBytes;
|
|
2846
|
+
return {
|
|
2847
|
+
segment: this.currentSegment,
|
|
2848
|
+
offset: wordOffset * 8
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
if (!this.options.allowMultipleSegments) throw new Error("Message too large for single segment");
|
|
2852
|
+
this.currentSegment = new Segment(Math.min(Math.max(alignedBytes, this.options.initialSegmentSize), this.options.maxSegmentSize));
|
|
2853
|
+
this.segments.push(this.currentSegment);
|
|
2854
|
+
const newWordOffset = this.currentSegment.allocate(words);
|
|
2855
|
+
this.totalSize += alignedBytes;
|
|
2856
|
+
return {
|
|
2857
|
+
segment: this.currentSegment,
|
|
2858
|
+
offset: newWordOffset * 8
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Get all segments
|
|
2863
|
+
*/
|
|
2864
|
+
getSegments() {
|
|
2865
|
+
return this.segments;
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Get the total size of all segments
|
|
2869
|
+
*/
|
|
2870
|
+
getTotalSize() {
|
|
2871
|
+
return this.totalSize;
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Get the number of segments
|
|
2875
|
+
*/
|
|
2876
|
+
getSegmentCount() {
|
|
2877
|
+
return this.segments.length;
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Serialize to a single buffer (for transport)
|
|
2881
|
+
*/
|
|
2882
|
+
toBuffer() {
|
|
2883
|
+
if (this.segments.length === 1) {
|
|
2884
|
+
const segmentData = this.segments[0].asUint8Array();
|
|
2885
|
+
return segmentData.buffer.slice(segmentData.byteOffset, segmentData.byteOffset + segmentData.byteLength);
|
|
2886
|
+
}
|
|
2887
|
+
const totalSize = this.segments.reduce((sum, seg) => sum + seg.byteLength, 0);
|
|
2888
|
+
const result = new ArrayBuffer(totalSize + 8 * this.segments.length);
|
|
2889
|
+
const view = new DataView(result);
|
|
2890
|
+
const bytes = new Uint8Array(result);
|
|
2891
|
+
view.setUint32(0, this.segments.length - 1, true);
|
|
2892
|
+
view.setUint32(4, 0, true);
|
|
2893
|
+
let offset = 8;
|
|
2894
|
+
for (let i = 0; i < this.segments.length; i++) {
|
|
2895
|
+
const segment = this.segments[i];
|
|
2896
|
+
if (i > 0) {
|
|
2897
|
+
view.setUint32(offset, segment.byteLength / 8, true);
|
|
2898
|
+
offset += 4;
|
|
2899
|
+
}
|
|
2900
|
+
const segmentBuffer = new Uint8Array(segment.byteLength);
|
|
2901
|
+
const segmentData = segment.dataView;
|
|
2902
|
+
for (let i = 0; i < segment.byteLength; i++) segmentBuffer[i] = segmentData.getUint8(i);
|
|
2903
|
+
bytes.set(segmentBuffer, offset);
|
|
2904
|
+
offset += segment.byteLength;
|
|
2905
|
+
}
|
|
2906
|
+
return result;
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
/**
|
|
2910
|
+
* Create a zero-copy view of a buffer
|
|
2911
|
+
*/
|
|
2912
|
+
function createZeroCopyView(buffer, byteOffset = 0, byteLength) {
|
|
2913
|
+
return {
|
|
2914
|
+
buffer,
|
|
2915
|
+
byteOffset,
|
|
2916
|
+
byteLength: byteLength ?? buffer.byteLength - byteOffset
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Check if two buffers are the same underlying memory
|
|
2921
|
+
*/
|
|
2922
|
+
function isSameBuffer(a, b) {
|
|
2923
|
+
try {
|
|
2924
|
+
return a === b;
|
|
2925
|
+
} catch {
|
|
2926
|
+
return false;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
/**
|
|
2930
|
+
* Copy data between buffers using the fastest available method
|
|
2931
|
+
*/
|
|
2932
|
+
function fastCopy(src, dst, srcOffset = 0, dstOffset = 0, length) {
|
|
2933
|
+
const len = length ?? Math.min(src.byteLength - srcOffset, dst.byteLength - dstOffset);
|
|
2934
|
+
const srcView = new Uint8Array(src, srcOffset, len);
|
|
2935
|
+
new Uint8Array(dst, dstOffset, len).set(srcView);
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Optimized RPC message builder
|
|
2939
|
+
*/
|
|
2940
|
+
var OptimizedRpcMessageBuilder = class {
|
|
2941
|
+
options;
|
|
2942
|
+
pool;
|
|
2943
|
+
constructor(options) {
|
|
2944
|
+
this.options = {
|
|
2945
|
+
useMultiSegment: options?.useMultiSegment ?? true,
|
|
2946
|
+
initialSegmentSize: options?.initialSegmentSize ?? 8192,
|
|
2947
|
+
useMemoryPool: options?.useMemoryPool ?? true,
|
|
2948
|
+
memoryPool: options?.memoryPool ?? new MemoryPool()
|
|
2949
|
+
};
|
|
2950
|
+
this.pool = this.options.memoryPool;
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Build a message with optimizations applied
|
|
2954
|
+
*/
|
|
2955
|
+
buildMessage(content) {
|
|
2956
|
+
const totalSize = 8 + content.length;
|
|
2957
|
+
if (this.options.useMemoryPool) {
|
|
2958
|
+
const buffer = this.pool.acquire(totalSize);
|
|
2959
|
+
const view = new DataView(buffer);
|
|
2960
|
+
const bytes = new Uint8Array(buffer);
|
|
2961
|
+
view.setUint32(0, 0, true);
|
|
2962
|
+
view.setUint32(4, content.length / 8, true);
|
|
2963
|
+
bytes.set(content, 8);
|
|
2964
|
+
return buffer;
|
|
2965
|
+
}
|
|
2966
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
2967
|
+
const view = new DataView(buffer);
|
|
2968
|
+
const bytes = new Uint8Array(buffer);
|
|
2969
|
+
view.setUint32(0, 0, true);
|
|
2970
|
+
view.setUint32(4, content.length / 8, true);
|
|
2971
|
+
bytes.set(content, 8);
|
|
2972
|
+
return buffer;
|
|
2973
|
+
}
|
|
2974
|
+
/**
|
|
2975
|
+
* Release a buffer back to the pool
|
|
2976
|
+
*/
|
|
2977
|
+
releaseBuffer(buffer) {
|
|
2978
|
+
if (this.options.useMemoryPool) this.pool.release(buffer);
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Get pool statistics
|
|
2982
|
+
*/
|
|
2983
|
+
getPoolStats() {
|
|
2984
|
+
return this.pool.getStats();
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
let globalMemoryPool = null;
|
|
2988
|
+
/**
|
|
2989
|
+
* Get the global memory pool instance
|
|
2990
|
+
*/
|
|
2991
|
+
function getGlobalMemoryPool() {
|
|
2992
|
+
if (!globalMemoryPool) globalMemoryPool = new MemoryPool();
|
|
2993
|
+
return globalMemoryPool;
|
|
2994
|
+
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Configure the global memory pool
|
|
2997
|
+
*/
|
|
2998
|
+
function configureGlobalMemoryPool(options) {
|
|
2999
|
+
globalMemoryPool = new MemoryPool(options);
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
//#endregion
|
|
3003
|
+
exports.AnswerTable = AnswerTable;
|
|
3004
|
+
exports.BaseCapabilityClient = BaseCapabilityClient;
|
|
828
3005
|
exports.ElementSize = ElementSize;
|
|
3006
|
+
exports.ExportTable = ExportTable;
|
|
3007
|
+
exports.ImportTable = ImportTable;
|
|
829
3008
|
exports.ListBuilder = ListBuilder;
|
|
830
3009
|
exports.ListReader = ListReader;
|
|
3010
|
+
exports.MemoryPool = MemoryPool;
|
|
831
3011
|
exports.MessageBuilder = MessageBuilder;
|
|
832
3012
|
exports.MessageReader = MessageReader;
|
|
3013
|
+
exports.MultiSegmentMessageBuilder = MultiSegmentMessageBuilder;
|
|
3014
|
+
exports.OptimizedRpcMessageBuilder = OptimizedRpcMessageBuilder;
|
|
3015
|
+
exports.PIPELINE_CLIENT_SYMBOL = PIPELINE_CLIENT_SYMBOL;
|
|
3016
|
+
exports.PipelineOpTracker = PipelineOpTracker;
|
|
3017
|
+
exports.PipelineResolutionTracker = PipelineResolutionTracker;
|
|
833
3018
|
exports.PointerTag = PointerTag;
|
|
3019
|
+
exports.QuestionTable = QuestionTable;
|
|
3020
|
+
exports.QueuedCallManager = QueuedCallManager;
|
|
3021
|
+
exports.RestoreHandler = RestoreHandler;
|
|
3022
|
+
exports.RpcConnection = RpcConnection;
|
|
834
3023
|
exports.Segment = Segment;
|
|
835
3024
|
exports.StructBuilder = StructBuilder;
|
|
836
3025
|
exports.StructReader = StructReader;
|
|
3026
|
+
exports.SturdyRefManager = SturdyRefManager;
|
|
837
3027
|
exports.UnionBuilder = UnionBuilder;
|
|
838
3028
|
exports.UnionReader = UnionReader;
|
|
839
3029
|
exports.WORD_SIZE = WORD_SIZE;
|
|
3030
|
+
exports.WebSocketTransport = WebSocketTransport;
|
|
3031
|
+
exports.configureGlobalMemoryPool = configureGlobalMemoryPool;
|
|
3032
|
+
exports.createPipelineClient = createPipelineClient;
|
|
3033
|
+
exports.createSturdyRef = createSturdyRef;
|
|
840
3034
|
exports.createUnionBuilder = createUnionBuilder;
|
|
841
3035
|
exports.createUnionReader = createUnionReader;
|
|
3036
|
+
exports.createZeroCopyView = createZeroCopyView;
|
|
842
3037
|
exports.decodePointer = decodePointer;
|
|
3038
|
+
exports.deserializeRpcMessage = deserializeRpcMessage;
|
|
3039
|
+
exports.deserializeSturdyRef = deserializeSturdyRef;
|
|
843
3040
|
exports.encodeListPointer = encodeListPointer;
|
|
844
3041
|
exports.encodeStructPointer = encodeStructPointer;
|
|
3042
|
+
exports.fastCopy = fastCopy;
|
|
3043
|
+
exports.getGlobalMemoryPool = getGlobalMemoryPool;
|
|
3044
|
+
exports.isPipelineClient = isPipelineClient;
|
|
3045
|
+
exports.isSameBuffer = isSameBuffer;
|
|
3046
|
+
exports.isSturdyRefValid = isSturdyRefValid;
|
|
3047
|
+
exports.serializeRpcMessage = serializeRpcMessage;
|
|
3048
|
+
exports.serializeSturdyRef = serializeSturdyRef;
|
|
845
3049
|
//# sourceMappingURL=index.cjs.map
|