@freqhole/reliquary 0.0.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 (226) hide show
  1. package/dist/automerge/acl-filtering.d.ts +87 -0
  2. package/dist/automerge/acl-filtering.d.ts.map +1 -0
  3. package/dist/automerge/acl-filtering.js +138 -0
  4. package/dist/automerge/acl-filtering.test.d.ts +2 -0
  5. package/dist/automerge/acl-filtering.test.d.ts.map +1 -0
  6. package/dist/automerge/acl-filtering.test.js +302 -0
  7. package/dist/automerge/index.d.ts +5 -0
  8. package/dist/automerge/index.d.ts.map +1 -0
  9. package/dist/automerge/index.js +7 -0
  10. package/dist/automerge/iroh-network-adapter.d.ts +178 -0
  11. package/dist/automerge/iroh-network-adapter.d.ts.map +1 -0
  12. package/dist/automerge/iroh-network-adapter.js +691 -0
  13. package/dist/automerge/iroh-network-adapter.test.d.ts +2 -0
  14. package/dist/automerge/iroh-network-adapter.test.d.ts.map +1 -0
  15. package/dist/automerge/iroh-network-adapter.test.js +818 -0
  16. package/dist/automerge/types.d.ts +66 -0
  17. package/dist/automerge/types.d.ts.map +1 -0
  18. package/dist/automerge/types.js +10 -0
  19. package/dist/blobs/bytes-backend.d.ts +41 -0
  20. package/dist/blobs/bytes-backend.d.ts.map +1 -0
  21. package/dist/blobs/bytes-backend.js +252 -0
  22. package/dist/blobs/bytes-backend.test.d.ts +2 -0
  23. package/dist/blobs/bytes-backend.test.d.ts.map +1 -0
  24. package/dist/blobs/bytes-backend.test.js +241 -0
  25. package/dist/blobs/db.d.ts +8 -0
  26. package/dist/blobs/db.d.ts.map +1 -0
  27. package/dist/blobs/db.js +141 -0
  28. package/dist/blobs/db.test.d.ts +2 -0
  29. package/dist/blobs/db.test.d.ts.map +1 -0
  30. package/dist/blobs/db.test.js +120 -0
  31. package/dist/blobs/index.d.ts +5 -0
  32. package/dist/blobs/index.d.ts.map +1 -0
  33. package/dist/blobs/index.js +2 -0
  34. package/dist/blobs/store.d.ts +47 -0
  35. package/dist/blobs/store.d.ts.map +1 -0
  36. package/dist/blobs/store.js +263 -0
  37. package/dist/blobs/store.test.d.ts +2 -0
  38. package/dist/blobs/store.test.d.ts.map +1 -0
  39. package/dist/blobs/store.test.js +377 -0
  40. package/dist/blobs/types.d.ts +55 -0
  41. package/dist/blobs/types.d.ts.map +1 -0
  42. package/dist/blobs/types.js +8 -0
  43. package/dist/ensure/client.d.ts +19 -0
  44. package/dist/ensure/client.d.ts.map +1 -0
  45. package/dist/ensure/client.js +74 -0
  46. package/dist/ensure/client.test.d.ts +2 -0
  47. package/dist/ensure/client.test.d.ts.map +1 -0
  48. package/dist/ensure/client.test.js +133 -0
  49. package/dist/ensure/index.d.ts +4 -0
  50. package/dist/ensure/index.d.ts.map +1 -0
  51. package/dist/ensure/index.js +4 -0
  52. package/dist/ensure/responder.d.ts +20 -0
  53. package/dist/ensure/responder.d.ts.map +1 -0
  54. package/dist/ensure/responder.js +100 -0
  55. package/dist/ensure/responder.test.d.ts +2 -0
  56. package/dist/ensure/responder.test.d.ts.map +1 -0
  57. package/dist/ensure/responder.test.js +191 -0
  58. package/dist/ensure/types.d.ts +19 -0
  59. package/dist/ensure/types.d.ts.map +1 -0
  60. package/dist/ensure/types.js +4 -0
  61. package/dist/ensure/types.test.d.ts +2 -0
  62. package/dist/ensure/types.test.d.ts.map +1 -0
  63. package/dist/ensure/types.test.js +81 -0
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +3 -0
  67. package/dist/index.test.d.ts +2 -0
  68. package/dist/index.test.d.ts.map +1 -0
  69. package/dist/index.test.js +7 -0
  70. package/dist/solid/blob-url.d.ts +10 -0
  71. package/dist/solid/blob-url.d.ts.map +1 -0
  72. package/dist/solid/blob-url.js +34 -0
  73. package/dist/solid/blob-url.test.d.ts +2 -0
  74. package/dist/solid/blob-url.test.d.ts.map +1 -0
  75. package/dist/solid/blob-url.test.js +62 -0
  76. package/dist/solid/connection-summary.d.ts +25 -0
  77. package/dist/solid/connection-summary.d.ts.map +1 -0
  78. package/dist/solid/connection-summary.js +29 -0
  79. package/dist/solid/connection-summary.test.d.ts +2 -0
  80. package/dist/solid/connection-summary.test.d.ts.map +1 -0
  81. package/dist/solid/connection-summary.test.js +57 -0
  82. package/dist/solid/doc-store.d.ts +29 -0
  83. package/dist/solid/doc-store.d.ts.map +1 -0
  84. package/dist/solid/doc-store.js +79 -0
  85. package/dist/solid/doc-store.test.d.ts +2 -0
  86. package/dist/solid/doc-store.test.d.ts.map +1 -0
  87. package/dist/solid/doc-store.test.js +234 -0
  88. package/dist/solid/index.d.ts +4 -0
  89. package/dist/solid/index.d.ts.map +1 -0
  90. package/dist/solid/index.js +9 -0
  91. package/dist/solid/transfer-progress.d.ts +21 -0
  92. package/dist/solid/transfer-progress.d.ts.map +1 -0
  93. package/dist/solid/transfer-progress.js +32 -0
  94. package/dist/solid/transfer-progress.test.d.ts +2 -0
  95. package/dist/solid/transfer-progress.test.d.ts.map +1 -0
  96. package/dist/solid/transfer-progress.test.js +76 -0
  97. package/dist/testing/blob-fixtures.d.ts +19 -0
  98. package/dist/testing/blob-fixtures.d.ts.map +1 -0
  99. package/dist/testing/blob-fixtures.js +46 -0
  100. package/dist/testing/blob-fixtures.test.d.ts +2 -0
  101. package/dist/testing/blob-fixtures.test.d.ts.map +1 -0
  102. package/dist/testing/blob-fixtures.test.js +46 -0
  103. package/dist/testing/idb-harness.d.ts +10 -0
  104. package/dist/testing/idb-harness.d.ts.map +1 -0
  105. package/dist/testing/idb-harness.js +17 -0
  106. package/dist/testing/idb-harness.test.d.ts +2 -0
  107. package/dist/testing/idb-harness.test.d.ts.map +1 -0
  108. package/dist/testing/idb-harness.test.js +48 -0
  109. package/dist/testing/index.d.ts +7 -0
  110. package/dist/testing/index.d.ts.map +1 -0
  111. package/dist/testing/index.js +11 -0
  112. package/dist/testing/media.d.ts +15 -0
  113. package/dist/testing/media.d.ts.map +1 -0
  114. package/dist/testing/media.js +37 -0
  115. package/dist/testing/media.test.d.ts +2 -0
  116. package/dist/testing/media.test.d.ts.map +1 -0
  117. package/dist/testing/media.test.js +47 -0
  118. package/dist/testing/mock-blob-fetch.d.ts +33 -0
  119. package/dist/testing/mock-blob-fetch.d.ts.map +1 -0
  120. package/dist/testing/mock-blob-fetch.js +39 -0
  121. package/dist/testing/mock-blob-fetch.test.d.ts +2 -0
  122. package/dist/testing/mock-blob-fetch.test.d.ts.map +1 -0
  123. package/dist/testing/mock-blob-fetch.test.js +65 -0
  124. package/dist/testing/mock-node.d.ts +36 -0
  125. package/dist/testing/mock-node.d.ts.map +1 -0
  126. package/dist/testing/mock-node.js +97 -0
  127. package/dist/testing/mock-node.test.d.ts +2 -0
  128. package/dist/testing/mock-node.test.d.ts.map +1 -0
  129. package/dist/testing/mock-node.test.js +66 -0
  130. package/dist/testing/streams.d.ts +68 -0
  131. package/dist/testing/streams.d.ts.map +1 -0
  132. package/dist/testing/streams.js +99 -0
  133. package/dist/testing/streams.test.d.ts +2 -0
  134. package/dist/testing/streams.test.d.ts.map +1 -0
  135. package/dist/testing/streams.test.js +82 -0
  136. package/dist/transfer/cancellation.d.ts +8 -0
  137. package/dist/transfer/cancellation.d.ts.map +1 -0
  138. package/dist/transfer/cancellation.js +19 -0
  139. package/dist/transfer/index.d.ts +8 -0
  140. package/dist/transfer/index.d.ts.map +1 -0
  141. package/dist/transfer/index.js +4 -0
  142. package/dist/transfer/prefetch.d.ts +38 -0
  143. package/dist/transfer/prefetch.d.ts.map +1 -0
  144. package/dist/transfer/prefetch.js +61 -0
  145. package/dist/transfer/prefetch.test.d.ts +2 -0
  146. package/dist/transfer/prefetch.test.d.ts.map +1 -0
  147. package/dist/transfer/prefetch.test.js +136 -0
  148. package/dist/transfer/serve.d.ts +66 -0
  149. package/dist/transfer/serve.d.ts.map +1 -0
  150. package/dist/transfer/serve.js +126 -0
  151. package/dist/transfer/serve.test.d.ts +2 -0
  152. package/dist/transfer/serve.test.d.ts.map +1 -0
  153. package/dist/transfer/serve.test.js +101 -0
  154. package/dist/transfer/snatch.d.ts +39 -0
  155. package/dist/transfer/snatch.d.ts.map +1 -0
  156. package/dist/transfer/snatch.js +289 -0
  157. package/dist/transfer/snatch.test.d.ts +2 -0
  158. package/dist/transfer/snatch.test.d.ts.map +1 -0
  159. package/dist/transfer/snatch.test.js +337 -0
  160. package/dist/transfer/types.d.ts +85 -0
  161. package/dist/transfer/types.d.ts.map +1 -0
  162. package/dist/transfer/types.js +15 -0
  163. package/dist/utils/hash.d.ts +13 -0
  164. package/dist/utils/hash.d.ts.map +1 -0
  165. package/dist/utils/hash.js +31 -0
  166. package/dist/utils/hash.test.d.ts +2 -0
  167. package/dist/utils/hash.test.d.ts.map +1 -0
  168. package/dist/utils/hash.test.js +56 -0
  169. package/dist/utils/image-utils.d.ts +28 -0
  170. package/dist/utils/image-utils.d.ts.map +1 -0
  171. package/dist/utils/image-utils.js +113 -0
  172. package/dist/utils/image-utils.test.d.ts +2 -0
  173. package/dist/utils/image-utils.test.d.ts.map +1 -0
  174. package/dist/utils/image-utils.test.js +181 -0
  175. package/dist/utils/index.d.ts +4 -0
  176. package/dist/utils/index.d.ts.map +1 -0
  177. package/dist/utils/index.js +3 -0
  178. package/dist/utils/log.d.ts +22 -0
  179. package/dist/utils/log.d.ts.map +1 -0
  180. package/dist/utils/log.js +100 -0
  181. package/dist/utils/log.test.d.ts +2 -0
  182. package/dist/utils/log.test.d.ts.map +1 -0
  183. package/dist/utils/log.test.js +113 -0
  184. package/dist/worker/blob-worker-client.d.ts +118 -0
  185. package/dist/worker/blob-worker-client.d.ts.map +1 -0
  186. package/dist/worker/blob-worker-client.js +361 -0
  187. package/dist/worker/blob-worker-client.test.d.ts +2 -0
  188. package/dist/worker/blob-worker-client.test.d.ts.map +1 -0
  189. package/dist/worker/blob-worker-client.test.js +137 -0
  190. package/dist/worker/blob-worker-logic.d.ts +120 -0
  191. package/dist/worker/blob-worker-logic.d.ts.map +1 -0
  192. package/dist/worker/blob-worker-logic.js +368 -0
  193. package/dist/worker/blob-worker-logic.test.d.ts +2 -0
  194. package/dist/worker/blob-worker-logic.test.d.ts.map +1 -0
  195. package/dist/worker/blob-worker-logic.test.js +191 -0
  196. package/dist/worker/blob-worker.d.ts +21 -0
  197. package/dist/worker/blob-worker.d.ts.map +1 -0
  198. package/dist/worker/blob-worker.js +44 -0
  199. package/dist/worker/index.d.ts +12 -0
  200. package/dist/worker/index.d.ts.map +1 -0
  201. package/dist/worker/index.js +6 -0
  202. package/dist/worker/midden-blake3.d.ts +23 -0
  203. package/dist/worker/midden-blake3.d.ts.map +1 -0
  204. package/dist/worker/midden-blake3.js +47 -0
  205. package/dist/worker/midden-blake3.test.d.ts +2 -0
  206. package/dist/worker/midden-blake3.test.d.ts.map +1 -0
  207. package/dist/worker/midden-blake3.test.js +25 -0
  208. package/dist/worker/midden-not-bundled.stub.d.ts +1 -0
  209. package/dist/worker/midden-not-bundled.stub.d.ts.map +1 -0
  210. package/dist/worker/midden-not-bundled.stub.js +10 -0
  211. package/dist/worker/midden-stub.d.ts +10 -0
  212. package/dist/worker/midden-stub.d.ts.map +1 -0
  213. package/dist/worker/midden-stub.js +31 -0
  214. package/dist/worker/midden-stub.test.d.ts +2 -0
  215. package/dist/worker/midden-stub.test.d.ts.map +1 -0
  216. package/dist/worker/midden-stub.test.js +25 -0
  217. package/dist/worker/midden-worker-client.d.ts +103 -0
  218. package/dist/worker/midden-worker-client.d.ts.map +1 -0
  219. package/dist/worker/midden-worker-client.js +246 -0
  220. package/dist/worker/midden-worker-client.test.d.ts +2 -0
  221. package/dist/worker/midden-worker-client.test.d.ts.map +1 -0
  222. package/dist/worker/midden-worker-client.test.js +151 -0
  223. package/dist/worker/midden-worker-contract.d.ts +87 -0
  224. package/dist/worker/midden-worker-contract.d.ts.map +1 -0
  225. package/dist/worker/midden-worker-contract.js +30 -0
  226. package/package.json +90 -0
