@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.
Files changed (274) hide show
  1. package/README.md +70 -0
  2. package/announced.d.ts +45 -0
  3. package/announced.d.ts.map +1 -0
  4. package/announced.js +70 -0
  5. package/announced.js.map +1 -0
  6. package/bandwidth.d.ts +11 -0
  7. package/bandwidth.d.ts.map +1 -0
  8. package/bandwidth.js +7 -0
  9. package/bandwidth.js.map +1 -0
  10. package/broadcast.d.ts +39 -0
  11. package/broadcast.d.ts.map +1 -0
  12. package/broadcast.js +76 -0
  13. package/broadcast.js.map +1 -0
  14. package/connection/accept.d.ts +15 -0
  15. package/connection/accept.d.ts.map +1 -0
  16. package/connection/accept.js +137 -0
  17. package/connection/accept.js.map +1 -0
  18. package/connection/cert.test.d.ts +2 -0
  19. package/connection/cert.test.d.ts.map +1 -0
  20. package/connection/cert.test.js +26 -0
  21. package/connection/cert.test.js.map +1 -0
  22. package/connection/connect.d.ts +37 -0
  23. package/connection/connect.d.ts.map +1 -0
  24. package/connection/connect.js +348 -0
  25. package/connection/connect.js.map +1 -0
  26. package/connection/established.d.ts +30 -0
  27. package/connection/established.d.ts.map +1 -0
  28. package/connection/established.js +3 -0
  29. package/connection/established.js.map +1 -0
  30. package/connection/handshake.d.ts +10 -0
  31. package/connection/handshake.d.ts.map +1 -0
  32. package/connection/handshake.js +48 -0
  33. package/connection/handshake.js.map +1 -0
  34. package/connection/index.d.ts +5 -0
  35. package/connection/index.d.ts.map +1 -0
  36. package/connection/index.js +6 -0
  37. package/connection/index.js.map +1 -0
  38. package/connection/reload.d.ts +47 -0
  39. package/connection/reload.d.ts.map +1 -0
  40. package/connection/reload.js +137 -0
  41. package/connection/reload.js.map +1 -0
  42. package/group.d.ts +66 -0
  43. package/group.d.ts.map +1 -0
  44. package/group.js +135 -0
  45. package/group.js.map +1 -0
  46. package/group.test.d.ts +2 -0
  47. package/group.test.d.ts.map +1 -0
  48. package/group.test.js +66 -0
  49. package/group.test.js.map +1 -0
  50. package/ietf/adapter.d.ts +65 -0
  51. package/ietf/adapter.d.ts.map +1 -0
  52. package/ietf/adapter.js +626 -0
  53. package/ietf/adapter.js.map +1 -0
  54. package/ietf/connection.d.ts +65 -0
  55. package/ietf/connection.d.ts.map +1 -0
  56. package/ietf/connection.js +248 -0
  57. package/ietf/connection.js.map +1 -0
  58. package/ietf/control.d.ts +135 -0
  59. package/ietf/control.d.ts.map +1 -0
  60. package/ietf/control.js +242 -0
  61. package/ietf/control.js.map +1 -0
  62. package/ietf/fetch.d.ts +64 -0
  63. package/ietf/fetch.d.ts.map +1 -0
  64. package/ietf/fetch.js +100 -0
  65. package/ietf/fetch.js.map +1 -0
  66. package/ietf/goaway.d.ts +15 -0
  67. package/ietf/goaway.d.ts.map +1 -0
  68. package/ietf/goaway.js +34 -0
  69. package/ietf/goaway.js.map +1 -0
  70. package/ietf/ietf.test.d.ts +2 -0
  71. package/ietf/ietf.test.d.ts.map +1 -0
  72. package/ietf/ietf.test.js +873 -0
  73. package/ietf/ietf.test.js.map +1 -0
  74. package/ietf/index.d.ts +18 -0
  75. package/ietf/index.d.ts.map +1 -0
  76. package/ietf/index.js +19 -0
  77. package/ietf/index.js.map +1 -0
  78. package/ietf/message.d.ts +4 -0
  79. package/ietf/message.d.ts.map +1 -0
  80. package/ietf/message.js +54 -0
  81. package/ietf/message.js.map +1 -0
  82. package/ietf/namespace.d.ts +5 -0
  83. package/ietf/namespace.d.ts.map +1 -0
  84. package/ietf/namespace.js +27 -0
  85. package/ietf/namespace.js.map +1 -0
  86. package/ietf/object.d.ts +40 -0
  87. package/ietf/object.d.ts.map +1 -0
  88. package/ietf/object.js +160 -0
  89. package/ietf/object.js.map +1 -0
  90. package/ietf/parameters.d.ts +56 -0
  91. package/ietf/parameters.d.ts.map +1 -0
  92. package/ietf/parameters.js +337 -0
  93. package/ietf/parameters.js.map +1 -0
  94. package/ietf/properties.d.ts +4 -0
  95. package/ietf/properties.d.ts.map +1 -0
  96. package/ietf/properties.js +33 -0
  97. package/ietf/properties.js.map +1 -0
  98. package/ietf/publish.d.ts +68 -0
  99. package/ietf/publish.d.ts.map +1 -0
  100. package/ietf/publish.js +192 -0
  101. package/ietf/publish.js.map +1 -0
  102. package/ietf/publish_namespace.d.ts +68 -0
  103. package/ietf/publish_namespace.d.ts.map +1 -0
  104. package/ietf/publish_namespace.js +176 -0
  105. package/ietf/publish_namespace.js.map +1 -0
  106. package/ietf/publisher.d.ts +50 -0
  107. package/ietf/publisher.d.ts.map +1 -0
  108. package/ietf/publisher.js +330 -0
  109. package/ietf/publisher.js.map +1 -0
  110. package/ietf/request.d.ts +52 -0
  111. package/ietf/request.d.ts.map +1 -0
  112. package/ietf/request.js +119 -0
  113. package/ietf/request.js.map +1 -0
  114. package/ietf/setup.d.ts +38 -0
  115. package/ietf/setup.d.ts.map +1 -0
  116. package/ietf/setup.js +129 -0
  117. package/ietf/setup.js.map +1 -0
  118. package/ietf/subscribe.d.ts +66 -0
  119. package/ietf/subscribe.d.ts.map +1 -0
  120. package/ietf/subscribe.js +276 -0
  121. package/ietf/subscribe.js.map +1 -0
  122. package/ietf/subscribe_namespace.d.ts +101 -0
  123. package/ietf/subscribe_namespace.d.ts.map +1 -0
  124. package/ietf/subscribe_namespace.js +256 -0
  125. package/ietf/subscribe_namespace.js.map +1 -0
  126. package/ietf/subscriber.d.ts +53 -0
  127. package/ietf/subscriber.d.ts.map +1 -0
  128. package/ietf/subscriber.js +404 -0
  129. package/ietf/subscriber.js.map +1 -0
  130. package/ietf/track.d.ts +40 -0
  131. package/ietf/track.d.ts.map +1 -0
  132. package/ietf/track.js +108 -0
  133. package/ietf/track.js.map +1 -0
  134. package/ietf/version.d.ts +56 -0
  135. package/ietf/version.d.ts.map +1 -0
  136. package/ietf/version.js +63 -0
  137. package/ietf/version.js.map +1 -0
  138. package/index.d.ts +22 -0
  139. package/index.d.ts.map +1 -0
  140. package/index.js +23 -0
  141. package/index.js.map +1 -0
  142. package/integration.test.d.ts +2 -0
  143. package/integration.test.d.ts.map +1 -0
  144. package/integration.test.js +90 -0
  145. package/integration.test.js.map +1 -0
  146. package/lite/announce.d.ts +35 -0
  147. package/lite/announce.d.ts.map +1 -0
  148. package/lite/announce.js +166 -0
  149. package/lite/announce.js.map +1 -0
  150. package/lite/connection.d.ts +47 -0
  151. package/lite/connection.d.ts.map +1 -0
  152. package/lite/connection.js +247 -0
  153. package/lite/connection.js.map +1 -0
  154. package/lite/fetch.d.ts +14 -0
  155. package/lite/fetch.d.ts.map +1 -0
  156. package/lite/fetch.js +47 -0
  157. package/lite/fetch.js.map +1 -0
  158. package/lite/goaway.d.ts +10 -0
  159. package/lite/goaway.d.ts.map +1 -0
  160. package/lite/goaway.js +37 -0
  161. package/lite/goaway.js.map +1 -0
  162. package/lite/group.d.ts +18 -0
  163. package/lite/group.d.ts.map +1 -0
  164. package/lite/group.js +46 -0
  165. package/lite/group.js.map +1 -0
  166. package/lite/index.d.ts +11 -0
  167. package/lite/index.d.ts.map +1 -0
  168. package/lite/index.js +12 -0
  169. package/lite/index.js.map +1 -0
  170. package/lite/message.d.ts +5 -0
  171. package/lite/message.d.ts.map +1 -0
  172. package/lite/message.js +51 -0
  173. package/lite/message.js.map +1 -0
  174. package/lite/origin.d.ts +25 -0
  175. package/lite/origin.d.ts.map +1 -0
  176. package/lite/origin.js +35 -0
  177. package/lite/origin.js.map +1 -0
  178. package/lite/probe.d.ts +12 -0
  179. package/lite/probe.d.ts.map +1 -0
  180. package/lite/probe.js +60 -0
  181. package/lite/probe.js.map +1 -0
  182. package/lite/publisher.d.ts +63 -0
  183. package/lite/publisher.d.ts.map +1 -0
  184. package/lite/publisher.js +350 -0
  185. package/lite/publisher.js.map +1 -0
  186. package/lite/session.d.ts +36 -0
  187. package/lite/session.d.ts.map +1 -0
  188. package/lite/session.js +130 -0
  189. package/lite/session.js.map +1 -0
  190. package/lite/setup.d.ts +18 -0
  191. package/lite/setup.d.ts.map +1 -0
  192. package/lite/setup.js +55 -0
  193. package/lite/setup.js.map +1 -0
  194. package/lite/stream.d.ts +23 -0
  195. package/lite/stream.d.ts.map +1 -0
  196. package/lite/stream.js +20 -0
  197. package/lite/stream.js.map +1 -0
  198. package/lite/subscribe.d.ts +111 -0
  199. package/lite/subscribe.d.ts.map +1 -0
  200. package/lite/subscribe.js +341 -0
  201. package/lite/subscribe.js.map +1 -0
  202. package/lite/subscriber.d.ts +80 -0
  203. package/lite/subscriber.d.ts.map +1 -0
  204. package/lite/subscriber.js +316 -0
  205. package/lite/subscriber.js.map +1 -0
  206. package/lite/track.d.ts +38 -0
  207. package/lite/track.d.ts.map +1 -0
  208. package/lite/track.js +81 -0
  209. package/lite/track.js.map +1 -0
  210. package/lite/track.test.d.ts +2 -0
  211. package/lite/track.test.d.ts.map +1 -0
  212. package/lite/track.test.js +53 -0
  213. package/lite/track.test.js.map +1 -0
  214. package/lite/version.d.ts +16 -0
  215. package/lite/version.d.ts.map +1 -0
  216. package/lite/version.js +41 -0
  217. package/lite/version.js.map +1 -0
  218. package/mock.d.ts +16 -0
  219. package/mock.d.ts.map +1 -0
  220. package/mock.js +154 -0
  221. package/mock.js.map +1 -0
  222. package/package.json +27 -0
  223. package/path.d.ts +111 -0
  224. package/path.d.ts.map +1 -0
  225. package/path.js +133 -0
  226. package/path.js.map +1 -0
  227. package/path.test.d.ts +2 -0
  228. package/path.test.d.ts.map +1 -0
  229. package/path.test.js +169 -0
  230. package/path.test.js.map +1 -0
  231. package/stream.d.ts +57 -0
  232. package/stream.d.ts.map +1 -0
  233. package/stream.js +326 -0
  234. package/stream.js.map +1 -0
  235. package/stream.test.d.ts +2 -0
  236. package/stream.test.d.ts.map +1 -0
  237. package/stream.test.js +210 -0
  238. package/stream.test.js.map +1 -0
  239. package/time.d.ts +109 -0
  240. package/time.d.ts.map +1 -0
  241. package/time.js +94 -0
  242. package/time.js.map +1 -0
  243. package/track.d.ts +85 -0
  244. package/track.d.ts.map +1 -0
  245. package/track.js +227 -0
  246. package/track.js.map +1 -0
  247. package/track.test.d.ts +2 -0
  248. package/track.test.d.ts.map +1 -0
  249. package/track.test.js +56 -0
  250. package/track.test.js.map +1 -0
  251. package/util/error.d.ts +3 -0
  252. package/util/error.d.ts.map +1 -0
  253. package/util/error.js +9 -0
  254. package/util/error.js.map +1 -0
  255. package/util/hex.d.ts +3 -0
  256. package/util/hex.d.ts.map +1 -0
  257. package/util/hex.js +16 -0
  258. package/util/hex.js.map +1 -0
  259. package/util/index.d.ts +2 -0
  260. package/util/index.d.ts.map +1 -0
  261. package/util/index.js +3 -0
  262. package/util/index.js.map +1 -0
  263. package/varint.d.ts +36 -0
  264. package/varint.d.ts.map +1 -0
  265. package/varint.js +288 -0
  266. package/varint.js.map +1 -0
  267. package/varint.test.d.ts +2 -0
  268. package/varint.test.d.ts.map +1 -0
  269. package/varint.test.js +106 -0
  270. package/varint.test.js.map +1 -0
  271. package/zod.d.ts +13 -0
  272. package/zod.d.ts.map +1 -0
  273. package/zod.js +19 -0
  274. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=path.test.d.ts.map
@@ -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