@norskvideo/moq-net 0.1.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 +70 -0
- package/announced.d.ts +45 -0
- package/announced.d.ts.map +1 -0
- package/announced.js +70 -0
- package/announced.js.map +1 -0
- package/bandwidth.d.ts +11 -0
- package/bandwidth.d.ts.map +1 -0
- package/bandwidth.js +7 -0
- package/bandwidth.js.map +1 -0
- package/broadcast.d.ts +39 -0
- package/broadcast.d.ts.map +1 -0
- package/broadcast.js +76 -0
- package/broadcast.js.map +1 -0
- package/connection/accept.d.ts +15 -0
- package/connection/accept.d.ts.map +1 -0
- package/connection/accept.js +137 -0
- package/connection/accept.js.map +1 -0
- package/connection/cert.test.d.ts +2 -0
- package/connection/cert.test.d.ts.map +1 -0
- package/connection/cert.test.js +26 -0
- package/connection/cert.test.js.map +1 -0
- package/connection/connect.d.ts +37 -0
- package/connection/connect.d.ts.map +1 -0
- package/connection/connect.js +348 -0
- package/connection/connect.js.map +1 -0
- package/connection/established.d.ts +30 -0
- package/connection/established.d.ts.map +1 -0
- package/connection/established.js +3 -0
- package/connection/established.js.map +1 -0
- package/connection/handshake.d.ts +10 -0
- package/connection/handshake.d.ts.map +1 -0
- package/connection/handshake.js +48 -0
- package/connection/handshake.js.map +1 -0
- package/connection/index.d.ts +5 -0
- package/connection/index.d.ts.map +1 -0
- package/connection/index.js +6 -0
- package/connection/index.js.map +1 -0
- package/connection/reload.d.ts +47 -0
- package/connection/reload.d.ts.map +1 -0
- package/connection/reload.js +137 -0
- package/connection/reload.js.map +1 -0
- package/group.d.ts +66 -0
- package/group.d.ts.map +1 -0
- package/group.js +135 -0
- package/group.js.map +1 -0
- package/group.test.d.ts +2 -0
- package/group.test.d.ts.map +1 -0
- package/group.test.js +66 -0
- package/group.test.js.map +1 -0
- package/ietf/adapter.d.ts +65 -0
- package/ietf/adapter.d.ts.map +1 -0
- package/ietf/adapter.js +626 -0
- package/ietf/adapter.js.map +1 -0
- package/ietf/connection.d.ts +65 -0
- package/ietf/connection.d.ts.map +1 -0
- package/ietf/connection.js +248 -0
- package/ietf/connection.js.map +1 -0
- package/ietf/control.d.ts +135 -0
- package/ietf/control.d.ts.map +1 -0
- package/ietf/control.js +242 -0
- package/ietf/control.js.map +1 -0
- package/ietf/fetch.d.ts +64 -0
- package/ietf/fetch.d.ts.map +1 -0
- package/ietf/fetch.js +100 -0
- package/ietf/fetch.js.map +1 -0
- package/ietf/goaway.d.ts +15 -0
- package/ietf/goaway.d.ts.map +1 -0
- package/ietf/goaway.js +34 -0
- package/ietf/goaway.js.map +1 -0
- package/ietf/ietf.test.d.ts +2 -0
- package/ietf/ietf.test.d.ts.map +1 -0
- package/ietf/ietf.test.js +873 -0
- package/ietf/ietf.test.js.map +1 -0
- package/ietf/index.d.ts +18 -0
- package/ietf/index.d.ts.map +1 -0
- package/ietf/index.js +19 -0
- package/ietf/index.js.map +1 -0
- package/ietf/message.d.ts +4 -0
- package/ietf/message.d.ts.map +1 -0
- package/ietf/message.js +54 -0
- package/ietf/message.js.map +1 -0
- package/ietf/namespace.d.ts +5 -0
- package/ietf/namespace.d.ts.map +1 -0
- package/ietf/namespace.js +27 -0
- package/ietf/namespace.js.map +1 -0
- package/ietf/object.d.ts +40 -0
- package/ietf/object.d.ts.map +1 -0
- package/ietf/object.js +160 -0
- package/ietf/object.js.map +1 -0
- package/ietf/parameters.d.ts +56 -0
- package/ietf/parameters.d.ts.map +1 -0
- package/ietf/parameters.js +337 -0
- package/ietf/parameters.js.map +1 -0
- package/ietf/properties.d.ts +4 -0
- package/ietf/properties.d.ts.map +1 -0
- package/ietf/properties.js +33 -0
- package/ietf/properties.js.map +1 -0
- package/ietf/publish.d.ts +68 -0
- package/ietf/publish.d.ts.map +1 -0
- package/ietf/publish.js +192 -0
- package/ietf/publish.js.map +1 -0
- package/ietf/publish_namespace.d.ts +68 -0
- package/ietf/publish_namespace.d.ts.map +1 -0
- package/ietf/publish_namespace.js +176 -0
- package/ietf/publish_namespace.js.map +1 -0
- package/ietf/publisher.d.ts +50 -0
- package/ietf/publisher.d.ts.map +1 -0
- package/ietf/publisher.js +330 -0
- package/ietf/publisher.js.map +1 -0
- package/ietf/request.d.ts +52 -0
- package/ietf/request.d.ts.map +1 -0
- package/ietf/request.js +119 -0
- package/ietf/request.js.map +1 -0
- package/ietf/setup.d.ts +38 -0
- package/ietf/setup.d.ts.map +1 -0
- package/ietf/setup.js +129 -0
- package/ietf/setup.js.map +1 -0
- package/ietf/subscribe.d.ts +66 -0
- package/ietf/subscribe.d.ts.map +1 -0
- package/ietf/subscribe.js +276 -0
- package/ietf/subscribe.js.map +1 -0
- package/ietf/subscribe_namespace.d.ts +101 -0
- package/ietf/subscribe_namespace.d.ts.map +1 -0
- package/ietf/subscribe_namespace.js +256 -0
- package/ietf/subscribe_namespace.js.map +1 -0
- package/ietf/subscriber.d.ts +53 -0
- package/ietf/subscriber.d.ts.map +1 -0
- package/ietf/subscriber.js +404 -0
- package/ietf/subscriber.js.map +1 -0
- package/ietf/track.d.ts +40 -0
- package/ietf/track.d.ts.map +1 -0
- package/ietf/track.js +108 -0
- package/ietf/track.js.map +1 -0
- package/ietf/version.d.ts +56 -0
- package/ietf/version.d.ts.map +1 -0
- package/ietf/version.js +63 -0
- package/ietf/version.js.map +1 -0
- package/index.d.ts +22 -0
- package/index.d.ts.map +1 -0
- package/index.js +23 -0
- package/index.js.map +1 -0
- package/integration.test.d.ts +2 -0
- package/integration.test.d.ts.map +1 -0
- package/integration.test.js +90 -0
- package/integration.test.js.map +1 -0
- package/lite/announce.d.ts +35 -0
- package/lite/announce.d.ts.map +1 -0
- package/lite/announce.js +166 -0
- package/lite/announce.js.map +1 -0
- package/lite/connection.d.ts +47 -0
- package/lite/connection.d.ts.map +1 -0
- package/lite/connection.js +247 -0
- package/lite/connection.js.map +1 -0
- package/lite/fetch.d.ts +14 -0
- package/lite/fetch.d.ts.map +1 -0
- package/lite/fetch.js +47 -0
- package/lite/fetch.js.map +1 -0
- package/lite/goaway.d.ts +10 -0
- package/lite/goaway.d.ts.map +1 -0
- package/lite/goaway.js +37 -0
- package/lite/goaway.js.map +1 -0
- package/lite/group.d.ts +18 -0
- package/lite/group.d.ts.map +1 -0
- package/lite/group.js +46 -0
- package/lite/group.js.map +1 -0
- package/lite/index.d.ts +11 -0
- package/lite/index.d.ts.map +1 -0
- package/lite/index.js +12 -0
- package/lite/index.js.map +1 -0
- package/lite/message.d.ts +5 -0
- package/lite/message.d.ts.map +1 -0
- package/lite/message.js +51 -0
- package/lite/message.js.map +1 -0
- package/lite/origin.d.ts +25 -0
- package/lite/origin.d.ts.map +1 -0
- package/lite/origin.js +35 -0
- package/lite/origin.js.map +1 -0
- package/lite/probe.d.ts +12 -0
- package/lite/probe.d.ts.map +1 -0
- package/lite/probe.js +60 -0
- package/lite/probe.js.map +1 -0
- package/lite/publisher.d.ts +63 -0
- package/lite/publisher.d.ts.map +1 -0
- package/lite/publisher.js +350 -0
- package/lite/publisher.js.map +1 -0
- package/lite/session.d.ts +36 -0
- package/lite/session.d.ts.map +1 -0
- package/lite/session.js +130 -0
- package/lite/session.js.map +1 -0
- package/lite/setup.d.ts +18 -0
- package/lite/setup.d.ts.map +1 -0
- package/lite/setup.js +55 -0
- package/lite/setup.js.map +1 -0
- package/lite/stream.d.ts +23 -0
- package/lite/stream.d.ts.map +1 -0
- package/lite/stream.js +20 -0
- package/lite/stream.js.map +1 -0
- package/lite/subscribe.d.ts +111 -0
- package/lite/subscribe.d.ts.map +1 -0
- package/lite/subscribe.js +341 -0
- package/lite/subscribe.js.map +1 -0
- package/lite/subscriber.d.ts +80 -0
- package/lite/subscriber.d.ts.map +1 -0
- package/lite/subscriber.js +316 -0
- package/lite/subscriber.js.map +1 -0
- package/lite/track.d.ts +38 -0
- package/lite/track.d.ts.map +1 -0
- package/lite/track.js +81 -0
- package/lite/track.js.map +1 -0
- package/lite/track.test.d.ts +2 -0
- package/lite/track.test.d.ts.map +1 -0
- package/lite/track.test.js +53 -0
- package/lite/track.test.js.map +1 -0
- package/lite/version.d.ts +16 -0
- package/lite/version.d.ts.map +1 -0
- package/lite/version.js +41 -0
- package/lite/version.js.map +1 -0
- package/mock.d.ts +16 -0
- package/mock.d.ts.map +1 -0
- package/mock.js +154 -0
- package/mock.js.map +1 -0
- package/package.json +27 -0
- package/path.d.ts +111 -0
- package/path.d.ts.map +1 -0
- package/path.js +133 -0
- package/path.js.map +1 -0
- package/path.test.d.ts +2 -0
- package/path.test.d.ts.map +1 -0
- package/path.test.js +169 -0
- package/path.test.js.map +1 -0
- package/stream.d.ts +57 -0
- package/stream.d.ts.map +1 -0
- package/stream.js +326 -0
- package/stream.js.map +1 -0
- package/stream.test.d.ts +2 -0
- package/stream.test.d.ts.map +1 -0
- package/stream.test.js +210 -0
- package/stream.test.js.map +1 -0
- package/time.d.ts +109 -0
- package/time.d.ts.map +1 -0
- package/time.js +94 -0
- package/time.js.map +1 -0
- package/track.d.ts +85 -0
- package/track.d.ts.map +1 -0
- package/track.js +227 -0
- package/track.js.map +1 -0
- package/track.test.d.ts +2 -0
- package/track.test.d.ts.map +1 -0
- package/track.test.js +56 -0
- package/track.test.js.map +1 -0
- package/util/error.d.ts +3 -0
- package/util/error.d.ts.map +1 -0
- package/util/error.js +9 -0
- package/util/error.js.map +1 -0
- package/util/hex.d.ts +3 -0
- package/util/hex.d.ts.map +1 -0
- package/util/hex.js +16 -0
- package/util/hex.js.map +1 -0
- package/util/index.d.ts +2 -0
- package/util/index.d.ts.map +1 -0
- package/util/index.js +3 -0
- package/util/index.js.map +1 -0
- package/varint.d.ts +36 -0
- package/varint.d.ts.map +1 -0
- package/varint.js +288 -0
- package/varint.js.map +1 -0
- package/varint.test.d.ts +2 -0
- package/varint.test.d.ts.map +1 -0
- package/varint.test.js +106 -0
- package/varint.test.js.map +1 -0
- package/zod.d.ts +13 -0
- package/zod.d.ts.map +1 -0
- package/zod.js +19 -0
- package/zod.js.map +1 -0
package/mock.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock WebTransport implementation for in-process testing.
|
|
3
|
+
*
|
|
4
|
+
* Creates paired client/server transports connected via TransformStreams.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Creates a pair of connected MockTransport instances.
|
|
8
|
+
*
|
|
9
|
+
* @param protocol - The WebTransport protocol identifier (e.g. "moqt-17", "moql", "")
|
|
10
|
+
* @returns An object containing `client` and `server` transports
|
|
11
|
+
*/
|
|
12
|
+
export declare function createMockTransportPair(protocol?: string): {
|
|
13
|
+
client: WebTransport;
|
|
14
|
+
server: WebTransport;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=mock.d.ts.map
|
package/mock.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiKH;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,SAAK,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,CAMrG"}
|
package/mock.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* @ts-self-types="./mock.d.ts" */
|
|
2
|
+
/**
|
|
3
|
+
* Mock WebTransport implementation for in-process testing.
|
|
4
|
+
*
|
|
5
|
+
* Creates paired client/server transports connected via TransformStreams.
|
|
6
|
+
*/
|
|
7
|
+
// High watermark to prevent writes from blocking on backpressure.
|
|
8
|
+
// Real WebTransport has kernel buffers; we simulate this with a large queue.
|
|
9
|
+
const WRITABLE_STRATEGY = { highWaterMark: 256 };
|
|
10
|
+
const READABLE_STRATEGY = { highWaterMark: 256 };
|
|
11
|
+
function newStream() {
|
|
12
|
+
return new TransformStream({
|
|
13
|
+
// Copy each chunk to simulate real WebTransport's kernel-boundary copy.
|
|
14
|
+
// Without this, Writer's scratch buffer reuse corrupts queued data.
|
|
15
|
+
transform(chunk, controller) {
|
|
16
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
17
|
+
},
|
|
18
|
+
}, WRITABLE_STRATEGY, READABLE_STRATEGY);
|
|
19
|
+
}
|
|
20
|
+
class MockTransport {
|
|
21
|
+
protocol;
|
|
22
|
+
ready;
|
|
23
|
+
closed;
|
|
24
|
+
incomingBidirectionalStreams;
|
|
25
|
+
incomingUnidirectionalStreams;
|
|
26
|
+
datagrams;
|
|
27
|
+
congestionControl;
|
|
28
|
+
reliability;
|
|
29
|
+
#closeResolve;
|
|
30
|
+
#bidiController;
|
|
31
|
+
#uniController;
|
|
32
|
+
// Reference to the peer so we can enqueue streams to them
|
|
33
|
+
#peer;
|
|
34
|
+
constructor(protocol) {
|
|
35
|
+
this.protocol = protocol;
|
|
36
|
+
this.ready = Promise.resolve(undefined);
|
|
37
|
+
this.closed = new Promise((resolve) => {
|
|
38
|
+
this.#closeResolve = resolve;
|
|
39
|
+
});
|
|
40
|
+
this.incomingBidirectionalStreams = new ReadableStream({
|
|
41
|
+
start: (controller) => {
|
|
42
|
+
this.#bidiController = controller;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
this.incomingUnidirectionalStreams = new ReadableStream({
|
|
46
|
+
start: (controller) => {
|
|
47
|
+
this.#uniController = controller;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
this.congestionControl = "default";
|
|
51
|
+
this.reliability = "supports-unreliable";
|
|
52
|
+
// Stub datagrams
|
|
53
|
+
this.datagrams = {
|
|
54
|
+
readable: new ReadableStream(),
|
|
55
|
+
writable: new WritableStream(),
|
|
56
|
+
incomingHighWaterMark: 0,
|
|
57
|
+
outgoingHighWaterMark: 0,
|
|
58
|
+
incomingMaxAge: null,
|
|
59
|
+
outgoingMaxAge: null,
|
|
60
|
+
maxDatagramSize: 0,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
setPeer(peer) {
|
|
64
|
+
this.#peer = peer;
|
|
65
|
+
}
|
|
66
|
+
async createBidirectionalStream(_options) {
|
|
67
|
+
const peer = this.#peer;
|
|
68
|
+
if (!peer)
|
|
69
|
+
throw new Error("no peer");
|
|
70
|
+
// Create two TransformStreams for the two directions
|
|
71
|
+
const c2s = newStream();
|
|
72
|
+
const s2c = newStream();
|
|
73
|
+
// Local side: writes to c2s, reads from s2c
|
|
74
|
+
const local = {
|
|
75
|
+
readable: s2c.readable,
|
|
76
|
+
writable: c2s.writable,
|
|
77
|
+
};
|
|
78
|
+
// Peer side: writes to s2c, reads from c2s
|
|
79
|
+
const remote = {
|
|
80
|
+
readable: c2s.readable,
|
|
81
|
+
writable: s2c.writable,
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
peer.#bidiController.enqueue(remote);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Peer closed
|
|
88
|
+
}
|
|
89
|
+
return local;
|
|
90
|
+
}
|
|
91
|
+
async createUnidirectionalStream(_options) {
|
|
92
|
+
const peer = this.#peer;
|
|
93
|
+
if (!peer)
|
|
94
|
+
throw new Error("no peer");
|
|
95
|
+
const c2s = newStream();
|
|
96
|
+
try {
|
|
97
|
+
peer.#uniController.enqueue(c2s.readable);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Peer closed
|
|
101
|
+
}
|
|
102
|
+
return c2s.writable;
|
|
103
|
+
}
|
|
104
|
+
close(_closeInfo) {
|
|
105
|
+
const info = _closeInfo ?? { closeCode: 0, reason: "" };
|
|
106
|
+
this.#closeResolve(info);
|
|
107
|
+
try {
|
|
108
|
+
this.#bidiController.close();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Already closed
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
this.#uniController.close();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Already closed
|
|
118
|
+
}
|
|
119
|
+
// Also close peer's incoming streams
|
|
120
|
+
if (this.#peer) {
|
|
121
|
+
try {
|
|
122
|
+
this.#peer.#bidiController.close();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Already closed
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
this.#peer.#uniController.close();
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Already closed
|
|
132
|
+
}
|
|
133
|
+
this.#peer.#closeResolve(info);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// biome-ignore lint/suspicious/noExplicitAny: WebTransportStats type not available in all TS libs
|
|
137
|
+
async getStats() {
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Creates a pair of connected MockTransport instances.
|
|
143
|
+
*
|
|
144
|
+
* @param protocol - The WebTransport protocol identifier (e.g. "moqt-17", "moql", "")
|
|
145
|
+
* @returns An object containing `client` and `server` transports
|
|
146
|
+
*/
|
|
147
|
+
export function createMockTransportPair(protocol = "") {
|
|
148
|
+
const client = new MockTransport(protocol);
|
|
149
|
+
const server = new MockTransport(protocol);
|
|
150
|
+
client.setPeer(server);
|
|
151
|
+
server.setPeer(client);
|
|
152
|
+
return { client, server };
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=mock.js.map
|
package/mock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.js","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mock WebTransport implementation for in-process testing.\n *\n * Creates paired client/server transports connected via TransformStreams.\n */\n\n// High watermark to prevent writes from blocking on backpressure.\n// Real WebTransport has kernel buffers; we simulate this with a large queue.\nconst WRITABLE_STRATEGY: QueuingStrategy<Uint8Array> = { highWaterMark: 256 };\nconst READABLE_STRATEGY: QueuingStrategy<Uint8Array> = { highWaterMark: 256 };\n\nfunction newStream(): TransformStream<Uint8Array, Uint8Array> {\n\treturn new TransformStream(\n\t\t{\n\t\t\t// Copy each chunk to simulate real WebTransport's kernel-boundary copy.\n\t\t\t// Without this, Writer's scratch buffer reuse corrupts queued data.\n\t\t\ttransform(chunk, controller) {\n\t\t\t\tcontroller.enqueue(new Uint8Array(chunk));\n\t\t\t},\n\t\t},\n\t\tWRITABLE_STRATEGY,\n\t\tREADABLE_STRATEGY,\n\t);\n}\n\nclass MockTransport implements WebTransport {\n\treadonly protocol: string;\n\treadonly ready: Promise<undefined>;\n\treadonly closed: Promise<WebTransportCloseInfo>;\n\n\treadonly incomingBidirectionalStreams: ReadableStream<WebTransportBidirectionalStream>;\n\treadonly incomingUnidirectionalStreams: ReadableStream<ReadableStream<Uint8Array>>;\n\n\treadonly datagrams: WebTransportDatagramDuplexStream;\n\treadonly congestionControl: WebTransportCongestionControl;\n\treadonly reliability: string;\n\n\t#closeResolve!: (info: WebTransportCloseInfo) => void;\n\t#bidiController!: ReadableStreamDefaultController<WebTransportBidirectionalStream>;\n\t#uniController!: ReadableStreamDefaultController<ReadableStream<Uint8Array>>;\n\n\t// Reference to the peer so we can enqueue streams to them\n\t#peer?: MockTransport;\n\n\tconstructor(protocol: string) {\n\t\tthis.protocol = protocol;\n\t\tthis.ready = Promise.resolve(undefined);\n\t\tthis.closed = new Promise((resolve) => {\n\t\t\tthis.#closeResolve = resolve;\n\t\t});\n\n\t\tthis.incomingBidirectionalStreams = new ReadableStream({\n\t\t\tstart: (controller) => {\n\t\t\t\tthis.#bidiController = controller;\n\t\t\t},\n\t\t});\n\n\t\tthis.incomingUnidirectionalStreams = new ReadableStream({\n\t\t\tstart: (controller) => {\n\t\t\t\tthis.#uniController = controller;\n\t\t\t},\n\t\t});\n\n\t\tthis.congestionControl = \"default\";\n\t\tthis.reliability = \"supports-unreliable\";\n\n\t\t// Stub datagrams\n\t\tthis.datagrams = {\n\t\t\treadable: new ReadableStream(),\n\t\t\twritable: new WritableStream(),\n\t\t\tincomingHighWaterMark: 0,\n\t\t\toutgoingHighWaterMark: 0,\n\t\t\tincomingMaxAge: null,\n\t\t\toutgoingMaxAge: null,\n\t\t\tmaxDatagramSize: 0,\n\t\t};\n\t}\n\n\tsetPeer(peer: MockTransport) {\n\t\tthis.#peer = peer;\n\t}\n\n\tasync createBidirectionalStream(\n\t\t_options?: WebTransportSendStreamOptions,\n\t): Promise<WebTransportBidirectionalStream> {\n\t\tconst peer = this.#peer;\n\t\tif (!peer) throw new Error(\"no peer\");\n\n\t\t// Create two TransformStreams for the two directions\n\t\tconst c2s = newStream();\n\t\tconst s2c = newStream();\n\n\t\t// Local side: writes to c2s, reads from s2c\n\t\tconst local = {\n\t\t\treadable: s2c.readable,\n\t\t\twritable: c2s.writable,\n\t\t} as WebTransportBidirectionalStream;\n\n\t\t// Peer side: writes to s2c, reads from c2s\n\t\tconst remote = {\n\t\t\treadable: c2s.readable,\n\t\t\twritable: s2c.writable,\n\t\t} as WebTransportBidirectionalStream;\n\n\t\ttry {\n\t\t\tpeer.#bidiController.enqueue(remote);\n\t\t} catch {\n\t\t\t// Peer closed\n\t\t}\n\n\t\treturn local;\n\t}\n\n\tasync createUnidirectionalStream(_options?: WebTransportSendStreamOptions): Promise<WritableStream<Uint8Array>> {\n\t\tconst peer = this.#peer;\n\t\tif (!peer) throw new Error(\"no peer\");\n\n\t\tconst c2s = newStream();\n\n\t\ttry {\n\t\t\tpeer.#uniController.enqueue(c2s.readable);\n\t\t} catch {\n\t\t\t// Peer closed\n\t\t}\n\n\t\treturn c2s.writable;\n\t}\n\n\tclose(_closeInfo?: WebTransportCloseInfo): void {\n\t\tconst info = _closeInfo ?? { closeCode: 0, reason: \"\" };\n\t\tthis.#closeResolve(info);\n\n\t\ttry {\n\t\t\tthis.#bidiController.close();\n\t\t} catch {\n\t\t\t// Already closed\n\t\t}\n\t\ttry {\n\t\t\tthis.#uniController.close();\n\t\t} catch {\n\t\t\t// Already closed\n\t\t}\n\n\t\t// Also close peer's incoming streams\n\t\tif (this.#peer) {\n\t\t\ttry {\n\t\t\t\tthis.#peer.#bidiController.close();\n\t\t\t} catch {\n\t\t\t\t// Already closed\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tthis.#peer.#uniController.close();\n\t\t\t} catch {\n\t\t\t\t// Already closed\n\t\t\t}\n\t\t\tthis.#peer.#closeResolve(info);\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: WebTransportStats type not available in all TS libs\n\tasync getStats(): Promise<any> {\n\t\treturn {};\n\t}\n}\n\n/**\n * Creates a pair of connected MockTransport instances.\n *\n * @param protocol - The WebTransport protocol identifier (e.g. \"moqt-17\", \"moql\", \"\")\n * @returns An object containing `client` and `server` transports\n */\nexport function createMockTransportPair(protocol = \"\"): { client: WebTransport; server: WebTransport } {\n\tconst client = new MockTransport(protocol);\n\tconst server = new MockTransport(protocol);\n\tclient.setPeer(server);\n\tserver.setPeer(client);\n\treturn { client, server };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@norskvideo/moq-net",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "The networking layer for Media over QUIC: real-time pub/sub with built-in caching, fan-out, and prioritization.",
|
|
6
|
+
"license": "(MIT OR Apache-2.0)",
|
|
7
|
+
"repository": "github:moq-dev/moq",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"default": "./index.js"
|
|
13
|
+
},
|
|
14
|
+
"./zod": {
|
|
15
|
+
"types": "./zod.d.ts",
|
|
16
|
+
"default": "./zod.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@moq/qmux": "^0.1.3",
|
|
21
|
+
"async-mutex": "^0.5.0",
|
|
22
|
+
"@norskvideo/moq-signals": "^0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"zod": "^4.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/path.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A broadcast path that provides safe prefix matching operations.
|
|
3
|
+
*
|
|
4
|
+
* This module provides path-aware operations that respect delimiter boundaries,
|
|
5
|
+
* preventing issues like "foo" matching "foobar".
|
|
6
|
+
*
|
|
7
|
+
* Paths are automatically trimmed of leading and trailing slashes on creation,
|
|
8
|
+
* making all slashes implicit at boundaries.
|
|
9
|
+
* All paths are RELATIVE; you cannot join with a leading slash to make an absolute path.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Creation automatically trims slashes
|
|
14
|
+
* const path1 = Path.from("/foo/bar/");
|
|
15
|
+
* const path2 = Path.from("foo/bar");
|
|
16
|
+
* console.log(path1 === path2); // true
|
|
17
|
+
*
|
|
18
|
+
* // Multiple arguments are joined with "/"
|
|
19
|
+
* const path3 = Path.from("api", "v1", "users");
|
|
20
|
+
* console.log(path3); // "api/v1/users"
|
|
21
|
+
*
|
|
22
|
+
* // Safe prefix matching
|
|
23
|
+
* const base = Path.from("api/v1");
|
|
24
|
+
* console.log(Path.hasPrefix(Path.from("api"), base)); // true
|
|
25
|
+
* console.log(Path.hasPrefix(Path.from("api/v1"), base)); // true
|
|
26
|
+
*
|
|
27
|
+
* const joined = Path.join(base, Path.from("users"));
|
|
28
|
+
* console.log(joined); // "api/v1/users"
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export type Valid = string & {
|
|
32
|
+
__brand: "Name";
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Maximum number of slash-separated parts in a path.
|
|
36
|
+
*
|
|
37
|
+
* Matches the IETF moq-transport limit of 32 fields in a namespace tuple.
|
|
38
|
+
* moq-lite enforces the same bound when decoding paths off the wire.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MAX_PARTS = 32;
|
|
41
|
+
/** Build a path from one or more components, joining with "/" and trimming redundant slashes. */
|
|
42
|
+
export declare function from(...paths: string[]): Valid;
|
|
43
|
+
/** Split a path into its slash-separated parts. The empty path has no parts. */
|
|
44
|
+
export declare function parts(path: Valid): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Validate an untrusted wire string as a path, enforcing {@link MAX_PARTS}.
|
|
47
|
+
*
|
|
48
|
+
* Throws when the path has too many parts; use at wire decode sites.
|
|
49
|
+
*/
|
|
50
|
+
export declare function decode(raw: string): Valid;
|
|
51
|
+
/**
|
|
52
|
+
* Validate a path before writing it to the wire, enforcing {@link MAX_PARTS}.
|
|
53
|
+
*
|
|
54
|
+
* Throws when the path has too many parts; use at wire encode sites so we never
|
|
55
|
+
* emit a path the remote side is required to reject.
|
|
56
|
+
*/
|
|
57
|
+
export declare function encode(path: Valid): Valid;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a path has the given prefix, respecting path boundaries.
|
|
60
|
+
*
|
|
61
|
+
* Unlike String.startsWith, this ensures that "foo" does not match "foobar".
|
|
62
|
+
* The prefix must either:
|
|
63
|
+
* - Be exactly equal to the path
|
|
64
|
+
* - Be followed by a '/' delimiter in the original path
|
|
65
|
+
* - Be empty (matches everything)
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const path = Path.from("foo/bar");
|
|
70
|
+
* console.log(Path.hasPrefix(Path.from("foo"), path)); // true
|
|
71
|
+
* console.log(Path.hasPrefix(Path.from("foo/"), path)); // true (trailing slash ignored)
|
|
72
|
+
* console.log(Path.hasPrefix(Path.from("fo"), path)); // false
|
|
73
|
+
*
|
|
74
|
+
* const path2 = Path.from("foobar");
|
|
75
|
+
* console.log(Path.hasPrefix(Path.from("foo"), path2)); // false
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function hasPrefix(prefix: Valid, path: Valid): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Strip the given prefix from a path, returning the suffix.
|
|
81
|
+
*
|
|
82
|
+
* Returns null if the prefix doesn't match according to hasPrefix rules.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const path = Path.from("foo/bar/baz");
|
|
87
|
+
* const suffix = Path.stripPrefix(Path.from("foo"), path);
|
|
88
|
+
* console.log(suffix); // "bar/baz"
|
|
89
|
+
*
|
|
90
|
+
* const suffix2 = Path.stripPrefix(Path.from("foo/"), path);
|
|
91
|
+
* console.log(suffix2); // "bar/baz"
|
|
92
|
+
*
|
|
93
|
+
* const noMatch = Path.stripPrefix(Path.from("notfound"), path);
|
|
94
|
+
* console.log(noMatch); // null
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function stripPrefix(prefix: Valid, path: Valid): Valid | null;
|
|
98
|
+
/**
|
|
99
|
+
* Join two path components together.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const base = Path.from("foo");
|
|
104
|
+
* const joined = Path.join(base, Path.from("bar"));
|
|
105
|
+
* console.log(joined); // "foo/bar"
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function join(path: Valid, other: Valid): Valid;
|
|
109
|
+
/** The empty path, which is a prefix of every path. */
|
|
110
|
+
export declare function empty(): Valid;
|
|
111
|
+
//# sourceMappingURL=path.d.ts.map
|
package/path.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B,iGAAiG;AACjG,wBAAgB,IAAI,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAI9C;AAED,gFAAgF;AAChF,wBAAgB,KAAK,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,EAAE,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAGzC;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,KAAK,GAAG,KAAK,CAKzC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAgB7D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,IAAI,CAiBpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,CASrD;AAED,uDAAuD;AACvD,wBAAgB,KAAK,IAAI,KAAK,CAE7B"}
|
package/path.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* @ts-self-types="./path.d.ts" */
|
|
2
|
+
/**
|
|
3
|
+
* Maximum number of slash-separated parts in a path.
|
|
4
|
+
*
|
|
5
|
+
* Matches the IETF moq-transport limit of 32 fields in a namespace tuple.
|
|
6
|
+
* moq-lite enforces the same bound when decoding paths off the wire.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_PARTS = 32;
|
|
9
|
+
/** Build a path from one or more components, joining with "/" and trimming redundant slashes. */
|
|
10
|
+
export function from(...paths) {
|
|
11
|
+
// Join paths with "/" and then remove leading and trailing slashes, and collapse multiple slashes into one.
|
|
12
|
+
const joined = paths.join("/");
|
|
13
|
+
return joined.replace(/\/+/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
14
|
+
}
|
|
15
|
+
/** Split a path into its slash-separated parts. The empty path has no parts. */
|
|
16
|
+
export function parts(path) {
|
|
17
|
+
return path === "" ? [] : path.split("/");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validate an untrusted wire string as a path, enforcing {@link MAX_PARTS}.
|
|
21
|
+
*
|
|
22
|
+
* Throws when the path has too many parts; use at wire decode sites.
|
|
23
|
+
*/
|
|
24
|
+
export function decode(raw) {
|
|
25
|
+
const path = from(raw);
|
|
26
|
+
return encode(path);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validate a path before writing it to the wire, enforcing {@link MAX_PARTS}.
|
|
30
|
+
*
|
|
31
|
+
* Throws when the path has too many parts; use at wire encode sites so we never
|
|
32
|
+
* emit a path the remote side is required to reject.
|
|
33
|
+
*/
|
|
34
|
+
export function encode(path) {
|
|
35
|
+
if (parts(path).length > MAX_PARTS) {
|
|
36
|
+
throw new Error(`path exceeds ${MAX_PARTS} parts`);
|
|
37
|
+
}
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if a path has the given prefix, respecting path boundaries.
|
|
42
|
+
*
|
|
43
|
+
* Unlike String.startsWith, this ensures that "foo" does not match "foobar".
|
|
44
|
+
* The prefix must either:
|
|
45
|
+
* - Be exactly equal to the path
|
|
46
|
+
* - Be followed by a '/' delimiter in the original path
|
|
47
|
+
* - Be empty (matches everything)
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const path = Path.from("foo/bar");
|
|
52
|
+
* console.log(Path.hasPrefix(Path.from("foo"), path)); // true
|
|
53
|
+
* console.log(Path.hasPrefix(Path.from("foo/"), path)); // true (trailing slash ignored)
|
|
54
|
+
* console.log(Path.hasPrefix(Path.from("fo"), path)); // false
|
|
55
|
+
*
|
|
56
|
+
* const path2 = Path.from("foobar");
|
|
57
|
+
* console.log(Path.hasPrefix(Path.from("foo"), path2)); // false
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function hasPrefix(prefix, path) {
|
|
61
|
+
if (prefix === "") {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (!path.startsWith(prefix)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// Check if the prefix is the exact match
|
|
68
|
+
if (path.length === prefix.length) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// Otherwise, ensure the character after the prefix is a delimiter
|
|
72
|
+
return path[prefix.length] === "/";
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Strip the given prefix from a path, returning the suffix.
|
|
76
|
+
*
|
|
77
|
+
* Returns null if the prefix doesn't match according to hasPrefix rules.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* const path = Path.from("foo/bar/baz");
|
|
82
|
+
* const suffix = Path.stripPrefix(Path.from("foo"), path);
|
|
83
|
+
* console.log(suffix); // "bar/baz"
|
|
84
|
+
*
|
|
85
|
+
* const suffix2 = Path.stripPrefix(Path.from("foo/"), path);
|
|
86
|
+
* console.log(suffix2); // "bar/baz"
|
|
87
|
+
*
|
|
88
|
+
* const noMatch = Path.stripPrefix(Path.from("notfound"), path);
|
|
89
|
+
* console.log(noMatch); // null
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function stripPrefix(prefix, path) {
|
|
93
|
+
if (!hasPrefix(prefix, path)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
// Handle empty prefix case
|
|
97
|
+
if (prefix === "") {
|
|
98
|
+
return path;
|
|
99
|
+
}
|
|
100
|
+
// If prefix matches exactly, return empty
|
|
101
|
+
if (path.length === prefix.length) {
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
// For non-empty prefix that's shorter, skip the prefix and the following slash
|
|
105
|
+
return path.slice(prefix.length + 1);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Join two path components together.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const base = Path.from("foo");
|
|
113
|
+
* const joined = Path.join(base, Path.from("bar"));
|
|
114
|
+
* console.log(joined); // "foo/bar"
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function join(path, other) {
|
|
118
|
+
if (path === "") {
|
|
119
|
+
return other;
|
|
120
|
+
}
|
|
121
|
+
else if (other === "") {
|
|
122
|
+
return path;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Since paths are trimmed, we always need to add a slash
|
|
126
|
+
return `${path}/${other}`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** The empty path, which is a prefix of every path. */
|
|
130
|
+
export function empty() {
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=path.js.map
|
package/path.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAgCA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAE5B,iGAAiG;AACjG,MAAM,UAAU,IAAI,CAAC,GAAG,KAAe;IACtC,4GAA4G;IAC5G,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAU,CAAC;AACrF,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,KAAK,CAAC,IAAW;IAChC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,IAAW;IACjC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,gBAAgB,SAAS,QAAQ,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,SAAS,CAAC,MAAa,EAAE,IAAW;IACnD,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACd,CAAC;IAED,yCAAyC;IACzC,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,kEAAkE;IAClE,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAAC,MAAa,EAAE,IAAW;IACrD,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,2BAA2B;IAC3B,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,0CAA0C;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,EAAW,CAAC;IACpB,CAAC;IAED,+EAA+E;IAC/E,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAU,CAAC;AAC/C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,IAAI,CAAC,IAAW,EAAE,KAAY;IAC7C,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC;IACd,CAAC;SAAM,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACb,CAAC;SAAM,CAAC;QACP,yDAAyD;QACzD,OAAO,GAAG,IAAI,IAAI,KAAK,EAAW,CAAC;IACpC,CAAC;AACF,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,KAAK;IACpB,OAAO,EAAW,CAAC;AACpB,CAAC","sourcesContent":["/**\n * A broadcast path that provides safe prefix matching operations.\n *\n * This module provides path-aware operations that respect delimiter boundaries,\n * preventing issues like \"foo\" matching \"foobar\".\n *\n * Paths are automatically trimmed of leading and trailing slashes on creation,\n * making all slashes implicit at boundaries.\n * All paths are RELATIVE; you cannot join with a leading slash to make an absolute path.\n *\n * @example\n * ```typescript\n * // Creation automatically trims slashes\n * const path1 = Path.from(\"/foo/bar/\");\n * const path2 = Path.from(\"foo/bar\");\n * console.log(path1 === path2); // true\n *\n * // Multiple arguments are joined with \"/\"\n * const path3 = Path.from(\"api\", \"v1\", \"users\");\n * console.log(path3); // \"api/v1/users\"\n *\n * // Safe prefix matching\n * const base = Path.from(\"api/v1\");\n * console.log(Path.hasPrefix(Path.from(\"api\"), base)); // true\n * console.log(Path.hasPrefix(Path.from(\"api/v1\"), base)); // true\n *\n * const joined = Path.join(base, Path.from(\"users\"));\n * console.log(joined); // \"api/v1/users\"\n * ```\n */\nexport type Valid = string & { __brand: \"Name\" };\n\n/**\n * Maximum number of slash-separated parts in a path.\n *\n * Matches the IETF moq-transport limit of 32 fields in a namespace tuple.\n * moq-lite enforces the same bound when decoding paths off the wire.\n */\nexport const MAX_PARTS = 32;\n\n/** Build a path from one or more components, joining with \"/\" and trimming redundant slashes. */\nexport function from(...paths: string[]): Valid {\n\t// Join paths with \"/\" and then remove leading and trailing slashes, and collapse multiple slashes into one.\n\tconst joined = paths.join(\"/\");\n\treturn joined.replace(/\\/+/g, \"/\").replace(/^\\/+/, \"\").replace(/\\/+$/, \"\") as Valid;\n}\n\n/** Split a path into its slash-separated parts. The empty path has no parts. */\nexport function parts(path: Valid): string[] {\n\treturn path === \"\" ? [] : path.split(\"/\");\n}\n\n/**\n * Validate an untrusted wire string as a path, enforcing {@link MAX_PARTS}.\n *\n * Throws when the path has too many parts; use at wire decode sites.\n */\nexport function decode(raw: string): Valid {\n\tconst path = from(raw);\n\treturn encode(path);\n}\n\n/**\n * Validate a path before writing it to the wire, enforcing {@link MAX_PARTS}.\n *\n * Throws when the path has too many parts; use at wire encode sites so we never\n * emit a path the remote side is required to reject.\n */\nexport function encode(path: Valid): Valid {\n\tif (parts(path).length > MAX_PARTS) {\n\t\tthrow new Error(`path exceeds ${MAX_PARTS} parts`);\n\t}\n\treturn path;\n}\n\n/**\n * Check if a path has the given prefix, respecting path boundaries.\n *\n * Unlike String.startsWith, this ensures that \"foo\" does not match \"foobar\".\n * The prefix must either:\n * - Be exactly equal to the path\n * - Be followed by a '/' delimiter in the original path\n * - Be empty (matches everything)\n *\n * @example\n * ```typescript\n * const path = Path.from(\"foo/bar\");\n * console.log(Path.hasPrefix(Path.from(\"foo\"), path)); // true\n * console.log(Path.hasPrefix(Path.from(\"foo/\"), path)); // true (trailing slash ignored)\n * console.log(Path.hasPrefix(Path.from(\"fo\"), path)); // false\n *\n * const path2 = Path.from(\"foobar\");\n * console.log(Path.hasPrefix(Path.from(\"foo\"), path2)); // false\n * ```\n */\nexport function hasPrefix(prefix: Valid, path: Valid): boolean {\n\tif (prefix === \"\") {\n\t\treturn true;\n\t}\n\n\tif (!path.startsWith(prefix)) {\n\t\treturn false;\n\t}\n\n\t// Check if the prefix is the exact match\n\tif (path.length === prefix.length) {\n\t\treturn true;\n\t}\n\n\t// Otherwise, ensure the character after the prefix is a delimiter\n\treturn path[prefix.length] === \"/\";\n}\n\n/**\n * Strip the given prefix from a path, returning the suffix.\n *\n * Returns null if the prefix doesn't match according to hasPrefix rules.\n *\n * @example\n * ```typescript\n * const path = Path.from(\"foo/bar/baz\");\n * const suffix = Path.stripPrefix(Path.from(\"foo\"), path);\n * console.log(suffix); // \"bar/baz\"\n *\n * const suffix2 = Path.stripPrefix(Path.from(\"foo/\"), path);\n * console.log(suffix2); // \"bar/baz\"\n *\n * const noMatch = Path.stripPrefix(Path.from(\"notfound\"), path);\n * console.log(noMatch); // null\n * ```\n */\nexport function stripPrefix(prefix: Valid, path: Valid): Valid | null {\n\tif (!hasPrefix(prefix, path)) {\n\t\treturn null;\n\t}\n\n\t// Handle empty prefix case\n\tif (prefix === \"\") {\n\t\treturn path;\n\t}\n\n\t// If prefix matches exactly, return empty\n\tif (path.length === prefix.length) {\n\t\treturn \"\" as Valid;\n\t}\n\n\t// For non-empty prefix that's shorter, skip the prefix and the following slash\n\treturn path.slice(prefix.length + 1) as Valid;\n}\n\n/**\n * Join two path components together.\n *\n * @example\n * ```typescript\n * const base = Path.from(\"foo\");\n * const joined = Path.join(base, Path.from(\"bar\"));\n * console.log(joined); // \"foo/bar\"\n * ```\n */\nexport function join(path: Valid, other: Valid): Valid {\n\tif (path === \"\") {\n\t\treturn other;\n\t} else if (other === \"\") {\n\t\treturn path;\n\t} else {\n\t\t// Since paths are trimmed, we always need to add a slash\n\t\treturn `${path}/${other}` as Valid;\n\t}\n}\n\n/** The empty path, which is a prefix of every path. */\nexport function empty(): Valid {\n\treturn \"\" as Valid;\n}\n"]}
|
package/path.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.test.d.ts","sourceRoot":"","sources":["../src/path.test.ts"],"names":[],"mappings":""}
|
package/path.test.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/* @ts-self-types="./path.test.d.ts" */
|
|
2
|
+
import { expect, test } from "bun:test";
|
|
3
|
+
import * as Path from "./path.js";
|
|
4
|
+
test("Path constructor trims leading and trailing slashes", () => {
|
|
5
|
+
expect(Path.from("/foo/bar/")).toBe("foo/bar");
|
|
6
|
+
expect(Path.from("///foo/bar///")).toBe("foo/bar");
|
|
7
|
+
expect(Path.from("foo/bar")).toBe("foo/bar");
|
|
8
|
+
});
|
|
9
|
+
test("Path constructor handles empty paths", () => {
|
|
10
|
+
expect(Path.from("")).toBe("");
|
|
11
|
+
expect(Path.from("/")).toBe("");
|
|
12
|
+
expect(Path.from("///")).toBe("");
|
|
13
|
+
});
|
|
14
|
+
test("hasPrefix matches exact paths", () => {
|
|
15
|
+
const path = Path.from("foo/bar");
|
|
16
|
+
expect(Path.hasPrefix(Path.from("foo/bar"), path)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
test("hasPrefix matches proper prefixes", () => {
|
|
19
|
+
const path = Path.from("foo/bar/baz");
|
|
20
|
+
expect(Path.hasPrefix(Path.from("foo"), path)).toBe(true);
|
|
21
|
+
expect(Path.hasPrefix(Path.from("foo/bar"), path)).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
test("hasPrefix does not match partial segment prefixes", () => {
|
|
24
|
+
const path = Path.from("foobar");
|
|
25
|
+
expect(Path.hasPrefix(Path.from("foo"), path)).toBe(false);
|
|
26
|
+
const path2 = Path.from("foo/bar");
|
|
27
|
+
expect(Path.hasPrefix(Path.from("fo"), path2)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
test("hasPrefix handles empty prefix", () => {
|
|
30
|
+
const path = Path.from("foo/bar");
|
|
31
|
+
expect(Path.hasPrefix(Path.empty(), path)).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
test("hasPrefix ignores trailing slashes in prefix", () => {
|
|
34
|
+
const path = Path.from("foo/bar");
|
|
35
|
+
expect(Path.hasPrefix(Path.from("foo/"), path)).toBe(true);
|
|
36
|
+
expect(Path.hasPrefix(Path.from("foo/bar/"), path)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
test("stripPrefix strips valid prefixes", () => {
|
|
39
|
+
const path = Path.from("foo/bar/baz");
|
|
40
|
+
const suffix1 = Path.stripPrefix(Path.from("foo"), path);
|
|
41
|
+
expect(suffix1).toBe("bar/baz");
|
|
42
|
+
const suffix2 = Path.stripPrefix(Path.from("foo/bar"), path);
|
|
43
|
+
expect(suffix2).toBe("baz");
|
|
44
|
+
const suffix3 = Path.stripPrefix(Path.from("foo/bar/baz"), path);
|
|
45
|
+
expect(suffix3).toBe("");
|
|
46
|
+
});
|
|
47
|
+
test("stripPrefix returns null for invalid prefixes", () => {
|
|
48
|
+
const path = Path.from("foo/bar");
|
|
49
|
+
expect(Path.stripPrefix(Path.from("notfound"), path)).toBe(null);
|
|
50
|
+
expect(Path.stripPrefix(Path.from("fo"), path)).toBe(null);
|
|
51
|
+
});
|
|
52
|
+
test("stripPrefix handles empty prefix", () => {
|
|
53
|
+
const path = Path.from("foo/bar");
|
|
54
|
+
const result = Path.stripPrefix(Path.empty(), path);
|
|
55
|
+
expect(result).toBe("foo/bar");
|
|
56
|
+
});
|
|
57
|
+
test("stripPrefix accepts Path instances", () => {
|
|
58
|
+
const path = Path.from("foo/bar/baz");
|
|
59
|
+
const prefix = Path.from("foo/bar");
|
|
60
|
+
const result = Path.stripPrefix(prefix, path);
|
|
61
|
+
expect(result).toBe("baz");
|
|
62
|
+
});
|
|
63
|
+
test("join paths with slashes", () => {
|
|
64
|
+
const base = Path.from("foo");
|
|
65
|
+
const joined = Path.join(base, Path.from("bar"));
|
|
66
|
+
expect(joined).toBe("foo/bar");
|
|
67
|
+
});
|
|
68
|
+
test("join handles empty base", () => {
|
|
69
|
+
const base = Path.empty();
|
|
70
|
+
const joined = Path.join(base, Path.from("bar"));
|
|
71
|
+
expect(joined).toBe("bar");
|
|
72
|
+
});
|
|
73
|
+
test("join handles empty suffix", () => {
|
|
74
|
+
const base = Path.from("foo");
|
|
75
|
+
const joined = Path.join(base, Path.empty());
|
|
76
|
+
expect(joined).toBe("foo");
|
|
77
|
+
});
|
|
78
|
+
test("join accepts Path instances", () => {
|
|
79
|
+
const base = Path.from("foo");
|
|
80
|
+
const suffix = Path.from("bar");
|
|
81
|
+
const joined = Path.join(base, suffix);
|
|
82
|
+
expect(joined).toBe("foo/bar");
|
|
83
|
+
});
|
|
84
|
+
test("join handles multiple joins", () => {
|
|
85
|
+
const path = Path.join(Path.join(Path.join(Path.from("api"), Path.from("v1")), Path.from("users")), Path.from("123"));
|
|
86
|
+
expect(path).toBe("api/v1/users/123");
|
|
87
|
+
});
|
|
88
|
+
test("isEmpty checks correctly", () => {
|
|
89
|
+
expect(Path.from("") === "").toBe(true);
|
|
90
|
+
expect(Path.from("foo") === "").toBe(false);
|
|
91
|
+
expect(Path.empty() === "").toBe(true);
|
|
92
|
+
});
|
|
93
|
+
test("length property works correctly", () => {
|
|
94
|
+
expect(Path.from("foo").length).toBe(3);
|
|
95
|
+
expect(Path.from("foo/bar").length).toBe(7);
|
|
96
|
+
expect(Path.empty().length).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
test("equals checks correctly", () => {
|
|
99
|
+
const path1 = Path.from("foo/bar");
|
|
100
|
+
const path2 = Path.from("/foo/bar/");
|
|
101
|
+
const path3 = Path.from("foo/baz");
|
|
102
|
+
expect(path1 === path2).toBe(true);
|
|
103
|
+
expect(path1 === path3).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
test("JSON serialization works", () => {
|
|
106
|
+
const path = Path.from("foo/bar");
|
|
107
|
+
expect(JSON.stringify(path)).toBe('"foo/bar"');
|
|
108
|
+
});
|
|
109
|
+
test("handles paths with multiple consecutive slashes", () => {
|
|
110
|
+
const path = Path.from("foo//bar///baz");
|
|
111
|
+
// Multiple consecutive slashes are collapsed to single slashes
|
|
112
|
+
expect(path).toBe("foo/bar/baz");
|
|
113
|
+
});
|
|
114
|
+
test("removes multiple slashes comprehensively", () => {
|
|
115
|
+
// Test various multiple slash scenarios
|
|
116
|
+
expect(Path.from("foo//bar")).toBe("foo/bar");
|
|
117
|
+
expect(Path.from("foo///bar")).toBe("foo/bar");
|
|
118
|
+
expect(Path.from("foo////bar")).toBe("foo/bar");
|
|
119
|
+
// Multiple occurrences of double slashes
|
|
120
|
+
expect(Path.from("foo//bar//baz")).toBe("foo/bar/baz");
|
|
121
|
+
expect(Path.from("a//b//c//d")).toBe("a/b/c/d");
|
|
122
|
+
// Mixed slash counts
|
|
123
|
+
expect(Path.from("foo//bar///baz////qux")).toBe("foo/bar/baz/qux");
|
|
124
|
+
// With leading and trailing slashes
|
|
125
|
+
expect(Path.from("//foo//bar//")).toBe("foo/bar");
|
|
126
|
+
expect(Path.from("///foo///bar///")).toBe("foo/bar");
|
|
127
|
+
// Edge case: only slashes
|
|
128
|
+
expect(Path.from("//")).toBe("");
|
|
129
|
+
expect(Path.from("////")).toBe("");
|
|
130
|
+
// Test that operations work correctly with normalized paths
|
|
131
|
+
const pathWithSlashes = Path.from("foo//bar///baz");
|
|
132
|
+
expect(Path.hasPrefix(Path.from("foo/bar"), pathWithSlashes)).toBe(true);
|
|
133
|
+
expect(Path.stripPrefix(Path.from("foo"), pathWithSlashes)).toBe("bar/baz");
|
|
134
|
+
expect(Path.join(pathWithSlashes, Path.from("qux"))).toBe("foo/bar/baz/qux");
|
|
135
|
+
});
|
|
136
|
+
test("handles special characters", () => {
|
|
137
|
+
const path = Path.from("foo-bar_baz.txt");
|
|
138
|
+
expect(path).toBe("foo-bar_baz.txt");
|
|
139
|
+
expect(Path.hasPrefix(Path.from("foo-bar"), path)).toBe(false);
|
|
140
|
+
expect(Path.hasPrefix(Path.from("foo-bar_baz.txt"), path)).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
test("from accepts multiple arguments", () => {
|
|
143
|
+
expect(Path.from("foo", "bar", "baz")).toBe("foo/bar/baz");
|
|
144
|
+
expect(Path.from("api", "v1", "users")).toBe("api/v1/users");
|
|
145
|
+
});
|
|
146
|
+
test("from handles empty strings in arguments", () => {
|
|
147
|
+
expect(Path.from("foo", "", "bar")).toBe("foo/bar");
|
|
148
|
+
expect(Path.from("", "foo", "bar", "")).toBe("foo/bar");
|
|
149
|
+
});
|
|
150
|
+
test("from sanitizes multiple arguments with slashes", () => {
|
|
151
|
+
expect(Path.from("/foo/", "/bar/", "/baz/")).toBe("foo/bar/baz");
|
|
152
|
+
expect(Path.from("foo//", "//bar", "baz")).toBe("foo/bar/baz");
|
|
153
|
+
});
|
|
154
|
+
test("parts splits a path into its components", () => {
|
|
155
|
+
expect(Path.parts(Path.from(""))).toEqual([]);
|
|
156
|
+
expect(Path.parts(Path.from("foo"))).toEqual(["foo"]);
|
|
157
|
+
expect(Path.parts(Path.from("/foo//bar/"))).toEqual(["foo", "bar"]);
|
|
158
|
+
});
|
|
159
|
+
test("decode enforces the max part count", () => {
|
|
160
|
+
const atLimit = Array.from({ length: Path.MAX_PARTS }, (_, i) => `${i}`).join("/");
|
|
161
|
+
expect(Path.decode(atLimit)).toBe(atLimit);
|
|
162
|
+
expect(() => Path.decode(`${atLimit}/extra`)).toThrow();
|
|
163
|
+
});
|
|
164
|
+
test("encode enforces the max part count", () => {
|
|
165
|
+
const atLimit = Array.from({ length: Path.MAX_PARTS }, (_, i) => `${i}`).join("/");
|
|
166
|
+
expect(Path.encode(atLimit)).toBe(atLimit);
|
|
167
|
+
expect(() => Path.encode(`${atLimit}/extra`)).toThrow();
|
|
168
|
+
});
|
|
169
|
+
//# sourceMappingURL=path.test.js.map
|