@@ -0,0 +1,97 @@
1
+ // pushable test doubles for the automerge/transfer transport surface: a
2
+ // `BiStreamLike` whose reads and writes a test can drive by hand, and a
3
+ // `MiddenStreamNode`-shaped node that opens/accepts those streams.
4
+ //
5
+ // every method is a `vi.fn()` so a consuming test can assert on call
6
+ // counts/arguments or override a single call's behavior (`mockResolvedValueOnce`,
7
+ // `mockRejectedValueOnce`, ...) without replacing the whole double.
8
+ //
9
+ // dialing (`open_bi`) on the mock node always succeeds and hands back a
10
+ // fresh mock stream addressed to whatever peer id was dialed - a test
11
+ // wanting to simulate a dial failure overrides `open_bi` for one call via
12
+ // `mockRejectedValueOnce` instead.
13
+ import { vi } from "vitest";
14
+ /** creates a mock bidirectional stream addressed to `peerId`. reads block
15
+ * until a message is pushed via `pushMessage`; writes are recorded in
16
+ * `_written` for assertions. */
17
+ export function createMockBiStream(peerId, alpn = "iroh/automerge-repo/1") {
18
+ const stream = {
19
+ _messageQueue: [],
20
+ _written: [],
21
+ _closed: false,
22
+ _readResolvers: [],
23
+ peer_node_id: () => peerId,
24
+ alpn: () => alpn,
25
+ write_message: vi.fn(async (data) => {
26
+ stream._written.push(data);
27
+ }),
28
+ read_message: vi.fn(async () => {
29
+ if (stream._messageQueue.length > 0) {
30
+ return stream._messageQueue.shift();
31
+ }
32
+ return new Promise((resolve) => {
33
+ stream._readResolvers.push(resolve);
34
+ });
35
+ }),
36
+ read_to_end: vi.fn(async (max_size) => {
37
+ if (stream._messageQueue.length > 0) {
38
+ const data = stream._messageQueue.shift();
39
+ return data ?? new Uint8Array(0);
40
+ }
41
+ return new Promise((resolve) => {
42
+ const originalResolver = (value) => {
43
+ resolve(value ?? new Uint8Array(0));
44
+ };
45
+ stream._readResolvers.push(originalResolver);
46
+ });
47
+ }),
48
+ write_raw_and_finish: vi.fn(async (data) => {
49
+ stream._written.push(data);
50
+ stream._closed = true;
51
+ }),
52
+ close: vi.fn(() => {
53
+ stream._closed = true;
54
+ for (const resolve of stream._readResolvers) {
55
+ resolve(null);
56
+ }
57
+ stream._readResolvers = [];
58
+ }),
59
+ pushMessage(data) {
60
+ if (stream._readResolvers.length > 0) {
61
+ stream._readResolvers.shift()(data);
62
+ }
63
+ else {
64
+ stream._messageQueue.push(data);
65
+ }
66
+ },
67
+ };
68
+ return stream;
69
+ }
70
+ /** creates a mock midden node identified by `nodeId`. */
71
+ export function createMockMidden(nodeId = "a".repeat(64)) {
72
+ const acceptQueue = [];
73
+ const acceptResolvers = [];
74
+ const midden = {
75
+ node_id: () => nodeId,
76
+ open_bi: vi.fn(async (addr) => {
77
+ return createMockBiStream(addr);
78
+ }),
79
+ accept: vi.fn(async () => {
80
+ if (acceptQueue.length > 0) {
81
+ return acceptQueue.shift();
82
+ }
83
+ return new Promise((resolve) => {
84
+ acceptResolvers.push(resolve);
85
+ });
86
+ }),
87
+ pushIncoming(stream) {
88
+ if (acceptResolvers.length > 0) {
89
+ acceptResolvers.shift()(stream);
90
+ }
91
+ else {
92
+ acceptQueue.push(stream);
93
+ }
94
+ },
95
+ };
96
+ return midden;
97
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mock-node.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-node.test.d.ts","sourceRoot":"","sources":["../../src/testing/mock-node.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockBiStream, createMockMidden } from "./mock-node.js";
3
+ describe("createMockBiStream", () => {
4
+ it("reports the peer id and alpn it was created with", () => {
5
+ const stream = createMockBiStream("peer-x", "test-alpn/1");
6
+ expect(stream.peer_node_id()).toBe("peer-x");
7
+ expect(stream.alpn()).toBe("test-alpn/1");
8
+ });
9
+ it("defaults to the automerge sync alpn", () => {
10
+ const stream = createMockBiStream("peer-x");
11
+ expect(stream.alpn()).toBe("iroh/automerge-repo/1");
12
+ });
13
+ it("records every write in _written", async () => {
14
+ const stream = createMockBiStream("peer-x");
15
+ const a = new Uint8Array([1, 2, 3]);
16
+ const b = new Uint8Array([4, 5]);
17
+ await stream.write_message(a);
18
+ await stream.write_message(b);
19
+ expect(stream._written).toEqual([a, b]);
20
+ });
21
+ it("read_message resolves with a message pushed before the read starts", async () => {
22
+ const stream = createMockBiStream("peer-x");
23
+ const data = new Uint8Array([9, 9]);
24
+ stream.pushMessage(data);
25
+ await expect(stream.read_message()).resolves.toBe(data);
26
+ });
27
+ it("read_message resolves once a message is pushed after the read starts", async () => {
28
+ const stream = createMockBiStream("peer-x");
29
+ const pending = stream.read_message();
30
+ const data = new Uint8Array([7]);
31
+ stream.pushMessage(data);
32
+ await expect(pending).resolves.toBe(data);
33
+ });
34
+ it("close() marks the stream closed and resolves any pending read with null", async () => {
35
+ const stream = createMockBiStream("peer-x");
36
+ const pending = stream.read_message();
37
+ stream.close();
38
+ expect(stream._closed).toBe(true);
39
+ await expect(pending).resolves.toBeNull();
40
+ });
41
+ });
42
+ describe("createMockMidden", () => {
43
+ it("reports the node id it was created with, defaulting to a 64-char id", () => {
44
+ const midden = createMockMidden("my-node");
45
+ expect(midden.node_id()).toBe("my-node");
46
+ expect(createMockMidden().node_id()).toHaveLength(64);
47
+ });
48
+ it("open_bi resolves with a mock stream addressed to the dialed peer", async () => {
49
+ const midden = createMockMidden();
50
+ const stream = await midden.open_bi("peer-y", "some-alpn");
51
+ expect(stream.peer_node_id()).toBe("peer-y");
52
+ });
53
+ it("accept resolves with a stream pushed before the accept starts", async () => {
54
+ const midden = createMockMidden();
55
+ const incoming = createMockBiStream("peer-z");
56
+ midden.pushIncoming(incoming);
57
+ await expect(midden.accept()).resolves.toBe(incoming);
58
+ });
59
+ it("accept resolves once a stream is pushed after the accept starts", async () => {
60
+ const midden = createMockMidden();
61
+ const pending = midden.accept();
62
+ const incoming = createMockBiStream("peer-z");
63
+ midden.pushIncoming(incoming);
64
+ await expect(pending).resolves.toBe(incoming);
65
+ });
66
+ });
@@ -0,0 +1,68 @@
1
+ import type { BiStreamLike } from "../automerge/types.js";
2
+ /** encodes a message as utf-8 json bytes. */
3
+ export declare function encodeJsonMessage(message: unknown): Uint8Array;
4
+ /** decodes utf-8 json bytes back into a message. */
5
+ export declare function decodeJsonMessage<TMessage = unknown>(data: Uint8Array): TMessage;
6
+ export interface CollectingStreamOptions<TMessage> {
7
+ peerId?: string;
8
+ alpn?: string;
9
+ /** decodes a written frame into a message for `sent`. defaults to
10
+ * `decodeJsonMessage`. */
11
+ decode?(data: Uint8Array): TMessage;
12
+ }
13
+ /**
14
+ * a `BiStreamLike` double that never has anything to read and simply
15
+ * records every write - for exercising the serving side of a protocol in
16
+ * isolation (assert on what got sent back, without needing a live peer to
17
+ * read it).
18
+ */
19
+ export declare class CollectingStream<TMessage = unknown> implements BiStreamLike {
20
+ readonly sent: TMessage[];
21
+ private isClosed;
22
+ private readonly peerId;
23
+ private readonly streamAlpn;
24
+ private readonly decode;
25
+ constructor(options?: CollectingStreamOptions<TMessage>);
26
+ write_message(data: Uint8Array): Promise<void>;
27
+ read_message(): Promise<Uint8Array | null>;
28
+ close(): void;
29
+ get closed(): boolean;
30
+ peer_node_id(): string;
31
+ alpn(): string;
32
+ }
33
+ /** what a scripted serving stream knows about one blob, keyed by whatever
34
+ * id the request message addresses it by. */
35
+ export interface ServingStreamEntry {
36
+ blake3: string;
37
+ size: number;
38
+ mime?: string;
39
+ }
40
+ export interface MakeServingStreamOptions<TMessage> {
41
+ peerId?: string;
42
+ alpn?: string;
43
+ encode?(message: TMessage): Uint8Array;
44
+ decode?(data: Uint8Array): TMessage;
45
+ /** pulls the requested blob's id out of a decoded write, or `null` when
46
+ * the message isn't a blob request. defaults to reading
47
+ * `{ type: "blob_request", id }`. */
48
+ requestId?(message: TMessage): string | null;
49
+ /** builds the reply for a request id, given the matching table entry
50
+ * (or `undefined` on a miss). defaults to
51
+ * `{ type: "blob_ready", id, blake3, size, mime }` on a hit and
52
+ * `{ type: "error", code: "blob_not_found", id }` on a miss. */
53
+ buildReply?(id: string, entry: ServingStreamEntry | undefined): TMessage;
54
+ }
55
+ /**
56
+ * a `BiStreamLike` double that scripts replies to blob-request-shaped
57
+ * messages from a blake3-keyed lookup table - the fetching side of a
58
+ * protocol driven against fixed, known responses instead of a live peer.
59
+ * every write is checked against `requestId`; a match queues a
60
+ * `buildReply` result to be read back by the next `read_message()` call.
61
+ */
62
+ export declare function makeServingStream<TMessage = {
63
+ type: string;
64
+ id: string;
65
+ }>(table: Record<string, ServingStreamEntry>, options?: MakeServingStreamOptions<TMessage>): BiStreamLike & {
66
+ closed: boolean;
67
+ };
68
+ //# sourceMappingURL=streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../src/testing/streams.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,6CAA6C;AAC7C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAE9D;AAED,oDAAoD;AACpD,wBAAgB,iBAAiB,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,QAAQ,CAEhF;AAED,MAAM,WAAW,uBAAuB,CAAC,QAAQ;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;+BAC2B;IAC3B,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAC;CACrC;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB,CAAC,QAAQ,GAAG,OAAO,CAAE,YAAW,YAAY;IACvE,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAM;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;gBAE5C,OAAO,GAAE,uBAAuB,CAAC,QAAQ,CAAM;IAMrD,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,YAAY,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAIhD,KAAK,IAAI,IAAI;IAIb,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,YAAY,IAAI,MAAM;IAItB,IAAI,IAAI,MAAM;CAGf;AAED;8CAC8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB,CAAC,QAAQ;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,CAAC;IACvC,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAC;IACpC;;0CAEsC;IACtC,SAAS,CAAC,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7C;;;qEAGiE;IACjE,UAAU,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC1E;AAcD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EACvE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACzC,OAAO,GAAE,wBAAwB,CAAC,QAAQ,CAAM,GAC/C,YAAY,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CA+BpC"}
@@ -0,0 +1,99 @@
1
+ // generic `BiStreamLike` doubles for exercising a request/reply protocol
2
+ // built on top of length-delimited messages, without any real peer on the
3
+ // other end: `CollectingStream` records every write for later assertions,
4
+ // and `makeServingStream` scripts replies to blob-request-shaped messages
5
+ // from a blake3-keyed lookup table.
6
+ //
7
+ // both default to a plain json wire format (`encodeJsonMessage`/
8
+ // `decodeJsonMessage`) since this package has no wire protocol of its
9
+ // own to assume - a consumer with its own message codec (cbor, a custom
10
+ // binary framing, ...) supplies its own `encode`/`decode` instead.
11
+ const DEFAULT_ALPN = "reliquary/testing/1";
12
+ const DEFAULT_PEER_ID = "peer-a";
13
+ /** encodes a message as utf-8 json bytes. */
14
+ export function encodeJsonMessage(message) {
15
+ return new TextEncoder().encode(JSON.stringify(message));
16
+ }
17
+ /** decodes utf-8 json bytes back into a message. */
18
+ export function decodeJsonMessage(data) {
19
+ return JSON.parse(new TextDecoder().decode(data));
20
+ }
21
+ /**
22
+ * a `BiStreamLike` double that never has anything to read and simply
23
+ * records every write - for exercising the serving side of a protocol in
24
+ * isolation (assert on what got sent back, without needing a live peer to
25
+ * read it).
26
+ */
27
+ export class CollectingStream {
28
+ sent = [];
29
+ isClosed = false;
30
+ peerId;
31
+ streamAlpn;
32
+ decode;
33
+ constructor(options = {}) {
34
+ this.peerId = options.peerId ?? DEFAULT_PEER_ID;
35
+ this.streamAlpn = options.alpn ?? DEFAULT_ALPN;
36
+ this.decode = options.decode ?? decodeJsonMessage;
37
+ }
38
+ async write_message(data) {
39
+ this.sent.push(this.decode(data));
40
+ }
41
+ async read_message() {
42
+ return null;
43
+ }
44
+ close() {
45
+ this.isClosed = true;
46
+ }
47
+ get closed() {
48
+ return this.isClosed;
49
+ }
50
+ peer_node_id() {
51
+ return this.peerId;
52
+ }
53
+ alpn() {
54
+ return this.streamAlpn;
55
+ }
56
+ }
57
+ function defaultRequestId(message) {
58
+ const m = message;
59
+ return m.type === "blob_request" && typeof m.id === "string" ? m.id : null;
60
+ }
61
+ function defaultBuildReply(id, entry) {
62
+ if (!entry) {
63
+ return { type: "error", code: "blob_not_found", id };
64
+ }
65
+ return { type: "blob_ready", id, blake3: entry.blake3, size: entry.size, mime: entry.mime };
66
+ }
67
+ /**
68
+ * a `BiStreamLike` double that scripts replies to blob-request-shaped
69
+ * messages from a blake3-keyed lookup table - the fetching side of a
70
+ * protocol driven against fixed, known responses instead of a live peer.
71
+ * every write is checked against `requestId`; a match queues a
72
+ * `buildReply` result to be read back by the next `read_message()` call.
73
+ */
74
+ export function makeServingStream(table, options = {}) {
75
+ const encode = options.encode ?? encodeJsonMessage;
76
+ const decode = options.decode ?? decodeJsonMessage;
77
+ const requestId = options.requestId ?? defaultRequestId;
78
+ const buildReply = options.buildReply ?? defaultBuildReply;
79
+ const replies = [];
80
+ return {
81
+ closed: false,
82
+ async write_message(data) {
83
+ const message = decode(data);
84
+ const id = requestId(message);
85
+ if (id !== null) {
86
+ replies.push(buildReply(id, table[id]));
87
+ }
88
+ },
89
+ async read_message() {
90
+ const message = replies.shift();
91
+ return message === undefined ? null : encode(message);
92
+ },
93
+ close() {
94
+ this.closed = true;
95
+ },
96
+ peer_node_id: () => options.peerId ?? DEFAULT_PEER_ID,
97
+ alpn: () => options.alpn ?? DEFAULT_ALPN,
98
+ };
99
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=streams.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.test.d.ts","sourceRoot":"","sources":["../../src/testing/streams.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { CollectingStream, decodeJsonMessage, encodeJsonMessage, makeServingStream } from "./streams.js";
3
+ describe("encodeJsonMessage / decodeJsonMessage", () => {
4
+ it("round-trips a message", () => {
5
+ const message = { type: "ping", n: 3 };
6
+ expect(decodeJsonMessage(encodeJsonMessage(message))).toEqual(message);
7
+ });
8
+ });
9
+ describe("CollectingStream", () => {
10
+ it("decodes and records every write", async () => {
11
+ const stream = new CollectingStream();
12
+ await stream.write_message(encodeJsonMessage({ type: "hello" }));
13
+ await stream.write_message(encodeJsonMessage({ type: "world" }));
14
+ expect(stream.sent).toEqual([{ type: "hello" }, { type: "world" }]);
15
+ });
16
+ it("read_message always resolves null", async () => {
17
+ const stream = new CollectingStream();
18
+ await expect(stream.read_message()).resolves.toBeNull();
19
+ });
20
+ it("tracks closed state", () => {
21
+ const stream = new CollectingStream();
22
+ expect(stream.closed).toBe(false);
23
+ stream.close();
24
+ expect(stream.closed).toBe(true);
25
+ });
26
+ it("uses the given peer id/alpn, defaulting when omitted", () => {
27
+ const withDefaults = new CollectingStream();
28
+ expect(withDefaults.peer_node_id()).toBe("peer-a");
29
+ expect(withDefaults.alpn()).toBe("reliquary/testing/1");
30
+ const withOverrides = new CollectingStream({ peerId: "peer-q", alpn: "custom/1" });
31
+ expect(withOverrides.peer_node_id()).toBe("peer-q");
32
+ expect(withOverrides.alpn()).toBe("custom/1");
33
+ });
34
+ it("decodes writes with a caller-supplied codec", async () => {
35
+ const stream = new CollectingStream({ decode: (data) => new TextDecoder().decode(data) });
36
+ await stream.write_message(new TextEncoder().encode("raw text"));
37
+ expect(stream.sent).toEqual(["raw text"]);
38
+ });
39
+ });
40
+ describe("makeServingStream", () => {
41
+ const table = {
42
+ "blob-1": { blake3: "blake3-1", size: 10, mime: "audio/wav" },
43
+ };
44
+ it("replies blob_ready for a known id", async () => {
45
+ const stream = makeServingStream(table);
46
+ await stream.write_message(encodeJsonMessage({ type: "blob_request", id: "blob-1" }));
47
+ const reply = decodeJsonMessage(await stream.read_message());
48
+ expect(reply).toEqual({ type: "blob_ready", id: "blob-1", blake3: "blake3-1", size: 10, mime: "audio/wav" });
49
+ });
50
+ it("replies blob_not_found for an unknown id", async () => {
51
+ const stream = makeServingStream(table);
52
+ await stream.write_message(encodeJsonMessage({ type: "blob_request", id: "missing" }));
53
+ const reply = decodeJsonMessage(await stream.read_message());
54
+ expect(reply).toEqual({ type: "error", code: "blob_not_found", id: "missing" });
55
+ });
56
+ it("read_message resolves null once every scripted reply has been read", async () => {
57
+ const stream = makeServingStream(table);
58
+ await stream.write_message(encodeJsonMessage({ type: "blob_request", id: "blob-1" }));
59
+ await stream.read_message();
60
+ await expect(stream.read_message()).resolves.toBeNull();
61
+ });
62
+ it("ignores writes that aren't blob requests", async () => {
63
+ const stream = makeServingStream(table);
64
+ await stream.write_message(encodeJsonMessage({ type: "something_else" }));
65
+ await expect(stream.read_message()).resolves.toBeNull();
66
+ });
67
+ it("tracks closed state", () => {
68
+ const stream = makeServingStream(table);
69
+ expect(stream.closed).toBe(false);
70
+ stream.close();
71
+ expect(stream.closed).toBe(true);
72
+ });
73
+ it("supports a caller-supplied requestId/buildReply pair for a custom protocol", async () => {
74
+ const stream = makeServingStream(table, {
75
+ requestId: (m) => (m.kind === "get" ? (m.hash ?? null) : null),
76
+ buildReply: (id, entry) => entry ? { kind: "got", hash: id, blake3: entry.blake3, size: entry.size } : { kind: "miss", hash: id },
77
+ });
78
+ await stream.write_message(encodeJsonMessage({ kind: "get", hash: "blob-1" }));
79
+ const reply = decodeJsonMessage((await stream.read_message()));
80
+ expect(reply).toEqual({ kind: "got", hash: "blob-1", blake3: "blake3-1", size: 10 });
81
+ });
82
+ });
@@ -0,0 +1,8 @@
1
+ /** exact error message a transport uses for a deliberately cancelled or
2
+ * paused download (as opposed to a transport failure). */
3
+ export declare const DOWNLOAD_CANCELLED_MESSAGE = "download cancelled";
4
+ /** true when `err` represents a deliberate pause/cancel (an AbortError, or
5
+ * the exact cancelled-download message above), as opposed to a genuine
6
+ * transfer failure. */
7
+ export declare function isCancelledError(err: unknown): boolean;
8
+ //# sourceMappingURL=cancellation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancellation.d.ts","sourceRoot":"","sources":["../../src/transfer/cancellation.ts"],"names":[],"mappings":"AAQA;2DAC2D;AAC3D,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAE/D;;wBAEwB;AACxB,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAItD"}
@@ -0,0 +1,19 @@
1
+ // cancellation detection shared by every ./transfer flow.
2
+ //
3
+ // a deliberate pause/cancel of an in-flight download must never be
4
+ // mistaken for a genuine failure: the per-peer retry loop in `snatch.ts`
5
+ // rethrows a cancelled error immediately instead of trying the next peer,
6
+ // and the disk-streaming path leaves the writable exactly as it is
7
+ // (no truncate) since a resumed download rewrites the same byte offsets.
8
+ /** exact error message a transport uses for a deliberately cancelled or
9
+ * paused download (as opposed to a transport failure). */
10
+ export const DOWNLOAD_CANCELLED_MESSAGE = "download cancelled";
11
+ /** true when `err` represents a deliberate pause/cancel (an AbortError, or
12
+ * the exact cancelled-download message above), as opposed to a genuine
13
+ * transfer failure. */
14
+ export function isCancelledError(err) {
15
+ if (err instanceof DOMException && err.name === "AbortError")
16
+ return true;
17
+ const message = err instanceof Error ? err.message : String(err);
18
+ return message.includes(DOWNLOAD_CANCELLED_MESSAGE);
19
+ }
@@ -0,0 +1,8 @@
1
+ export type { BlobCapableNode, DiskSnatchResult, SnatchInfo, SnatchOptions, SnatchResult, } from "./types.js";
2
+ export { DOWNLOAD_CANCELLED_MESSAGE, isCancelledError } from "./cancellation.js";
3
+ export { discardPausedDownload, pauseSnatchDownload, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
4
+ export { BlobServer, DEFAULT_RELEASE_AFTER_MS, serveBlobRequest, } from "./serve.js";
5
+ export type { BlobServerOptions, ServedBlobInfo } from "./serve.js";
6
+ export { createPrefetcher, Prefetcher } from "./prefetch.js";
7
+ export type { PrefetchOptions } from "./prefetch.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transfer/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEjF,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EACV,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,UAAU,EACV,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEpE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { DOWNLOAD_CANCELLED_MESSAGE, isCancelledError } from "./cancellation.js";
2
+ export { discardPausedDownload, pauseSnatchDownload, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
3
+ export { BlobServer, DEFAULT_RELEASE_AFTER_MS, serveBlobRequest, } from "./serve.js";
4
+ export { createPrefetcher, Prefetcher } from "./prefetch.js";
@@ -0,0 +1,38 @@
1
+ /** what one prefetch run needs to know how to do. */
2
+ export interface PrefetchOptions<T> {
3
+ /** total budget consumed as items are selected for this run; selection
4
+ * stops once the budget is exhausted (or the item list runs out). */
5
+ budget: number;
6
+ /** cost of fetching one item, subtracted from the budget as items are
7
+ * selected. */
8
+ costOf(item: T): number;
9
+ /** fetch one selected item. */
10
+ fetchItem(item: T): Promise<void>;
11
+ /** how many items to fetch concurrently. default 3. */
12
+ concurrency?: number;
13
+ /** called once per item, when it is selected for this run (before any
14
+ * fetching starts) - useful for a "pending" ui state. */
15
+ onPending?(item: T): void;
16
+ /** called once per item whose `onPending` fired, when its fetch settles
17
+ * (success, failure, or superseded by a newer `run()` before it was
18
+ * reached) - always paired with a prior `onPending` call. */
19
+ onSettled?(item: T): void;
20
+ }
21
+ /**
22
+ * a cancellable prefetch runner. calling `run()` again before a prior run
23
+ * finishes supersedes it: a run-counter check before every selection and
24
+ * batch step stops the superseded run from selecting or fetching anything
25
+ * further, without needing an `AbortController` threaded through
26
+ * `fetchItem` itself.
27
+ */
28
+ export declare class Prefetcher<T> {
29
+ private runCounter;
30
+ /** fire-and-forget: start a new run over `items`, superseding any run
31
+ * still in flight. */
32
+ run(items: T[], options: PrefetchOptions<T>): void;
33
+ private isCurrent;
34
+ private execute;
35
+ }
36
+ /** construct a fresh, independent prefetch runner. */
37
+ export declare function createPrefetcher<T>(): Prefetcher<T>;
38
+ //# sourceMappingURL=prefetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefetch.d.ts","sourceRoot":"","sources":["../../src/transfer/prefetch.ts"],"names":[],"mappings":"AASA,qDAAqD;AACrD,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC;0EACsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf;oBACgB;IAChB,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC;IACxB,+BAA+B;IAC/B,SAAS,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;8DAC0D;IAC1D,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAC1B;;kEAE8D;IAC9D,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,qBAAa,UAAU,CAAC,CAAC;IACvB,OAAO,CAAC,UAAU,CAAK;IAEvB;2BACuB;IACvB,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI;IAKlD,OAAO,CAAC,SAAS;YAIH,OAAO;CA8BtB;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAEnD"}
@@ -0,0 +1,61 @@
1
+ // budget-limited, cancellable prefetch: select a prefix of "upcoming"
2
+ // items whose cumulative cost fits a budget (e.g. seconds of playback,
3
+ // bytes of storage), then fetch them in concurrent batches.
4
+ //
5
+ // the domain concept (what an item is, what it costs, how to fetch it)
6
+ // stays with the caller via `costOf`/`fetchItem`; this module only owns
7
+ // the budget selection, concurrency batching, and run-counter
8
+ // cancellation.
9
+ /**
10
+ * a cancellable prefetch runner. calling `run()` again before a prior run
11
+ * finishes supersedes it: a run-counter check before every selection and
12
+ * batch step stops the superseded run from selecting or fetching anything
13
+ * further, without needing an `AbortController` threaded through
14
+ * `fetchItem` itself.
15
+ */
16
+ export class Prefetcher {
17
+ runCounter = 0;
18
+ /** fire-and-forget: start a new run over `items`, superseding any run
19
+ * still in flight. */
20
+ run(items, options) {
21
+ const run = ++this.runCounter;
22
+ void this.execute(run, items, options);
23
+ }
24
+ isCurrent(run) {
25
+ return run === this.runCounter;
26
+ }
27
+ async execute(run, items, options) {
28
+ const concurrency = options.concurrency ?? 3;
29
+ let budget = options.budget;
30
+ const selected = [];
31
+ for (const item of items) {
32
+ if (!this.isCurrent(run))
33
+ return;
34
+ if (budget <= 0)
35
+ break;
36
+ budget -= options.costOf(item);
37
+ options.onPending?.(item);
38
+ selected.push(item);
39
+ }
40
+ for (let i = 0; i < selected.length; i += concurrency) {
41
+ if (!this.isCurrent(run)) {
42
+ for (const item of selected.slice(i))
43
+ options.onSettled?.(item);
44
+ return;
45
+ }
46
+ const batch = selected.slice(i, i + concurrency);
47
+ await Promise.allSettled(batch.map(async (item) => {
48
+ try {
49
+ await options.fetchItem(item);
50
+ }
51
+ finally {
52
+ options.onSettled?.(item);
53
+ }
54
+ }));
55
+ }
56
+ }
57
+ }
58
+ /** construct a fresh, independent prefetch runner. */
59
+ export function createPrefetcher() {
60
+ return new Prefetcher();
61
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prefetch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefetch.test.d.ts","sourceRoot":"","sources":["../../src/transfer/prefetch.test.ts"],"names":[],"mappings":""}