@hozon/blob 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fresh.d.ts +2 -0
- package/lib/fresh.js +6 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/manage.js +3 -2
- package/lib/transfer.js +5 -4
- package/lib/write.js +4 -3
- package/package.json +9 -8
package/lib/fresh.d.ts
ADDED
package/lib/fresh.js
ADDED
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { BlobLockTimeoutError } from '@hozon/blob-backend';
|
|
2
2
|
export { InvalidBlobIDError } from '@hozon/blob-id';
|
|
3
|
+
export { InvalidCursorError } from '@hozon/store-blob';
|
|
3
4
|
export { BlobIDMismatchError, type BlobIDMismatchErrorParams, BlobNotFoundError, BlobTooLargeError, BlobWriteAbortedError, ChunkDigestMismatchError, type ChunkDigestMismatchErrorParams, ChunkLengthError, type ChunkLengthErrorParams, ContentTypeMismatchError, type ContentTypeMismatchErrorParams, InvalidManifestError, InvalidRangeError, TransferIncompleteError, type TransferIncompleteErrorParams, } from './errors.js';
|
|
4
5
|
export { type BlobLimits, DEFAULT_CHUNK_SIZE, DEFAULT_LIMITS } from './limits.js';
|
|
5
6
|
export { type BlobService, type BlobServiceParams, createBlobService } from './service.js';
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { BlobLockTimeoutError } from '@hozon/blob-backend';
|
|
2
2
|
export { InvalidBlobIDError } from '@hozon/blob-id';
|
|
3
|
+
export { InvalidCursorError } from '@hozon/store-blob';
|
|
3
4
|
export { BlobIDMismatchError, BlobNotFoundError, BlobTooLargeError, BlobWriteAbortedError, ChunkDigestMismatchError, ChunkLengthError, ContentTypeMismatchError, InvalidManifestError, InvalidRangeError, TransferIncompleteError } from './errors.js';
|
|
4
5
|
export { DEFAULT_CHUNK_SIZE, DEFAULT_LIMITS } from './limits.js';
|
|
5
6
|
export { createBlobService } from './service.js';
|
package/lib/manage.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getBlobStore } from '@hozon/store-blob';
|
|
2
2
|
import { BlobNotFoundError } from './errors.js';
|
|
3
|
+
import { getFreshEntry } from './fresh.js';
|
|
3
4
|
export async function setBlobPinned(ctx, id, pinned) {
|
|
4
5
|
const blobID = ctx.codec.canonicalize(id);
|
|
5
6
|
await ctx.lock.withLock(blobID, async ()=>{
|
|
6
7
|
const store = await getBlobStore(ctx.db);
|
|
7
|
-
if (await store
|
|
8
|
+
if (await getFreshEntry(store, blobID) === null) throw new BlobNotFoundError(blobID);
|
|
8
9
|
await store.setPinned(blobID, pinned);
|
|
9
10
|
});
|
|
10
11
|
}
|
|
@@ -12,7 +13,7 @@ export async function deleteBlob(ctx, id) {
|
|
|
12
13
|
const blobID = ctx.codec.canonicalize(id);
|
|
13
14
|
return await ctx.lock.withLock(blobID, async ()=>{
|
|
14
15
|
const store = await getBlobStore(ctx.db);
|
|
15
|
-
const entry = await store
|
|
16
|
+
const entry = await getFreshEntry(store, blobID);
|
|
16
17
|
if (entry === null) return false;
|
|
17
18
|
const transfer = entry.state === 'partial' ? await store.getTransfer(blobID) : null;
|
|
18
19
|
// The row goes first: a crash then leaves orphaned bytes, never a row without bytes.
|
package/lib/transfer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getBlobStore } from '@hozon/store-blob';
|
|
2
2
|
import { BlobIDMismatchError, BlobNotFoundError, ChunkDigestMismatchError, ChunkLengthError, InvalidManifestError, TransferIncompleteError } from './errors.js';
|
|
3
|
+
import { getFreshEntry } from './fresh.js';
|
|
3
4
|
import { createStagingID } from './write.js';
|
|
4
5
|
function bytesEqual(a, b) {
|
|
5
6
|
if (a.length !== b.length) return false;
|
|
@@ -98,7 +99,7 @@ async function stagingExists(backend, transfer, chunkSize) {
|
|
|
98
99
|
// Lock held: the transfer of a `partial` entry, or BlobNotFoundError.
|
|
99
100
|
async function requireTransfer(ctx, blobID) {
|
|
100
101
|
const store = await getBlobStore(ctx.db);
|
|
101
|
-
const entry = await store
|
|
102
|
+
const entry = await getFreshEntry(store, blobID);
|
|
102
103
|
const transfer = entry?.state === 'partial' ? await store.getTransfer(blobID) : null;
|
|
103
104
|
if (entry === null || transfer === null) {
|
|
104
105
|
throw new BlobNotFoundError(blobID, {
|
|
@@ -119,7 +120,7 @@ export async function beginFetch(ctx, id, manifest) {
|
|
|
119
120
|
const chunkSize = manifest.contentLength === 0 ? ctx.chunkSize : manifest.chunkSize;
|
|
120
121
|
await ctx.lock.withLock(blobID, async ()=>{
|
|
121
122
|
const store = await getBlobStore(ctx.db);
|
|
122
|
-
const entry = await store
|
|
123
|
+
const entry = await getFreshEntry(store, blobID);
|
|
123
124
|
if (entry?.state === 'local') return;
|
|
124
125
|
// A stub with another length could never complete against this manifest.
|
|
125
126
|
if (entry !== null && entry.contentLength !== manifest.contentLength) {
|
|
@@ -166,7 +167,7 @@ export async function getPresentChunks(ctx, id) {
|
|
|
166
167
|
const blobID = ctx.codec.canonicalize(id);
|
|
167
168
|
return await ctx.lock.withLock(blobID, async ()=>{
|
|
168
169
|
const store = await getBlobStore(ctx.db);
|
|
169
|
-
const entry = await store
|
|
170
|
+
const entry = await getFreshEntry(store, blobID);
|
|
170
171
|
if (entry?.state !== 'partial') return [];
|
|
171
172
|
const transfer = await store.getTransfer(blobID);
|
|
172
173
|
if (transfer === null) return [];
|
|
@@ -289,7 +290,7 @@ export async function completeFetch(ctx, id) {
|
|
|
289
290
|
index,
|
|
290
291
|
digest
|
|
291
292
|
})));
|
|
292
|
-
const promoted = await store
|
|
293
|
+
const promoted = await getFreshEntry(store, blobID);
|
|
293
294
|
if (promoted === null) throw new Error(`Blob entry ${blobID} missing after promotion`);
|
|
294
295
|
return promoted;
|
|
295
296
|
});
|
package/lib/write.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { hashStream } from '@hozon/blob-id';
|
|
2
2
|
import { getBlobStore } from '@hozon/store-blob';
|
|
3
3
|
import { BlobIDMismatchError, BlobTooLargeError, BlobWriteAbortedError, ContentTypeMismatchError } from './errors.js';
|
|
4
|
+
import { getFreshEntry } from './fresh.js';
|
|
4
5
|
// Random staging ID: `prefix` (`w` for writes, `t` for transfers), a dash, 32 hex chars.
|
|
5
6
|
export function createStagingID(runtime, prefix) {
|
|
6
7
|
const bytes = runtime.getRandomValues(new Uint8Array(16));
|
|
@@ -132,14 +133,14 @@ export async function writeBlob(ctx, params) {
|
|
|
132
133
|
return await ctx.lock.withLock(blobID, async ()=>{
|
|
133
134
|
throwIfAborted(signal);
|
|
134
135
|
const store = await getBlobStore(ctx.db);
|
|
135
|
-
const existing = await store
|
|
136
|
+
const existing = await getFreshEntry(store, blobID);
|
|
136
137
|
if (existing?.state === 'local') {
|
|
137
138
|
await discardStaging();
|
|
138
139
|
// Existing metadata wins; only a null content type is filled in.
|
|
139
140
|
const fill = async (target)=>{
|
|
140
141
|
if (existing.contentType !== null || contentType === undefined) return existing;
|
|
141
142
|
await target.fillContentType(blobID, contentType);
|
|
142
|
-
return await target
|
|
143
|
+
return await getFreshEntry(target, blobID) ?? existing;
|
|
143
144
|
};
|
|
144
145
|
const { entry, result } = await record(ctx, fn, fill);
|
|
145
146
|
return {
|
|
@@ -168,7 +169,7 @@ export async function writeBlob(ctx, params) {
|
|
|
168
169
|
}));
|
|
169
170
|
const promote = async (target)=>{
|
|
170
171
|
await target.promoteEntry(input, chunks);
|
|
171
|
-
const entry = await target
|
|
172
|
+
const entry = await getFreshEntry(target, blobID);
|
|
172
173
|
if (entry === null) throw new Error(`Blob entry ${blobID} missing after promotion`);
|
|
173
174
|
return entry;
|
|
174
175
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hozon/blob",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Content-addressed blob service with verified writes for Hozon",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,17 +18,18 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@hozon/blob-backend": "^0.2.0",
|
|
21
|
-
"@hozon/blob-id": "^0.1.
|
|
22
|
-
"@hozon/db": "^0.
|
|
23
|
-
"@hozon/store-blob": "^0.
|
|
21
|
+
"@hozon/blob-id": "^0.1.1",
|
|
22
|
+
"@hozon/db": "^0.4.0",
|
|
23
|
+
"@hozon/store-blob": "^0.3.0",
|
|
24
24
|
"@sozai/runtime": "^0.1.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@hozon/blob-node-fs": "^0.2.0",
|
|
28
|
-
"@hozon/node-sqlite": "^0.
|
|
29
|
-
"@
|
|
30
|
-
"
|
|
31
|
-
"
|
|
28
|
+
"@hozon/node-sqlite": "^0.2.0",
|
|
29
|
+
"@sozai/codec": "^0.4.3",
|
|
30
|
+
"@types/node": "^26.6.5",
|
|
31
|
+
"del-cli": "^8.0.1",
|
|
32
|
+
"vitest": "5.0.5"
|
|
32
33
|
},
|
|
33
34
|
"publishConfig": {
|
|
34
35
|
"access": "public"
|