@rljson/bs 0.0.19 → 0.0.21
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.architecture.md +1295 -2
- package/README.public.md +324 -169
- package/dist/README.architecture.md +1295 -2
- package/dist/README.public.md +324 -169
- package/dist/bs-peer-bridge.d.ts +2 -0
- package/dist/bs.js +163 -14
- package/dist/directional-socket-mock.d.ts +10 -0
- package/dist/index.d.ts +1 -0
- package/package.json +17 -17
package/dist/bs.js
CHANGED
|
@@ -740,21 +740,23 @@ class BsPeerBridge {
|
|
|
740
740
|
this._socket = _socket;
|
|
741
741
|
}
|
|
742
742
|
_eventHandlers = /* @__PURE__ */ new Map();
|
|
743
|
+
_handleConnectBound = this._handleConnect.bind(this);
|
|
744
|
+
_handleDisconnectBound = this._handleDisconnect.bind(this);
|
|
743
745
|
/**
|
|
744
746
|
* Starts the bridge by setting up connection event handlers and
|
|
745
747
|
* automatically registering all Bs methods.
|
|
746
748
|
*/
|
|
747
749
|
start() {
|
|
748
|
-
this._socket.on("connect",
|
|
749
|
-
this._socket.on("disconnect",
|
|
750
|
+
this._socket.on("connect", this._handleConnectBound);
|
|
751
|
+
this._socket.on("disconnect", this._handleDisconnectBound);
|
|
750
752
|
this._registerBsMethods();
|
|
751
753
|
}
|
|
752
754
|
/**
|
|
753
755
|
* Stops the bridge by removing all event handlers.
|
|
754
756
|
*/
|
|
755
757
|
stop() {
|
|
756
|
-
this._socket.off("connect",
|
|
757
|
-
this._socket.off("disconnect",
|
|
758
|
+
this._socket.off("connect", this._handleConnectBound);
|
|
759
|
+
this._socket.off("disconnect", this._handleDisconnectBound);
|
|
758
760
|
for (const [eventName, handler] of this._eventHandlers) {
|
|
759
761
|
this._socket.off(eventName, handler);
|
|
760
762
|
}
|
|
@@ -765,14 +767,11 @@ class BsPeerBridge {
|
|
|
765
767
|
*/
|
|
766
768
|
_registerBsMethods() {
|
|
767
769
|
const bsMethods = [
|
|
768
|
-
"setBlob",
|
|
769
770
|
"getBlob",
|
|
770
771
|
"getBlobStream",
|
|
771
|
-
"deleteBlob",
|
|
772
772
|
"blobExists",
|
|
773
773
|
"getBlobProperties",
|
|
774
|
-
"listBlobs"
|
|
775
|
-
"generateSignedUrl"
|
|
774
|
+
"listBlobs"
|
|
776
775
|
];
|
|
777
776
|
for (const methodName of bsMethods) {
|
|
778
777
|
this.registerEvent(methodName);
|
|
@@ -794,17 +793,17 @@ class BsPeerBridge {
|
|
|
794
793
|
`Method "${methodName}" not found on Bs instance`
|
|
795
794
|
);
|
|
796
795
|
if (typeof callback === "function") {
|
|
797
|
-
callback(
|
|
796
|
+
callback(error, null);
|
|
798
797
|
}
|
|
799
798
|
return;
|
|
800
799
|
}
|
|
801
800
|
bsMethod.apply(this._bs, methodArgs).then((result) => {
|
|
802
801
|
if (typeof callback === "function") {
|
|
803
|
-
callback(
|
|
802
|
+
callback(null, result);
|
|
804
803
|
}
|
|
805
804
|
}).catch((error) => {
|
|
806
805
|
if (typeof callback === "function") {
|
|
807
|
-
callback(
|
|
806
|
+
callback(error, null);
|
|
808
807
|
}
|
|
809
808
|
});
|
|
810
809
|
};
|
|
@@ -852,9 +851,9 @@ class BsPeerBridge {
|
|
|
852
851
|
throw new Error(`Method "${bsMethodName}" not found on Bs instance`);
|
|
853
852
|
}
|
|
854
853
|
const result = await bsMethod.apply(this._bs, args);
|
|
855
|
-
this._socket.emit(socketEventName,
|
|
854
|
+
this._socket.emit(socketEventName, null, result);
|
|
856
855
|
} catch (error) {
|
|
857
|
-
this._socket.emit(socketEventName,
|
|
856
|
+
this._socket.emit(socketEventName, error, null);
|
|
858
857
|
}
|
|
859
858
|
}
|
|
860
859
|
/* v8 ignore next -- @preserve */
|
|
@@ -1075,6 +1074,155 @@ class SocketMock {
|
|
|
1075
1074
|
return new Map(this._onceListeners);
|
|
1076
1075
|
}
|
|
1077
1076
|
}
|
|
1077
|
+
function createSocketPair() {
|
|
1078
|
+
const socketA = new DirectionalSocketMock();
|
|
1079
|
+
const socketB = new DirectionalSocketMock();
|
|
1080
|
+
socketA._setPeer(socketB);
|
|
1081
|
+
socketB._setPeer(socketA);
|
|
1082
|
+
return [socketA, socketB];
|
|
1083
|
+
}
|
|
1084
|
+
class DirectionalSocketMock {
|
|
1085
|
+
connected = false;
|
|
1086
|
+
disconnected = true;
|
|
1087
|
+
_peer;
|
|
1088
|
+
_listeners = /* @__PURE__ */ new Map();
|
|
1089
|
+
_onceListeners = /* @__PURE__ */ new Map();
|
|
1090
|
+
_setPeer(peer) {
|
|
1091
|
+
this._peer = peer;
|
|
1092
|
+
}
|
|
1093
|
+
connect() {
|
|
1094
|
+
if (!this.connected) {
|
|
1095
|
+
this.connected = true;
|
|
1096
|
+
this.disconnected = false;
|
|
1097
|
+
this._triggerLocal("connect");
|
|
1098
|
+
if (this._peer) {
|
|
1099
|
+
this._peer._triggerLocal("connect");
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
disconnect() {
|
|
1104
|
+
if (this.connected) {
|
|
1105
|
+
this.connected = false;
|
|
1106
|
+
this.disconnected = true;
|
|
1107
|
+
this._triggerLocal("disconnect");
|
|
1108
|
+
if (this._peer) {
|
|
1109
|
+
this._peer._triggerLocal("disconnect");
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
on(eventName, listener) {
|
|
1114
|
+
if (!this._listeners.has(eventName)) {
|
|
1115
|
+
this._listeners.set(eventName, []);
|
|
1116
|
+
}
|
|
1117
|
+
this._listeners.get(eventName).push(listener);
|
|
1118
|
+
return this;
|
|
1119
|
+
}
|
|
1120
|
+
once(eventName, listener) {
|
|
1121
|
+
if (!this._onceListeners.has(eventName)) {
|
|
1122
|
+
this._onceListeners.set(eventName, []);
|
|
1123
|
+
}
|
|
1124
|
+
this._onceListeners.get(eventName).push(listener);
|
|
1125
|
+
return this;
|
|
1126
|
+
}
|
|
1127
|
+
off(eventName, listener) {
|
|
1128
|
+
if (listener) {
|
|
1129
|
+
const regularListeners = this._listeners.get(eventName);
|
|
1130
|
+
if (regularListeners) {
|
|
1131
|
+
const index = regularListeners.indexOf(listener);
|
|
1132
|
+
if (index > -1) regularListeners.splice(index, 1);
|
|
1133
|
+
}
|
|
1134
|
+
const onceListeners = this._onceListeners.get(eventName);
|
|
1135
|
+
if (onceListeners) {
|
|
1136
|
+
const index = onceListeners.indexOf(listener);
|
|
1137
|
+
if (index > -1) onceListeners.splice(index, 1);
|
|
1138
|
+
}
|
|
1139
|
+
} else {
|
|
1140
|
+
this._listeners.delete(eventName);
|
|
1141
|
+
this._onceListeners.delete(eventName);
|
|
1142
|
+
}
|
|
1143
|
+
return this;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Emits an event to the PEER socket (cross-socket emission).
|
|
1147
|
+
* This is the key difference from SocketMock - emit() sends to the other side,
|
|
1148
|
+
* not to local listeners.
|
|
1149
|
+
*
|
|
1150
|
+
* Implements Socket.IO acknowledgement pattern: the last argument can be a callback
|
|
1151
|
+
* that the peer will invoke to send a response back.
|
|
1152
|
+
* @param eventName - The event name to emit
|
|
1153
|
+
* @param args - Arguments to pass with the event
|
|
1154
|
+
*/
|
|
1155
|
+
emit(eventName, ...args) {
|
|
1156
|
+
if (!this._peer) {
|
|
1157
|
+
console.warn(
|
|
1158
|
+
`DirectionalSocketMock.emit: No peer connected for event ${String(eventName)}`
|
|
1159
|
+
);
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
this._peer._triggerLocal(eventName, ...args);
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Triggers listeners on THIS socket (local emission).
|
|
1167
|
+
* Used internally when receiving events from peer.
|
|
1168
|
+
* @param eventName - The event name to trigger
|
|
1169
|
+
* @param args - Arguments to pass to the listeners
|
|
1170
|
+
*/
|
|
1171
|
+
_triggerLocal(eventName, ...args) {
|
|
1172
|
+
const regularListeners = this._listeners.get(eventName);
|
|
1173
|
+
if (regularListeners) {
|
|
1174
|
+
[...regularListeners].forEach((listener) => {
|
|
1175
|
+
try {
|
|
1176
|
+
listener(...args);
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
console.error(`Error in listener for ${String(eventName)}:`, error);
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
const onceListeners = this._onceListeners.get(eventName);
|
|
1183
|
+
if (onceListeners) {
|
|
1184
|
+
const listenersToCall = [...onceListeners];
|
|
1185
|
+
this._onceListeners.delete(eventName);
|
|
1186
|
+
listenersToCall.forEach((listener) => {
|
|
1187
|
+
try {
|
|
1188
|
+
listener(...args);
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
console.error(
|
|
1191
|
+
`Error in once listener for ${String(eventName)}:`,
|
|
1192
|
+
error
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
removeAllListeners(eventName) {
|
|
1199
|
+
if (eventName !== void 0) {
|
|
1200
|
+
this._listeners.delete(eventName);
|
|
1201
|
+
this._onceListeners.delete(eventName);
|
|
1202
|
+
} else {
|
|
1203
|
+
this._listeners.clear();
|
|
1204
|
+
this._onceListeners.clear();
|
|
1205
|
+
}
|
|
1206
|
+
return this;
|
|
1207
|
+
}
|
|
1208
|
+
listenerCount(eventName) {
|
|
1209
|
+
const regularCount = this._listeners.get(eventName)?.length || 0;
|
|
1210
|
+
const onceCount = this._onceListeners.get(eventName)?.length || 0;
|
|
1211
|
+
return regularCount + onceCount;
|
|
1212
|
+
}
|
|
1213
|
+
listeners(eventName) {
|
|
1214
|
+
const regularListeners = this._listeners.get(eventName) || [];
|
|
1215
|
+
const onceListeners = this._onceListeners.get(eventName) || [];
|
|
1216
|
+
return [...regularListeners, ...onceListeners];
|
|
1217
|
+
}
|
|
1218
|
+
eventNames() {
|
|
1219
|
+
const allEvents = /* @__PURE__ */ new Set([
|
|
1220
|
+
...this._listeners.keys(),
|
|
1221
|
+
...this._onceListeners.keys()
|
|
1222
|
+
]);
|
|
1223
|
+
return Array.from(allEvents);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1078
1226
|
export {
|
|
1079
1227
|
BsMem,
|
|
1080
1228
|
BsMulti,
|
|
@@ -1082,5 +1230,6 @@ export {
|
|
|
1082
1230
|
BsPeerBridge,
|
|
1083
1231
|
BsServer,
|
|
1084
1232
|
PeerSocketMock,
|
|
1085
|
-
SocketMock
|
|
1233
|
+
SocketMock,
|
|
1234
|
+
createSocketPair
|
|
1086
1235
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Socket } from './socket.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a pair of connected sockets that properly route messages between them.
|
|
4
|
+
* Unlike SocketMock, this maintains directionality - when socketA emits, only socketB's
|
|
5
|
+
* listeners fire, not socketA's own listeners.
|
|
6
|
+
*
|
|
7
|
+
* This is essential for client-server testing where both use the same socket instance
|
|
8
|
+
* but need separate event handling.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createSocketPair(): [Socket, Socket];
|
package/dist/index.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export type { BsTestSetup } from './bs-test-setup.js';
|
|
|
7
7
|
export type { BlobProperties, Bs, DownloadBlobOptions, ListBlobsOptions, ListBlobsResult, } from './bs.js';
|
|
8
8
|
export { PeerSocketMock } from './peer-socket-mock.js';
|
|
9
9
|
export { SocketMock } from './socket-mock.js';
|
|
10
|
+
export { createSocketPair } from './directional-socket-mock.js';
|
|
10
11
|
export type { Socket } from './socket.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rljson/bs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Blob storage interface and implementations for rljson",
|
|
5
5
|
"homepage": "https://github.com/rljson/bs",
|
|
6
6
|
"bugs": "https://github.com/rljson/bs/issues",
|
|
@@ -20,36 +20,36 @@
|
|
|
20
20
|
],
|
|
21
21
|
"type": "module",
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@types/node": "^25.
|
|
24
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
25
|
-
"@typescript-eslint/parser": "^8.
|
|
26
|
-
"@vitest/coverage-v8": "^4.0.
|
|
23
|
+
"@types/node": "^25.2.3",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
|
25
|
+
"@typescript-eslint/parser": "^8.56.0",
|
|
26
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
27
27
|
"cross-env": "^10.1.0",
|
|
28
|
-
"eslint": "
|
|
29
|
-
"eslint-plugin-jsdoc": "^62.
|
|
28
|
+
"eslint": "~9.39.2",
|
|
29
|
+
"eslint-plugin-jsdoc": "^62.5.5",
|
|
30
30
|
"eslint-plugin-tsdoc": "^0.5.0",
|
|
31
|
-
"globals": "^17.
|
|
31
|
+
"globals": "^17.3.0",
|
|
32
32
|
"jsdoc": "^4.0.5",
|
|
33
|
-
"read-pkg": "^10.
|
|
33
|
+
"read-pkg": "^10.1.0",
|
|
34
34
|
"typescript": "~5.9.3",
|
|
35
|
-
"typescript-eslint": "^8.
|
|
35
|
+
"typescript-eslint": "^8.56.0",
|
|
36
36
|
"vite": "^7.3.1",
|
|
37
37
|
"vite-node": "^5.3.0",
|
|
38
38
|
"vite-plugin-dts": "^4.5.4",
|
|
39
|
-
"vite-tsconfig-paths": "^6.
|
|
40
|
-
"vitest": "^4.0.
|
|
39
|
+
"vite-tsconfig-paths": "^6.1.1",
|
|
40
|
+
"vitest": "^4.0.18",
|
|
41
41
|
"vitest-dom": "^0.1.1"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@rljson/hash": "^0.0.18",
|
|
45
45
|
"@rljson/json": "^0.0.23",
|
|
46
|
-
"@rljson/rljson": "^0.0.
|
|
46
|
+
"@rljson/rljson": "^0.0.76"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
|
-
"build": "
|
|
50
|
-
"test": "
|
|
51
|
-
"prebuild": "
|
|
52
|
-
"lint": "
|
|
49
|
+
"build": "pnpm exec vite build && tsc && node scripts/copy-readme-to-dist.js && node scripts/deploy-conformance-tests.js",
|
|
50
|
+
"test": "pnpm exec vitest run --coverage && pnpm run lint",
|
|
51
|
+
"prebuild": "pnpm run test",
|
|
52
|
+
"lint": "pnpm exec eslint",
|
|
53
53
|
"updateGoldens": "cross-env UPDATE_GOLDENS=true pnpm test"
|
|
54
54
|
}
|
|
55
55
|
}
|