@freqhole/reliquary 0.2.8 → 0.2.10
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/dist/blobs/db.d.ts +5 -0
- package/dist/blobs/db.d.ts.map +1 -1
- package/dist/blobs/db.js +100 -2
- package/dist/blobs/db.test.js +32 -1
- package/dist/blobs/store.d.ts +8 -0
- package/dist/blobs/store.d.ts.map +1 -1
- package/dist/blobs/store.js +6 -1
- package/dist/transfer/index.d.ts +1 -1
- package/dist/transfer/index.d.ts.map +1 -1
- package/dist/transfer/index.js +1 -1
- package/dist/transfer/snatch.d.ts +8 -0
- package/dist/transfer/snatch.d.ts.map +1 -1
- package/dist/transfer/snatch.js +12 -0
- package/dist/transfer/snatch.test.js +20 -1
- package/dist/transfer/types.d.ts +5 -0
- package/dist/transfer/types.d.ts.map +1 -1
- package/dist/worker/blob-worker-client.d.ts +5 -0
- package/dist/worker/blob-worker-client.d.ts.map +1 -1
- package/dist/worker/blob-worker-client.js +17 -0
- package/dist/worker/blob-worker-client.test.js +16 -0
- package/dist/worker/blob-worker-logic.d.ts +5 -0
- package/dist/worker/blob-worker-logic.d.ts.map +1 -1
- package/dist/worker/blob-worker-logic.js +20 -0
- package/dist/worker/blob-worker-logic.test.js +16 -0
- package/dist/worker/midden-worker-client.d.ts +7 -1
- package/dist/worker/midden-worker-client.d.ts.map +1 -1
- package/dist/worker/midden-worker-client.js +10 -0
- package/dist/worker/midden-worker-client.test.js +2 -0
- package/dist/worker/midden-worker-contract.d.ts +17 -0
- package/dist/worker/midden-worker-contract.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/blobs/db.d.ts
CHANGED
|
@@ -5,4 +5,9 @@ export declare function getRecordBySha256(dbName: string, sha256: string): Promi
|
|
|
5
5
|
export declare function getRecordByBlake3(dbName: string, blake3: string): Promise<BlobRecord | null>;
|
|
6
6
|
export declare function deleteRecord(dbName: string, blobId: string): Promise<void>;
|
|
7
7
|
export declare function clearRecords(dbName: string): Promise<void>;
|
|
8
|
+
export declare function addCanvasRef(dbName: string, blobId: string, canvasDocId: string): Promise<void>;
|
|
9
|
+
export declare function removeCanvasRef(dbName: string, blobId: string, canvasDocId: string): Promise<void>;
|
|
10
|
+
export declare function getCanvasRefs(dbName: string, blobId: string): Promise<string[]>;
|
|
11
|
+
export declare function removeAllCanvasRefsForCanvas(dbName: string, canvasDocId: string): Promise<void>;
|
|
12
|
+
export declare function clearCanvasRefs(dbName: string): Promise<void>;
|
|
8
13
|
//# sourceMappingURL=db.d.ts.map
|
package/dist/blobs/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/blobs/db.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/blobs/db.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA8E7C,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjF;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAS1F;AAkBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAE5F;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAE5F;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAchF;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAchE;AAQD,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrG;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcxG;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYrF;AAED,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBrG;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcnE"}
|
package/dist/blobs/db.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// rather than holding one long-lived connection open - simple, and avoids
|
|
9
9
|
// a version-change block from another tab ever having anything to wait on.
|
|
10
10
|
const STORE_NAME = "blobs";
|
|
11
|
-
const
|
|
11
|
+
const REFS_STORE_NAME = "blob_canvas_refs";
|
|
12
|
+
const DB_VERSION = 2;
|
|
12
13
|
function openVersioned(dbName, version) {
|
|
13
14
|
return new Promise((resolve, reject) => {
|
|
14
15
|
const req = indexedDB.open(dbName, version);
|
|
@@ -21,6 +22,16 @@ function openVersioned(dbName, version) {
|
|
|
21
22
|
store.createIndex("blob_type", "blob_type", { unique: false });
|
|
22
23
|
store.createIndex("parent_blob_id", "parent_blob_id", { unique: false });
|
|
23
24
|
}
|
|
25
|
+
if (!db.objectStoreNames.contains(REFS_STORE_NAME)) {
|
|
26
|
+
// tracks which canvas documents currently have a widget
|
|
27
|
+
// referencing a given blob - a widget-delete cleanup can check
|
|
28
|
+
// this before purging local bytes, instead of scanning every
|
|
29
|
+
// canvas. composite key so re-adding an existing ref is a no-op
|
|
30
|
+
// (put with the same key overwrites, add() would throw).
|
|
31
|
+
const refs = db.createObjectStore(REFS_STORE_NAME, { keyPath: ["blob_id", "canvas_doc_id"] });
|
|
32
|
+
refs.createIndex("blob_id", "blob_id", { unique: false });
|
|
33
|
+
refs.createIndex("canvas_doc_id", "canvas_doc_id", { unique: false });
|
|
34
|
+
}
|
|
24
35
|
};
|
|
25
36
|
req.onsuccess = () => resolve(req.result);
|
|
26
37
|
req.onerror = () => reject(req.error);
|
|
@@ -59,7 +70,7 @@ async function openDb(dbName) {
|
|
|
59
70
|
req.onerror = () => reject(req.error);
|
|
60
71
|
});
|
|
61
72
|
const currentVersion = probe.version;
|
|
62
|
-
const hasStore = probe.objectStoreNames.contains(STORE_NAME);
|
|
73
|
+
const hasStore = probe.objectStoreNames.contains(STORE_NAME) && probe.objectStoreNames.contains(REFS_STORE_NAME);
|
|
63
74
|
probe.close();
|
|
64
75
|
const targetVersion = hasStore ? currentVersion : currentVersion + 1;
|
|
65
76
|
resolvedVersions.set(dbName, targetVersion);
|
|
@@ -139,3 +150,90 @@ export async function clearRecords(dbName) {
|
|
|
139
150
|
};
|
|
140
151
|
});
|
|
141
152
|
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// blob/canvas reference index - which canvas docs currently have a widget
|
|
155
|
+
// referencing a given blob, so a widget-delete cleanup can check this
|
|
156
|
+
// before purging local bytes instead of scanning every canvas.
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
export async function addCanvasRef(dbName, blobId, canvasDocId) {
|
|
159
|
+
const db = await openDb(dbName);
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const tx = db.transaction(REFS_STORE_NAME, "readwrite");
|
|
162
|
+
tx.objectStore(REFS_STORE_NAME).put({ blob_id: blobId, canvas_doc_id: canvasDocId });
|
|
163
|
+
tx.oncomplete = () => {
|
|
164
|
+
db.close();
|
|
165
|
+
resolve();
|
|
166
|
+
};
|
|
167
|
+
tx.onerror = () => {
|
|
168
|
+
db.close();
|
|
169
|
+
reject(tx.error);
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export async function removeCanvasRef(dbName, blobId, canvasDocId) {
|
|
174
|
+
const db = await openDb(dbName);
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const tx = db.transaction(REFS_STORE_NAME, "readwrite");
|
|
177
|
+
tx.objectStore(REFS_STORE_NAME).delete([blobId, canvasDocId]);
|
|
178
|
+
tx.oncomplete = () => {
|
|
179
|
+
db.close();
|
|
180
|
+
resolve();
|
|
181
|
+
};
|
|
182
|
+
tx.onerror = () => {
|
|
183
|
+
db.close();
|
|
184
|
+
reject(tx.error);
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
export async function getCanvasRefs(dbName, blobId) {
|
|
189
|
+
const db = await openDb(dbName);
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
const tx = db.transaction(REFS_STORE_NAME, "readonly");
|
|
192
|
+
const req = tx.objectStore(REFS_STORE_NAME).index("blob_id").getAll(blobId);
|
|
193
|
+
req.onsuccess = () => {
|
|
194
|
+
const rows = req.result ?? [];
|
|
195
|
+
resolve(rows.map((r) => r.canvas_doc_id));
|
|
196
|
+
};
|
|
197
|
+
req.onerror = () => reject(req.error);
|
|
198
|
+
tx.oncomplete = () => db.close();
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
export async function removeAllCanvasRefsForCanvas(dbName, canvasDocId) {
|
|
202
|
+
const db = await openDb(dbName);
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const tx = db.transaction(REFS_STORE_NAME, "readwrite");
|
|
205
|
+
const store = tx.objectStore(REFS_STORE_NAME);
|
|
206
|
+
const req = store.index("canvas_doc_id").openCursor(IDBKeyRange.only(canvasDocId));
|
|
207
|
+
req.onsuccess = () => {
|
|
208
|
+
const cursor = req.result;
|
|
209
|
+
if (cursor) {
|
|
210
|
+
cursor.delete();
|
|
211
|
+
cursor.continue();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
req.onerror = () => reject(req.error);
|
|
215
|
+
tx.oncomplete = () => {
|
|
216
|
+
db.close();
|
|
217
|
+
resolve();
|
|
218
|
+
};
|
|
219
|
+
tx.onerror = () => {
|
|
220
|
+
db.close();
|
|
221
|
+
reject(tx.error);
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
export async function clearCanvasRefs(dbName) {
|
|
226
|
+
const db = await openDb(dbName);
|
|
227
|
+
return new Promise((resolve, reject) => {
|
|
228
|
+
const tx = db.transaction(REFS_STORE_NAME, "readwrite");
|
|
229
|
+
tx.objectStore(REFS_STORE_NAME).clear();
|
|
230
|
+
tx.oncomplete = () => {
|
|
231
|
+
db.close();
|
|
232
|
+
resolve();
|
|
233
|
+
};
|
|
234
|
+
tx.onerror = () => {
|
|
235
|
+
db.close();
|
|
236
|
+
reject(tx.error);
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
}
|
package/dist/blobs/db.test.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import "fake-indexeddb/auto";
|
|
4
4
|
import { IDBFactory } from "fake-indexeddb";
|
|
5
5
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
6
|
-
import { clearRecords, deleteRecord, getRecord, getRecordByBlake3, getRecordBySha256, putRecord, } from "./db.js";
|
|
6
|
+
import { addCanvasRef, clearCanvasRefs, clearRecords, deleteRecord, getCanvasRefs, getRecord, getRecordByBlake3, getRecordBySha256, putRecord, removeAllCanvasRefsForCanvas, removeCanvasRef, } from "./db.js";
|
|
7
7
|
const DB_NAME = "blobs-db-test";
|
|
8
8
|
beforeEach(() => {
|
|
9
9
|
globalThis.indexedDB = new IDBFactory();
|
|
@@ -91,6 +91,37 @@ describe("db name isolation", () => {
|
|
|
91
91
|
expect(await getRecord("db-one", "blake3-aaa")).not.toBeNull();
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
|
+
describe("canvas ref index", () => {
|
|
95
|
+
it("tracks refs per blob and de-dupes re-adding the same ref", async () => {
|
|
96
|
+
await addCanvasRef(DB_NAME, "blob-a", "canvas-1");
|
|
97
|
+
await addCanvasRef(DB_NAME, "blob-a", "canvas-2");
|
|
98
|
+
await addCanvasRef(DB_NAME, "blob-a", "canvas-1");
|
|
99
|
+
const refs = (await getCanvasRefs(DB_NAME, "blob-a")).sort();
|
|
100
|
+
expect(refs).toEqual(["canvas-1", "canvas-2"]);
|
|
101
|
+
});
|
|
102
|
+
it("removeCanvasRef removes only the targeted ref", async () => {
|
|
103
|
+
await addCanvasRef(DB_NAME, "blob-b", "canvas-1");
|
|
104
|
+
await addCanvasRef(DB_NAME, "blob-b", "canvas-2");
|
|
105
|
+
await removeCanvasRef(DB_NAME, "blob-b", "canvas-1");
|
|
106
|
+
expect(await getCanvasRefs(DB_NAME, "blob-b")).toEqual(["canvas-2"]);
|
|
107
|
+
});
|
|
108
|
+
it("removeCanvasRef on a ref that never existed is a no-op", async () => {
|
|
109
|
+
await expect(removeCanvasRef(DB_NAME, "blob-c", "canvas-1")).resolves.toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
it("removeAllCanvasRefsForCanvas clears that canvas's refs across every blob", async () => {
|
|
112
|
+
await addCanvasRef(DB_NAME, "blob-d", "canvas-x");
|
|
113
|
+
await addCanvasRef(DB_NAME, "blob-e", "canvas-x");
|
|
114
|
+
await addCanvasRef(DB_NAME, "blob-d", "canvas-y");
|
|
115
|
+
await removeAllCanvasRefsForCanvas(DB_NAME, "canvas-x");
|
|
116
|
+
expect(await getCanvasRefs(DB_NAME, "blob-d")).toEqual(["canvas-y"]);
|
|
117
|
+
expect(await getCanvasRefs(DB_NAME, "blob-e")).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
it("clearCanvasRefs empties the whole ref index", async () => {
|
|
120
|
+
await addCanvasRef(DB_NAME, "blob-f", "canvas-1");
|
|
121
|
+
await clearCanvasRefs(DB_NAME);
|
|
122
|
+
expect(await getCanvasRefs(DB_NAME, "blob-f")).toEqual([]);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
94
125
|
describe("pre-existing database at a higher version", () => {
|
|
95
126
|
it("still reads and writes when dbName already exists at a version above DB_VERSION", async () => {
|
|
96
127
|
// simulates an app's pre-existing database (this module's whole
|
package/dist/blobs/store.d.ts
CHANGED
|
@@ -38,6 +38,14 @@ export interface BlobStore {
|
|
|
38
38
|
checkBlobLocality(blobId: string, blake3?: string): Promise<BlobLocalityInfo>;
|
|
39
39
|
deleteBlob(blobId: string): Promise<void>;
|
|
40
40
|
clearAll(): Promise<void>;
|
|
41
|
+
/** record that `canvasDocId` currently has a widget referencing `blobId`. */
|
|
42
|
+
addCanvasRef(blobId: string, canvasDocId: string): Promise<void>;
|
|
43
|
+
/** remove a single canvas/blob reference (widget deleted or reassigned). */
|
|
44
|
+
removeCanvasRef(blobId: string, canvasDocId: string): Promise<void>;
|
|
45
|
+
/** every canvas doc id currently referencing `blobId`. */
|
|
46
|
+
getCanvasRefs(blobId: string): Promise<string[]>;
|
|
47
|
+
/** remove every ref row for `canvasDocId` (the whole canvas was deleted). */
|
|
48
|
+
removeAllCanvasRefsForCanvas(canvasDocId: string): Promise<void>;
|
|
41
49
|
}
|
|
42
50
|
/** create a blob store instance. each instance owns its own object-url
|
|
43
51
|
* cache; construct one per app (not per component/request). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/blobs/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/blobs/store.ts"],"names":[],"mappings":"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"}
|
package/dist/blobs/store.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// blake3 and shared at the origin regardless of which app wrote it, so it
|
|
21
21
|
// is not parameterized per store instance - see `bytes-backend.ts`.
|
|
22
22
|
import { hashBlake3, hashSha256, streamFileToOpfs } from "../worker/index.js";
|
|
23
|
-
import { clearRecords, deleteRecord, getRecord, getRecordByBlake3, getRecordBySha256, putRecord, } from "./db.js";
|
|
23
|
+
import { addCanvasRef, clearCanvasRefs, clearRecords, deleteRecord, getCanvasRefs, getRecord, getRecordByBlake3, getRecordBySha256, putRecord, removeAllCanvasRefsForCanvas, removeCanvasRef, } from "./db.js";
|
|
24
24
|
import { createOpfsBackend, defaultBytesChain, hasBytesInChain, readThroughChain, removeFromChain, writeThroughChain, } from "./bytes-backend.js";
|
|
25
25
|
export { isOPFSSupported } from "./bytes-backend.js";
|
|
26
26
|
/** default IndexedDB database name for apps that don't need to preserve an
|
|
@@ -237,6 +237,7 @@ export function createBlobStore(options = {}) {
|
|
|
237
237
|
async function clearAll() {
|
|
238
238
|
clearBlobUrlCache();
|
|
239
239
|
await clearRecords(dbName);
|
|
240
|
+
await clearCanvasRefs(dbName);
|
|
240
241
|
await Promise.all(chain.map((backend) => backend.clear()));
|
|
241
242
|
}
|
|
242
243
|
return {
|
|
@@ -255,6 +256,10 @@ export function createBlobStore(options = {}) {
|
|
|
255
256
|
checkBlobLocality,
|
|
256
257
|
deleteBlob,
|
|
257
258
|
clearAll,
|
|
259
|
+
addCanvasRef: (blobId, canvasDocId) => addCanvasRef(dbName, blobId, canvasDocId),
|
|
260
|
+
removeCanvasRef: (blobId, canvasDocId) => removeCanvasRef(dbName, blobId, canvasDocId),
|
|
261
|
+
getCanvasRefs: (blobId) => getCanvasRefs(dbName, blobId),
|
|
262
|
+
removeAllCanvasRefsForCanvas: (canvasDocId) => removeAllCanvasRefsForCanvas(dbName, canvasDocId),
|
|
258
263
|
};
|
|
259
264
|
}
|
|
260
265
|
// low-level building blocks re-exported for callers that need to compose
|
package/dist/transfer/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { BlobCapableNode, DiskSnatchResult, SnatchInfo, SnatchOptions, SnatchResult, } from "./types.js";
|
|
2
2
|
export { DOWNLOAD_CANCELLED_MESSAGE, isCancelledError } from "./cancellation.js";
|
|
3
|
-
export { discardPausedDownload, pauseSnatchDownload, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
|
|
3
|
+
export { discardPausedDownload, pauseSnatchDownload, pauseSnatchDownloadByBlake3, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
|
|
4
4
|
export { BlobServer, DEFAULT_RELEASE_AFTER_MS, serveBlobRequest, } from "./serve.js";
|
|
5
5
|
export type { BlobServerOptions, ServedBlobInfo } from "./serve.js";
|
|
6
6
|
export { createPrefetcher, Prefetcher } from "./prefetch.js";
|
|
@@ -1 +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"}
|
|
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,2BAA2B,EAC3B,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"}
|
package/dist/transfer/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { DOWNLOAD_CANCELLED_MESSAGE, isCancelledError } from "./cancellation.js";
|
|
2
|
-
export { discardPausedDownload, pauseSnatchDownload, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
|
|
2
|
+
export { discardPausedDownload, pauseSnatchDownload, pauseSnatchDownloadByBlake3, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
|
|
3
3
|
export { BlobServer, DEFAULT_RELEASE_AFTER_MS, serveBlobRequest, } from "./serve.js";
|
|
4
4
|
export { createPrefetcher, Prefetcher } from "./prefetch.js";
|
|
@@ -29,6 +29,14 @@ export declare function snatchBlobToDisk(node: BlobCapableNode, peerNodeIds: str
|
|
|
29
29
|
* `download_cancel` or the download already settled.
|
|
30
30
|
*/
|
|
31
31
|
export declare function pauseSnatchDownload(node: BlobCapableNode, downloadId: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* pause every in-flight snatch of this blake3 hash, without needing the
|
|
34
|
+
* `downloadId` the original caller registered (e.g. cleaning up after a
|
|
35
|
+
* caller - a deleted widget - that no longer has its own download state).
|
|
36
|
+
* returns false when `node` has no `download_cancel_by_blake3` or nothing
|
|
37
|
+
* was in flight for this hash.
|
|
38
|
+
*/
|
|
39
|
+
export declare function pauseSnatchDownloadByBlake3(node: BlobCapableNode, blake3Hash: string): Promise<boolean>;
|
|
32
40
|
/**
|
|
33
41
|
* discard a paused partial: releases the gc pin a paused download left
|
|
34
42
|
* behind so the transport can reclaim it. call when the user cancels for
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snatch.d.ts","sourceRoot":"","sources":["../../src/transfer/snatch.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AA0KpB;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,EAAE,EACrB,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAwBvB;AAiDD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,EAAE,EACrB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,4BAA4B,EACtC,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CAsD3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAOf"}
|
|
1
|
+
{"version":3,"file":"snatch.d.ts","sourceRoot":"","sources":["../../src/transfer/snatch.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AA0KpB;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,EAAE,EACrB,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAwBvB;AAiDD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,EAAE,EACrB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,4BAA4B,EACtC,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CAsD3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAOf"}
|
package/dist/transfer/snatch.js
CHANGED
|
@@ -271,6 +271,18 @@ export async function pauseSnatchDownload(node, downloadId) {
|
|
|
271
271
|
return false;
|
|
272
272
|
return (await node.download_cancel(downloadId)) === true;
|
|
273
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* pause every in-flight snatch of this blake3 hash, without needing the
|
|
276
|
+
* `downloadId` the original caller registered (e.g. cleaning up after a
|
|
277
|
+
* caller - a deleted widget - that no longer has its own download state).
|
|
278
|
+
* returns false when `node` has no `download_cancel_by_blake3` or nothing
|
|
279
|
+
* was in flight for this hash.
|
|
280
|
+
*/
|
|
281
|
+
export async function pauseSnatchDownloadByBlake3(node, blake3Hash) {
|
|
282
|
+
if (!node.download_cancel_by_blake3)
|
|
283
|
+
return false;
|
|
284
|
+
return (await node.download_cancel_by_blake3(blake3Hash)) > 0;
|
|
285
|
+
}
|
|
274
286
|
/**
|
|
275
287
|
* discard a paused partial: releases the gc pin a paused download left
|
|
276
288
|
* behind so the transport can reclaim it. call when the user cancels for
|
|
@@ -3,7 +3,7 @@ vi.mock("../worker/index.js", () => ({
|
|
|
3
3
|
hashBlake3: vi.fn(async (bytes) => `hash-of-${bytes.length}-bytes`),
|
|
4
4
|
}));
|
|
5
5
|
import { hashBlake3 } from "../worker/index.js";
|
|
6
|
-
import { discardPausedDownload, pauseSnatchDownload, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
|
|
6
|
+
import { discardPausedDownload, pauseSnatchDownload, pauseSnatchDownloadByBlake3, snatchBlob, snatchBlobToDisk, } from "./snatch.js";
|
|
7
7
|
afterEach(() => {
|
|
8
8
|
vi.clearAllMocks();
|
|
9
9
|
});
|
|
@@ -320,6 +320,25 @@ describe("pauseSnatchDownload / discardPausedDownload", () => {
|
|
|
320
320
|
const node = { node_id: () => "me" };
|
|
321
321
|
await expect(pauseSnatchDownload(node, "dl-1")).resolves.toBe(false);
|
|
322
322
|
});
|
|
323
|
+
it("pauseSnatchDownloadByBlake3 pauses via download_cancel_by_blake3 and reports true when at least one was flagged", async () => {
|
|
324
|
+
const node = {
|
|
325
|
+
node_id: () => "me",
|
|
326
|
+
download_cancel_by_blake3: vi.fn(async () => 2),
|
|
327
|
+
};
|
|
328
|
+
await expect(pauseSnatchDownloadByBlake3(node, BLAKE3)).resolves.toBe(true);
|
|
329
|
+
expect(node.download_cancel_by_blake3).toHaveBeenCalledWith(BLAKE3);
|
|
330
|
+
});
|
|
331
|
+
it("pauseSnatchDownloadByBlake3 returns false when nothing was in flight", async () => {
|
|
332
|
+
const node = {
|
|
333
|
+
node_id: () => "me",
|
|
334
|
+
download_cancel_by_blake3: vi.fn(async () => 0),
|
|
335
|
+
};
|
|
336
|
+
await expect(pauseSnatchDownloadByBlake3(node, BLAKE3)).resolves.toBe(false);
|
|
337
|
+
});
|
|
338
|
+
it("pauseSnatchDownloadByBlake3 returns false when the node has no download_cancel_by_blake3", async () => {
|
|
339
|
+
const node = { node_id: () => "me" };
|
|
340
|
+
await expect(pauseSnatchDownloadByBlake3(node, BLAKE3)).resolves.toBe(false);
|
|
341
|
+
});
|
|
323
342
|
it("discards a paused download via unprotect_blob, swallowing errors", async () => {
|
|
324
343
|
const node = {
|
|
325
344
|
node_id: () => "me",
|
package/dist/transfer/types.d.ts
CHANGED
|
@@ -28,6 +28,11 @@ export interface BlobCapableNode {
|
|
|
28
28
|
/** pause/cancel an in-flight download by the id passed to it. returns
|
|
29
29
|
* false when the download already settled. */
|
|
30
30
|
download_cancel?(downloadId: string): Promise<boolean>;
|
|
31
|
+
/** pause/cancel every in-flight download for this blake3 hash, without
|
|
32
|
+
* needing the per-attempt downloadId (e.g. a caller that no longer has
|
|
33
|
+
* its own download state, such as a widget being torn down). returns
|
|
34
|
+
* how many were flagged - 0 means none were in flight. */
|
|
35
|
+
download_cancel_by_blake3?(blake3Hash: string): Promise<number>;
|
|
31
36
|
/** release a gc pin held by a paused/cancelled download, letting the
|
|
32
37
|
* partial be reclaimed. */
|
|
33
38
|
unprotect_blob?(blake3Hash: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/transfer/types.ts"],"names":[],"mappings":"AAeA,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,MAAM,CAAC;IAElB;;iEAE6D;IAC7D,sCAAsC,CAAC,CACrC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACtC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB;;;;;;;;;gBASY;IACZ,uCAAuC,CAAC,CACtC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,EACjE,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACtC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnB;;;;2EAIuE;IACvE,aAAa,CAAC,CACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,IAAI,GAClB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C;mDAC+C;IAC/C,eAAe,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD;gCAC4B;IAC5B,cAAc,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;iEAC6D;IAC7D,WAAW,CAAC,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhD;wCACoC;IACpC,YAAY,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACzD;AAED,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB;;oDAEgD;IAChD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;2EACuE;IACvE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;uEACmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;mDAE+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;6DACyD;IACzD,SAAS,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B;;;sCAGkC;IAClC,kBAAkB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/transfer/types.ts"],"names":[],"mappings":"AAeA,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,MAAM,CAAC;IAElB;;iEAE6D;IAC7D,sCAAsC,CAAC,CACrC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACtC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB;;;;;;;;;gBASY;IACZ,uCAAuC,CAAC,CACtC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,EACjE,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACtC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnB;;;;2EAIuE;IACvE,aAAa,CAAC,CACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,IAAI,GAClB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C;mDAC+C;IAC/C,eAAe,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD;;;+DAG2D;IAC3D,yBAAyB,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhE;gCAC4B;IAC5B,cAAc,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;iEAC6D;IAC7D,WAAW,CAAC,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhD;wCACoC;IACpC,YAAY,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACzD;AAED,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB;;oDAEgD;IAChD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;2EACuE;IACvE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;uEACmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;mDAE+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;6DACyD;IACzD,SAAS,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B;;;sCAGkC;IAClC,kBAAkB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
@@ -94,6 +94,11 @@ export interface ResizeImageOptions {
|
|
|
94
94
|
maxHeight?: number;
|
|
95
95
|
quality?: number;
|
|
96
96
|
cropSquare?: boolean;
|
|
97
|
+
/** if true, fit the whole image inside a maxWidth x maxHeight square
|
|
98
|
+
* without cropping, padding the shorter axis with transparent pixels -
|
|
99
|
+
* the right choice for document/page thumbnails, where cropping can cut
|
|
100
|
+
* off real content. takes precedence over `cropSquare` when both are set. */
|
|
101
|
+
fitSquare?: boolean;
|
|
97
102
|
mime?: string;
|
|
98
103
|
}
|
|
99
104
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blob-worker-client.d.ts","sourceRoot":"","sources":["../../src/worker/blob-worker-client.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAsBtD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,CAuDnF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC;AAiCD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAIlE;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAInE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAIvE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAeD;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC1E,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkC3C;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,SAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOhG;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAQnE"}
|
|
1
|
+
{"version":3,"file":"blob-worker-client.d.ts","sourceRoot":"","sources":["../../src/worker/blob-worker-client.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAsBtD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,CAuDnF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC;AAiCD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAIlE;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAInE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAIvE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAeD;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC1E,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkC3C;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;iFAG6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,SAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOhG;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAQnE"}
|
|
@@ -314,10 +314,27 @@ async function mainThreadResizeImage(blob, options) {
|
|
|
314
314
|
const maxHeight = options?.maxHeight ?? 200;
|
|
315
315
|
const quality = options?.quality ?? 0.8;
|
|
316
316
|
const cropSquare = options?.cropSquare ?? false;
|
|
317
|
+
const fitSquare = options?.fitSquare ?? false;
|
|
317
318
|
const mime = options?.mime ?? "image/webp";
|
|
318
319
|
let bitmap = null;
|
|
319
320
|
try {
|
|
320
321
|
bitmap = await createImageBitmap(blob);
|
|
322
|
+
if (fitSquare) {
|
|
323
|
+
const scale = Math.min(maxWidth / bitmap.width, maxHeight / bitmap.height);
|
|
324
|
+
const drawW = Math.max(1, Math.round(bitmap.width * scale));
|
|
325
|
+
const drawH = Math.max(1, Math.round(bitmap.height * scale));
|
|
326
|
+
const dx = Math.floor((maxWidth - drawW) / 2);
|
|
327
|
+
const dy = Math.floor((maxHeight - drawH) / 2);
|
|
328
|
+
const canvas = new OffscreenCanvas(maxWidth, maxHeight);
|
|
329
|
+
const ctx = canvas.getContext("2d");
|
|
330
|
+
if (!ctx)
|
|
331
|
+
return null;
|
|
332
|
+
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, dx, dy, drawW, drawH);
|
|
333
|
+
const out = await canvas.convertToBlob({ type: mime, quality });
|
|
334
|
+
const buf = await out.arrayBuffer();
|
|
335
|
+
const b64 = await fallbackBase64Encode(buf);
|
|
336
|
+
return `data:${mime};base64,${b64}`;
|
|
337
|
+
}
|
|
321
338
|
let sx = 0;
|
|
322
339
|
let sy = 0;
|
|
323
340
|
let sw = bitmap.width;
|
|
@@ -26,6 +26,7 @@ class FakeImageBitmap {
|
|
|
26
26
|
class FakeOffscreenCanvasContext {
|
|
27
27
|
drawImage = vi.fn();
|
|
28
28
|
}
|
|
29
|
+
let lastFakeCanvas;
|
|
29
30
|
class FakeOffscreenCanvas {
|
|
30
31
|
width;
|
|
31
32
|
height;
|
|
@@ -33,6 +34,7 @@ class FakeOffscreenCanvas {
|
|
|
33
34
|
constructor(width, height) {
|
|
34
35
|
this.width = width;
|
|
35
36
|
this.height = height;
|
|
37
|
+
lastFakeCanvas = this;
|
|
36
38
|
}
|
|
37
39
|
getContext() {
|
|
38
40
|
return this.context;
|
|
@@ -128,6 +130,20 @@ describe("resizeImageToWebpDataUrl", () => {
|
|
|
128
130
|
const result = await resizeImageToWebpDataUrl(new Blob(["x"], { type: "image/png" }));
|
|
129
131
|
expect(result).toBeNull();
|
|
130
132
|
});
|
|
133
|
+
it("centers a non-square bitmap without cropping when fitSquare is set", async () => {
|
|
134
|
+
installCanvasFakes({ width: 400, height: 100 });
|
|
135
|
+
const result = await resizeImageToWebpDataUrl(new Blob(["x"], { type: "image/png" }), {
|
|
136
|
+
maxWidth: 200,
|
|
137
|
+
maxHeight: 200,
|
|
138
|
+
fitSquare: true,
|
|
139
|
+
});
|
|
140
|
+
expect(result).toMatch(/^data:image\/webp;base64,/);
|
|
141
|
+
// 400x100 fit into 200x200 scales to 200x50, centered with 75px of
|
|
142
|
+
// padding above and below — the full source width is drawn (no crop).
|
|
143
|
+
expect(lastFakeCanvas?.width).toBe(200);
|
|
144
|
+
expect(lastFakeCanvas?.height).toBe(200);
|
|
145
|
+
expect(lastFakeCanvas?.context.drawImage).toHaveBeenCalledWith(expect.anything(), 0, 0, 400, 100, 0, 75, 200, 50);
|
|
146
|
+
});
|
|
131
147
|
});
|
|
132
148
|
describe("generateThumbnailDataUrl", () => {
|
|
133
149
|
it("skips non-image blobs", async () => {
|
|
@@ -67,6 +67,11 @@ export interface ResizeImageOptions {
|
|
|
67
67
|
quality?: number;
|
|
68
68
|
/** if true, center-crop to a square before resizing */
|
|
69
69
|
cropSquare?: boolean;
|
|
70
|
+
/** if true, fit the whole image inside a maxWidth x maxHeight square
|
|
71
|
+
* without cropping, padding the shorter axis with transparent pixels -
|
|
72
|
+
* the right choice for document/page thumbnails, where cropping can cut
|
|
73
|
+
* off real content. takes precedence over `cropSquare` when both are set. */
|
|
74
|
+
fitSquare?: boolean;
|
|
70
75
|
/** output mime type (default: "image/webp") */
|
|
71
76
|
mime?: string;
|
|
72
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blob-worker-logic.d.ts","sourceRoot":"","sources":["../../src/worker/blob-worker-logic.ts"],"names":[],"mappings":"AAqBA;oEACoE;AACpE,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D;;;qCAGqC;AACrC,eAAO,MAAM,QAAQ,oBAAoB,CAAC;AAE1C;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAYlE;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CASvE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAOnE;AAuBD;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCtF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAUlF;AAID,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,aAAa,CAAC,CAcxB;AAID,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"blob-worker-logic.d.ts","sourceRoot":"","sources":["../../src/worker/blob-worker-logic.ts"],"names":[],"mappings":"AAqBA;oEACoE;AACpE,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D;;;qCAGqC;AACrC,eAAO,MAAM,QAAQ,oBAAoB,CAAC;AAE1C;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAYlE;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CASvE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAOnE;AAuBD;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCtF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAUlF;AAID,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,aAAa,CAAC,CAcxB;AAID,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;iFAG6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6ExB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,SAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOhG;AAoBD;;;;;GAKG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAwBnD;AAED,+DAA+D;AAC/D,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAO/E;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA0CxF;AAED,uDAAuD;AACvD,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3D;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAOzD;AAED;;;;GAIG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,CAOpE"}
|
|
@@ -176,10 +176,30 @@ export async function resizeImageToWebpDataUrl(blob, options) {
|
|
|
176
176
|
const maxHeight = options?.maxHeight ?? 200;
|
|
177
177
|
const quality = options?.quality ?? 0.8;
|
|
178
178
|
const cropSquare = options?.cropSquare ?? false;
|
|
179
|
+
const fitSquare = options?.fitSquare ?? false;
|
|
179
180
|
const mime = options?.mime ?? "image/webp";
|
|
180
181
|
let bitmap = null;
|
|
181
182
|
try {
|
|
182
183
|
bitmap = await createImageBitmap(blob);
|
|
184
|
+
if (fitSquare) {
|
|
185
|
+
const scale = Math.min(maxWidth / bitmap.width, maxHeight / bitmap.height);
|
|
186
|
+
const drawW = Math.max(1, Math.round(bitmap.width * scale));
|
|
187
|
+
const drawH = Math.max(1, Math.round(bitmap.height * scale));
|
|
188
|
+
const dx = Math.floor((maxWidth - drawW) / 2);
|
|
189
|
+
const dy = Math.floor((maxHeight - drawH) / 2);
|
|
190
|
+
const canvas = new OffscreenCanvas(maxWidth, maxHeight);
|
|
191
|
+
const ctx = canvas.getContext("2d");
|
|
192
|
+
if (!ctx)
|
|
193
|
+
return null;
|
|
194
|
+
// canvas starts fully transparent — draw the scaled image centered,
|
|
195
|
+
// leaving transparent padding on the shorter axis rather than
|
|
196
|
+
// cropping content off the longer one.
|
|
197
|
+
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, dx, dy, drawW, drawH);
|
|
198
|
+
const out = await canvas.convertToBlob({ type: mime, quality });
|
|
199
|
+
const buf = await out.arrayBuffer();
|
|
200
|
+
const b64 = await base64Encode(buf);
|
|
201
|
+
return `data:${mime};base64,${b64}`;
|
|
202
|
+
}
|
|
183
203
|
let sx = 0;
|
|
184
204
|
let sy = 0;
|
|
185
205
|
let sw = bitmap.width;
|
|
@@ -63,6 +63,7 @@ class FakeImageBitmap {
|
|
|
63
63
|
class FakeOffscreenCanvasContext {
|
|
64
64
|
drawImage = vi.fn();
|
|
65
65
|
}
|
|
66
|
+
let lastFakeCanvas;
|
|
66
67
|
class FakeOffscreenCanvas {
|
|
67
68
|
width;
|
|
68
69
|
height;
|
|
@@ -70,6 +71,7 @@ class FakeOffscreenCanvas {
|
|
|
70
71
|
constructor(width, height) {
|
|
71
72
|
this.width = width;
|
|
72
73
|
this.height = height;
|
|
74
|
+
lastFakeCanvas = this;
|
|
73
75
|
}
|
|
74
76
|
getContext() {
|
|
75
77
|
return this.context;
|
|
@@ -176,6 +178,20 @@ describe("resizeImageToWebpDataUrl", () => {
|
|
|
176
178
|
const result = await resizeImageToWebpDataUrl(new Blob(["x"], { type: "image/png" }));
|
|
177
179
|
expect(result).toBeNull();
|
|
178
180
|
});
|
|
181
|
+
it("centers a non-square bitmap without cropping when fitSquare is set", async () => {
|
|
182
|
+
installCanvasFakes({ width: 400, height: 100 });
|
|
183
|
+
const result = await resizeImageToWebpDataUrl(new Blob(["x"], { type: "image/png" }), {
|
|
184
|
+
maxWidth: 200,
|
|
185
|
+
maxHeight: 200,
|
|
186
|
+
fitSquare: true,
|
|
187
|
+
});
|
|
188
|
+
expect(result).toMatch(/^data:image\/webp;base64,/);
|
|
189
|
+
// 400x100 fit into 200x200 scales to 200x50, centered with 75px of
|
|
190
|
+
// padding above and below — the full source width is drawn (no crop).
|
|
191
|
+
expect(lastFakeCanvas?.width).toBe(200);
|
|
192
|
+
expect(lastFakeCanvas?.height).toBe(200);
|
|
193
|
+
expect(lastFakeCanvas?.context.drawImage).toHaveBeenCalledWith(expect.anything(), 0, 0, 400, 100, 0, 75, 200, 50);
|
|
194
|
+
});
|
|
179
195
|
});
|
|
180
196
|
describe("generateThumbnailDataUrl", () => {
|
|
181
197
|
it("skips non-image blobs", async () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Comlink from "comlink";
|
|
2
|
-
import { type MiddenWorkerApi, type StreamInfo } from "./midden-worker-contract.js";
|
|
2
|
+
import { type MiddenWorkerApi, type StreamInfo, type WorkerActiveTransfer } from "./midden-worker-contract.js";
|
|
3
3
|
type Api = Comlink.Remote<MiddenWorkerApi>;
|
|
4
4
|
/** main-thread face of a worker-held BiStream. implements BiStreamLike. */
|
|
5
5
|
export declare class WorkerBiStream {
|
|
@@ -77,6 +77,9 @@ export declare class WorkerMiddenNode {
|
|
|
77
77
|
release_blob(blake3Hash: string): Promise<void>;
|
|
78
78
|
restrict_blob_to_peers(blake3Hash: string, peerNodeIds: string[]): Promise<void>;
|
|
79
79
|
clear_blob_restriction(blake3Hash: string): Promise<void>;
|
|
80
|
+
/** snapshot of this node's own outgoing blob transfers currently in
|
|
81
|
+
* flight - see `MiddenNode::get_active_transfers` in `midden/src/lib.rs`. */
|
|
82
|
+
get_active_transfers(): Promise<WorkerActiveTransfer[]>;
|
|
80
83
|
start_import(): WorkerImportSession;
|
|
81
84
|
ensure_blob(peerAddr: string, blake3Hash: string): Promise<boolean>;
|
|
82
85
|
download_verified_with_ensure(peerAddr: string, blake3Hash: string): Promise<Uint8Array>;
|
|
@@ -89,6 +92,9 @@ export declare class WorkerMiddenNode {
|
|
|
89
92
|
* stays in the persistent store, pinned against gc - a later download
|
|
90
93
|
* of the same hash resumes from the persisted bitfield. */
|
|
91
94
|
download_cancel(downloadId: string): Promise<boolean>;
|
|
95
|
+
/** pause/cancel every in-flight download for this blake3 hash, without
|
|
96
|
+
* needing the per-attempt downloadId. returns how many were flagged. */
|
|
97
|
+
download_cancel_by_blake3(blake3Hash: string): Promise<number>;
|
|
92
98
|
/** pin a hash against gc (keep a paused partial alive). */
|
|
93
99
|
protect_blob(blake3Hash: string): Promise<void>;
|
|
94
100
|
/** remove a gc pin added by protect_blob or a cancelled download. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midden-worker-client.d.ts","sourceRoot":"","sources":["../../src/worker/midden-worker-client.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"midden-worker-client.d.ts","sourceRoot":"","sources":["../../src/worker/midden-worker-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -199,6 +199,11 @@ export class WorkerMiddenNode {
|
|
|
199
199
|
clear_blob_restriction(blake3Hash) {
|
|
200
200
|
return this.api.clearBlobRestriction(blake3Hash);
|
|
201
201
|
}
|
|
202
|
+
/** snapshot of this node's own outgoing blob transfers currently in
|
|
203
|
+
* flight - see `MiddenNode::get_active_transfers` in `midden/src/lib.rs`. */
|
|
204
|
+
get_active_transfers() {
|
|
205
|
+
return this.api.getActiveTransfers();
|
|
206
|
+
}
|
|
202
207
|
start_import() {
|
|
203
208
|
return new WorkerImportSession(this.api);
|
|
204
209
|
}
|
|
@@ -228,6 +233,11 @@ export class WorkerMiddenNode {
|
|
|
228
233
|
async download_cancel(downloadId) {
|
|
229
234
|
return this.api.downloadCancel(downloadId);
|
|
230
235
|
}
|
|
236
|
+
/** pause/cancel every in-flight download for this blake3 hash, without
|
|
237
|
+
* needing the per-attempt downloadId. returns how many were flagged. */
|
|
238
|
+
async download_cancel_by_blake3(blake3Hash) {
|
|
239
|
+
return this.api.downloadCancelByBlake3(blake3Hash);
|
|
240
|
+
}
|
|
231
241
|
/** pin a hash against gc (keep a paused partial alive). */
|
|
232
242
|
async protect_blob(blake3Hash) {
|
|
233
243
|
return this.api.protectBlob(blake3Hash);
|
|
@@ -30,6 +30,7 @@ function makeFakeApi(overrides = {}) {
|
|
|
30
30
|
releaseBlob: vi.fn(),
|
|
31
31
|
restrictBlobToPeers: vi.fn(),
|
|
32
32
|
clearBlobRestriction: vi.fn(),
|
|
33
|
+
getActiveTransfers: vi.fn(async () => []),
|
|
33
34
|
startImport: vi.fn(async () => 1),
|
|
34
35
|
importPush: vi.fn(async () => undefined),
|
|
35
36
|
importFinish: vi.fn(async () => "blake3-hex"),
|
|
@@ -41,6 +42,7 @@ function makeFakeApi(overrides = {}) {
|
|
|
41
42
|
downloadVerifiedByIdProgress: vi.fn(),
|
|
42
43
|
downloadVerifiedStreamingWithEnsure: vi.fn(),
|
|
43
44
|
downloadCancel: vi.fn(),
|
|
45
|
+
downloadCancelByBlake3: vi.fn(),
|
|
44
46
|
protectBlob: vi.fn(),
|
|
45
47
|
unprotectBlob: vi.fn(),
|
|
46
48
|
computeBlake3: vi.fn(),
|
|
@@ -11,6 +11,15 @@ export interface MiddenWorkerIdentity {
|
|
|
11
11
|
nodeId: string;
|
|
12
12
|
secretKey: Uint8Array;
|
|
13
13
|
}
|
|
14
|
+
/** one outgoing blob transfer in flight on the worker-hosted node - same
|
|
15
|
+
* shape as a raw wasm MiddenNode's `get_active_transfers()`
|
|
16
|
+
* (`{peerId, blake3, bytesSent, totalSize}`, see midden/src/transfers.rs). */
|
|
17
|
+
export interface WorkerActiveTransfer {
|
|
18
|
+
peerId: string;
|
|
19
|
+
blake3: string;
|
|
20
|
+
bytesSent: number;
|
|
21
|
+
totalSize: number;
|
|
22
|
+
}
|
|
14
23
|
/**
|
|
15
24
|
* the entry's comlink-exposed surface. every method here runs on the
|
|
16
25
|
* worker thread; callback arguments (`onProgress`, `onChunk`) arrive as
|
|
@@ -49,6 +58,9 @@ export interface MiddenWorkerApi {
|
|
|
49
58
|
releaseBlob(blake3Hash: string): Promise<void>;
|
|
50
59
|
restrictBlobToPeers(blake3Hash: string, peerNodeIds: string[]): Promise<void>;
|
|
51
60
|
clearBlobRestriction(blake3Hash: string): Promise<void>;
|
|
61
|
+
/** snapshot of this node's own outgoing blob transfers currently in
|
|
62
|
+
* flight - forwards to the wasm node's `get_active_transfers()`. */
|
|
63
|
+
getActiveTransfers(): Promise<WorkerActiveTransfer[]>;
|
|
52
64
|
startImport(): Promise<number>;
|
|
53
65
|
importPush(sessionId: number, chunk: Uint8Array): Promise<void>;
|
|
54
66
|
importFinish(sessionId: number): Promise<string>;
|
|
@@ -64,6 +76,11 @@ export interface MiddenWorkerApi {
|
|
|
64
76
|
* stays in the store, pinned against gc - a later download of the same
|
|
65
77
|
* hash resumes from the persisted bitfield. */
|
|
66
78
|
downloadCancel(downloadId: string): Promise<boolean>;
|
|
79
|
+
/** pause/cancel every in-flight download for this blake3 hash, without
|
|
80
|
+
* needing the per-attempt downloadId (e.g. cleaning up after a caller
|
|
81
|
+
* that no longer has its own download state). returns how many were
|
|
82
|
+
* flagged - 0 means none were in flight. */
|
|
83
|
+
downloadCancelByBlake3(blake3Hash: string): Promise<number>;
|
|
67
84
|
/** pin a hash against gc (keep a paused partial download alive). */
|
|
68
85
|
protectBlob(blake3Hash: string): Promise<void>;
|
|
69
86
|
/** remove a gc pin added by protectBlob or a cancelled download. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midden-worker-contract.d.ts","sourceRoot":"","sources":["../../src/worker/midden-worker-contract.ts"],"names":[],"mappings":"AAqBA;mEACmE;AACnE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;uEACuE;AACvE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,UAAU,CAAC;CACvB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAGlE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D;2BACuB;IACvB,MAAM,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACrC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAChE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACxE,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,wEAAwE;IACxE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG7C,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IACrF,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD;;8BAE0B;IAC1B,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"midden-worker-contract.d.ts","sourceRoot":"","sources":["../../src/worker/midden-worker-contract.ts"],"names":[],"mappings":"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"}
|