@hozon/store-blob 0.2.0 → 0.2.2

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/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Adapter } from '@hozon/adapter';
2
- import type { Kysely } from '@hozon/db';
2
+ import type { Kysely, StoreContext } from '@hozon/db';
3
3
  import type { BlobTables } from './tables.js';
4
4
  import type { BlobStoreAPI } from './types.js';
5
- export declare function createBlobStoreAPI(db: Kysely<BlobTables>, adapter: Adapter): BlobStoreAPI;
5
+ export declare function createBlobStoreAPI(db: Kysely<BlobTables>, adapter: Adapter, context: StoreContext): BlobStoreAPI;
package/lib/api.js CHANGED
@@ -1,5 +1,6 @@
1
- import { chunk, withStoreTransaction } from '@hozon/db';
1
+ import { assertBatchSize, chunk, withStoreTransaction } from '@hozon/db';
2
2
  import { b64uFromUTF, b64uToUTF } from '@sozai/codec';
3
+ import { InvalidCursorError } from './errors.js';
3
4
  function encodeCursor(createdAt, blobID) {
4
5
  return b64uFromUTF(JSON.stringify([
5
6
  createdAt,
@@ -18,9 +19,49 @@ function decodeCursor(cursor) {
18
19
  } catch {
19
20
  // fall through
20
21
  }
21
- throw new Error('Invalid cursor');
22
+ throw new InvalidCursorError();
22
23
  }
23
- export function createBlobStoreAPI(db, adapter) {
24
+ // The entry query binds only the IDs.
25
+ const ENTRY_BATCH_MAX = 500;
26
+ function toEntry(row) {
27
+ return {
28
+ blobID: row.blob_id,
29
+ contentLength: row.content_length,
30
+ encrypted: row.encrypted === 1,
31
+ keyID: row.key_id,
32
+ chunkSize: row.chunk_size,
33
+ state: row.state,
34
+ pinned: row.pinned === 1,
35
+ createdAt: row.created_at,
36
+ contentType: row.content_type
37
+ };
38
+ }
39
+ export function createBlobStoreAPI(db, adapter, context) {
40
+ async function getEntries(blobIDs, options) {
41
+ const unique = [
42
+ ...new Set(blobIDs)
43
+ ];
44
+ assertBatchSize(unique.length, ENTRY_BATCH_MAX, options?.batchLimit);
45
+ const result = new Map();
46
+ if (unique.length === 0) return result;
47
+ const rows = await db.selectFrom('blob_entries').selectAll().where('blob_id', 'in', unique).execute();
48
+ for (const id of unique)result.set(id, null);
49
+ for (const row of rows)result.set(row.blob_id, toEntry(row));
50
+ return result;
51
+ }
52
+ const entries = context.createLoader({
53
+ load: (ids)=>getEntries(ids),
54
+ maxBatchSize: ENTRY_BATCH_MAX
55
+ });
56
+ // Flushes pending reads before the write and drops the remembered entry afterwards.
57
+ async function mutate(blobID, run) {
58
+ entries.dispatch();
59
+ try {
60
+ return await run();
61
+ } finally{
62
+ entries.clear(blobID);
63
+ }
64
+ }
24
65
  async function insertManifest(trx, blobID, chunks) {
25
66
  const rows = chunks.map((item)=>({
26
67
  blob_id: blobID,
@@ -37,104 +78,102 @@ export function createBlobStoreAPI(db, adapter) {
37
78
  }
38
79
  return {
39
80
  async insertEntry (entry, chunks) {
40
- await withStoreTransaction(db, async (trx)=>{
41
- await trx.insertInto('blob_entries').values({
42
- blob_id: entry.blobID,
43
- content_length: entry.contentLength,
44
- encrypted: entry.encrypted ? 1 : 0,
45
- key_id: entry.keyID ?? null,
46
- chunk_size: entry.chunkSize,
47
- state: entry.state,
48
- pinned: entry.pinned ? 1 : 0,
49
- created_at: entry.createdAt,
50
- content_type: entry.contentType ?? null
51
- }).onConflict((oc)=>oc.column('blob_id').doNothing()).execute();
52
- await insertManifest(trx, entry.blobID, chunks);
81
+ return mutate(entry.blobID, async ()=>{
82
+ await withStoreTransaction(db, async (trx)=>{
83
+ await trx.insertInto('blob_entries').values({
84
+ blob_id: entry.blobID,
85
+ content_length: entry.contentLength,
86
+ encrypted: entry.encrypted ? 1 : 0,
87
+ key_id: entry.keyID ?? null,
88
+ chunk_size: entry.chunkSize,
89
+ state: entry.state,
90
+ pinned: entry.pinned ? 1 : 0,
91
+ created_at: entry.createdAt,
92
+ content_type: entry.contentType ?? null
93
+ }).onConflict((oc)=>oc.column('blob_id').doNothing()).execute();
94
+ await insertManifest(trx, entry.blobID, chunks);
95
+ });
53
96
  });
54
97
  },
55
- async getEntry (blobID) {
56
- const row = await db.selectFrom('blob_entries').selectAll().where('blob_id', '=', blobID).executeTakeFirst();
57
- return row === undefined ? null : {
58
- blobID: row.blob_id,
59
- contentLength: row.content_length,
60
- encrypted: row.encrypted === 1,
61
- keyID: row.key_id,
62
- chunkSize: row.chunk_size,
63
- state: row.state,
64
- pinned: row.pinned === 1,
65
- createdAt: row.created_at,
66
- contentType: row.content_type
67
- };
68
- },
98
+ getEntry: (blobID)=>entries.load(blobID),
99
+ getEntries,
69
100
  async getChunkDigests (blobID) {
70
101
  const rows = await db.selectFrom('blob_chunks').select('digest').where('blob_id', '=', blobID).orderBy('index', 'asc').execute();
71
102
  return rows.map((row)=>row.digest);
72
103
  },
73
104
  async setPinned (blobID, pinned) {
74
- await db.updateTable('blob_entries').set({
75
- pinned: pinned ? 1 : 0
76
- }).where('blob_id', '=', blobID).execute();
105
+ return mutate(blobID, async ()=>{
106
+ await db.updateTable('blob_entries').set({
107
+ pinned: pinned ? 1 : 0
108
+ }).where('blob_id', '=', blobID).execute();
109
+ });
77
110
  },
78
111
  async deleteEntry (blobID) {
79
- await withStoreTransaction(db, async (trx)=>{
80
- await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', blobID).execute();
81
- await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
82
- await trx.deleteFrom('blob_chunks').where('blob_id', '=', blobID).execute();
83
- await trx.deleteFrom('blob_entries').where('blob_id', '=', blobID).execute();
112
+ return mutate(blobID, async ()=>{
113
+ await withStoreTransaction(db, async (trx)=>{
114
+ await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', blobID).execute();
115
+ await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
116
+ await trx.deleteFrom('blob_chunks').where('blob_id', '=', blobID).execute();
117
+ await trx.deleteFrom('blob_entries').where('blob_id', '=', blobID).execute();
118
+ });
84
119
  });
85
120
  },
86
121
  async beginTransfer ({ blobID, chunkSize, chunks, stagingID }) {
87
- await withStoreTransaction(db, async (trx)=>{
88
- let query = trx.selectFrom('blob_entries').select([
89
- 'blob_id',
90
- 'chunk_size'
91
- ]).where('blob_id', '=', blobID);
92
- if (adapter.kind === 'postgres') query = query.forUpdate();
93
- const entry = await query.executeTakeFirst();
94
- if (entry === undefined) throw new Error(`Blob entry ${blobID} not found`);
95
- if (entry.chunk_size !== chunkSize) {
96
- const manifest = await trx.selectFrom('blob_chunks').select('index').where('blob_id', '=', blobID).executeTakeFirst();
97
- if (manifest !== undefined) {
98
- throw new Error(`Cannot change chunk size for blob ${blobID}: manifest already exists`);
122
+ return mutate(blobID, async ()=>{
123
+ await withStoreTransaction(db, async (trx)=>{
124
+ let query = trx.selectFrom('blob_entries').select([
125
+ 'blob_id',
126
+ 'chunk_size'
127
+ ]).where('blob_id', '=', blobID);
128
+ if (adapter.kind === 'postgres') query = query.forUpdate();
129
+ const entry = await query.executeTakeFirst();
130
+ if (entry === undefined) throw new Error(`Blob entry ${blobID} not found`);
131
+ if (entry.chunk_size !== chunkSize) {
132
+ const manifest = await trx.selectFrom('blob_chunks').select('index').where('blob_id', '=', blobID).executeTakeFirst();
133
+ if (manifest !== undefined) {
134
+ throw new Error(`Cannot change chunk size for blob ${blobID}: manifest already exists`);
135
+ }
99
136
  }
100
- }
101
- await trx.updateTable('blob_entries').set({
102
- chunk_size: chunkSize,
103
- state: 'partial'
104
- }).where('blob_id', '=', blobID).execute();
105
- await insertManifest(trx, blobID, chunks);
106
- const existing = await trx.selectFrom('blob_transfer_sessions').select('staging_id').where('blob_id', '=', blobID).executeTakeFirst();
107
- // A new staging area holds none of the previous session's chunks.
108
- if (existing !== undefined && existing.staging_id !== stagingID) {
109
- await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
110
- }
111
- const updatedAt = Date.now();
112
- await trx.insertInto('blob_transfer_sessions').values({
113
- blob_id: blobID,
114
- staging_id: stagingID,
115
- updated_at: updatedAt
116
- }).onConflict((oc)=>oc.column('blob_id').doUpdateSet({
137
+ await trx.updateTable('blob_entries').set({
138
+ chunk_size: chunkSize,
139
+ state: 'partial'
140
+ }).where('blob_id', '=', blobID).execute();
141
+ await insertManifest(trx, blobID, chunks);
142
+ const existing = await trx.selectFrom('blob_transfer_sessions').select('staging_id').where('blob_id', '=', blobID).executeTakeFirst();
143
+ // A new staging area holds none of the previous session's chunks.
144
+ if (existing !== undefined && existing.staging_id !== stagingID) {
145
+ await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
146
+ }
147
+ const updatedAt = Date.now();
148
+ await trx.insertInto('blob_transfer_sessions').values({
149
+ blob_id: blobID,
117
150
  staging_id: stagingID,
118
151
  updated_at: updatedAt
119
- })).execute();
152
+ }).onConflict((oc)=>oc.column('blob_id').doUpdateSet({
153
+ staging_id: stagingID,
154
+ updated_at: updatedAt
155
+ })).execute();
156
+ });
120
157
  });
121
158
  },
122
159
  async recordTransferChunk (blobID, index) {
123
- await withStoreTransaction(db, async (trx)=>{
124
- const entry = await trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', blobID).executeTakeFirst();
125
- if (entry === undefined) throw new Error(`Blob entry ${blobID} not found`);
126
- const chunk = await trx.selectFrom('blob_chunks').select('index').where('blob_id', '=', blobID).where('index', '=', index).executeTakeFirst();
127
- if (chunk === undefined) throw new Error(`Blob chunk ${blobID} at index ${index} not found`);
128
- await trx.insertInto('blob_transfers').values({
129
- blob_id: blobID,
130
- index
131
- }).onConflict((oc)=>oc.columns([
132
- 'blob_id',
133
- 'index'
134
- ]).doNothing()).execute();
135
- await trx.updateTable('blob_transfer_sessions').set({
136
- updated_at: Date.now()
137
- }).where('blob_id', '=', blobID).execute();
160
+ return mutate(blobID, async ()=>{
161
+ await withStoreTransaction(db, async (trx)=>{
162
+ const entry = await trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', blobID).executeTakeFirst();
163
+ if (entry === undefined) throw new Error(`Blob entry ${blobID} not found`);
164
+ const chunk = await trx.selectFrom('blob_chunks').select('index').where('blob_id', '=', blobID).where('index', '=', index).executeTakeFirst();
165
+ if (chunk === undefined) throw new Error(`Blob chunk ${blobID} at index ${index} not found`);
166
+ await trx.insertInto('blob_transfers').values({
167
+ blob_id: blobID,
168
+ index
169
+ }).onConflict((oc)=>oc.columns([
170
+ 'blob_id',
171
+ 'index'
172
+ ]).doNothing()).execute();
173
+ await trx.updateTable('blob_transfer_sessions').set({
174
+ updated_at: Date.now()
175
+ }).where('blob_id', '=', blobID).execute();
176
+ });
138
177
  });
139
178
  },
140
179
  async getPresentChunkIndexes (blobID) {
@@ -142,21 +181,23 @@ export function createBlobStoreAPI(db, adapter) {
142
181
  return rows.map((row)=>row.index);
143
182
  },
144
183
  async finalizeTransfer (blobID) {
145
- await withStoreTransaction(db, async (trx)=>{
146
- const entry = await trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', blobID).executeTakeFirst();
147
- if (entry === undefined) throw new Error(`Blob entry ${blobID} not found`);
148
- const manifest = await trx.selectFrom('blob_chunks').select('index').where('blob_id', '=', blobID).execute();
149
- const present = await trx.selectFrom('blob_transfers').select('index').where('blob_id', '=', blobID).execute();
150
- const presentSet = new Set(present.map((row)=>row.index));
151
- const missing = manifest.filter((row)=>!presentSet.has(row.index));
152
- if (missing.length > 0) {
153
- throw new Error(`Cannot finalize transfer ${blobID}: ${missing.length} chunk(s) missing`);
154
- }
155
- await trx.updateTable('blob_entries').set({
156
- state: 'local'
157
- }).where('blob_id', '=', blobID).execute();
158
- await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
159
- await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', blobID).execute();
184
+ return mutate(blobID, async ()=>{
185
+ await withStoreTransaction(db, async (trx)=>{
186
+ const entry = await trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', blobID).executeTakeFirst();
187
+ if (entry === undefined) throw new Error(`Blob entry ${blobID} not found`);
188
+ const manifest = await trx.selectFrom('blob_chunks').select('index').where('blob_id', '=', blobID).execute();
189
+ const present = await trx.selectFrom('blob_transfers').select('index').where('blob_id', '=', blobID).execute();
190
+ const presentSet = new Set(present.map((row)=>row.index));
191
+ const missing = manifest.filter((row)=>!presentSet.has(row.index));
192
+ if (missing.length > 0) {
193
+ throw new Error(`Cannot finalize transfer ${blobID}: ${missing.length} chunk(s) missing`);
194
+ }
195
+ await trx.updateTable('blob_entries').set({
196
+ state: 'local'
197
+ }).where('blob_id', '=', blobID).execute();
198
+ await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
199
+ await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', blobID).execute();
200
+ });
160
201
  });
161
202
  },
162
203
  async getTransfer (blobID) {
@@ -183,9 +224,11 @@ export function createBlobStoreAPI(db, adapter) {
183
224
  };
184
225
  },
185
226
  async touchTransfer (blobID) {
186
- await db.updateTable('blob_transfer_sessions').set({
187
- updated_at: Date.now()
188
- }).where('blob_id', '=', blobID).execute();
227
+ return mutate(blobID, async ()=>{
228
+ await db.updateTable('blob_transfer_sessions').set({
229
+ updated_at: Date.now()
230
+ }).where('blob_id', '=', blobID).execute();
231
+ });
189
232
  },
190
233
  async listEntries (params) {
191
234
  const { limit, cursor } = params;
@@ -205,17 +248,7 @@ export function createBlobStoreAPI(db, adapter) {
205
248
  }
206
249
  const rows = await query.orderBy('created_at', 'asc').orderBy('blob_id', 'asc').limit(limit + 1).execute();
207
250
  const page = rows.slice(0, limit);
208
- const entries = page.map((row)=>({
209
- blobID: row.blob_id,
210
- contentLength: row.content_length,
211
- encrypted: row.encrypted === 1,
212
- keyID: row.key_id,
213
- chunkSize: row.chunk_size,
214
- state: row.state,
215
- pinned: row.pinned === 1,
216
- createdAt: row.created_at,
217
- contentType: row.content_type
218
- }));
251
+ const entries = page.map(toEntry);
219
252
  const last = page[page.length - 1];
220
253
  const nextCursor = rows.length > limit && last !== undefined ? encodeCursor(last.created_at, last.blob_id) : null;
221
254
  return {
@@ -224,48 +257,54 @@ export function createBlobStoreAPI(db, adapter) {
224
257
  };
225
258
  },
226
259
  async promoteEntry (entry, chunks) {
227
- await withStoreTransaction(db, async (trx)=>{
228
- let lock = trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', entry.blobID);
229
- if (adapter.kind === 'postgres') lock = lock.forUpdate();
230
- await lock.executeTakeFirst();
231
- const values = {
232
- content_length: entry.contentLength,
233
- encrypted: entry.encrypted ? 1 : 0,
234
- key_id: entry.keyID ?? null,
235
- chunk_size: entry.chunkSize,
236
- state: 'local',
237
- content_type: entry.contentType ?? null
238
- };
239
- await trx.insertInto('blob_entries').values({
240
- blob_id: entry.blobID,
241
- pinned: entry.pinned ? 1 : 0,
242
- created_at: entry.createdAt,
243
- ...values
244
- }).onConflict((oc)=>oc.column('blob_id').doUpdateSet(values)).execute();
245
- await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', entry.blobID).execute();
246
- await trx.deleteFrom('blob_transfers').where('blob_id', '=', entry.blobID).execute();
247
- await trx.deleteFrom('blob_chunks').where('blob_id', '=', entry.blobID).execute();
248
- await insertManifest(trx, entry.blobID, chunks);
260
+ return mutate(entry.blobID, async ()=>{
261
+ await withStoreTransaction(db, async (trx)=>{
262
+ let lock = trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', entry.blobID);
263
+ if (adapter.kind === 'postgres') lock = lock.forUpdate();
264
+ await lock.executeTakeFirst();
265
+ const values = {
266
+ content_length: entry.contentLength,
267
+ encrypted: entry.encrypted ? 1 : 0,
268
+ key_id: entry.keyID ?? null,
269
+ chunk_size: entry.chunkSize,
270
+ state: 'local',
271
+ content_type: entry.contentType ?? null
272
+ };
273
+ await trx.insertInto('blob_entries').values({
274
+ blob_id: entry.blobID,
275
+ pinned: entry.pinned ? 1 : 0,
276
+ created_at: entry.createdAt,
277
+ ...values
278
+ }).onConflict((oc)=>oc.column('blob_id').doUpdateSet(values)).execute();
279
+ await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', entry.blobID).execute();
280
+ await trx.deleteFrom('blob_transfers').where('blob_id', '=', entry.blobID).execute();
281
+ await trx.deleteFrom('blob_chunks').where('blob_id', '=', entry.blobID).execute();
282
+ await insertManifest(trx, entry.blobID, chunks);
283
+ });
249
284
  });
250
285
  },
251
286
  async resetTransfer (blobID) {
252
- await withStoreTransaction(db, async (trx)=>{
253
- let lock = trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', blobID);
254
- if (adapter.kind === 'postgres') lock = lock.forUpdate();
255
- const entry = await lock.executeTakeFirst();
256
- if (entry === undefined) return;
257
- await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', blobID).execute();
258
- await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
259
- await trx.deleteFrom('blob_chunks').where('blob_id', '=', blobID).execute();
260
- await trx.updateTable('blob_entries').set({
261
- state: 'remote-only'
262
- }).where('blob_id', '=', blobID).execute();
287
+ return mutate(blobID, async ()=>{
288
+ await withStoreTransaction(db, async (trx)=>{
289
+ let lock = trx.selectFrom('blob_entries').select('blob_id').where('blob_id', '=', blobID);
290
+ if (adapter.kind === 'postgres') lock = lock.forUpdate();
291
+ const entry = await lock.executeTakeFirst();
292
+ if (entry === undefined) return;
293
+ await trx.deleteFrom('blob_transfer_sessions').where('blob_id', '=', blobID).execute();
294
+ await trx.deleteFrom('blob_transfers').where('blob_id', '=', blobID).execute();
295
+ await trx.deleteFrom('blob_chunks').where('blob_id', '=', blobID).execute();
296
+ await trx.updateTable('blob_entries').set({
297
+ state: 'remote-only'
298
+ }).where('blob_id', '=', blobID).execute();
299
+ });
263
300
  });
264
301
  },
265
302
  async fillContentType (blobID, contentType) {
266
- await db.updateTable('blob_entries').set({
267
- content_type: contentType
268
- }).where('blob_id', '=', blobID).where('content_type', 'is', null).execute();
303
+ return mutate(blobID, async ()=>{
304
+ await db.updateTable('blob_entries').set({
305
+ content_type: contentType
306
+ }).where('blob_id', '=', blobID).where('content_type', 'is', null).execute();
307
+ });
269
308
  }
270
309
  };
271
310
  }
@@ -0,0 +1,3 @@
1
+ export declare class InvalidCursorError extends Error {
2
+ constructor(options?: ErrorOptions);
3
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,6 @@
1
+ export class InvalidCursorError extends Error {
2
+ constructor(options){
3
+ super('Invalid cursor', options);
4
+ this.name = 'InvalidCursorError';
5
+ }
6
+ }
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { BLOB_STORE, blobStoreDefinition, getBlobStore } from './definition.js';
2
+ export { InvalidCursorError } from './errors.js';
2
3
  export type { BlobChunkTable, BlobEntryTable, BlobTables, BlobTransferSessionTable, BlobTransferTable, } from './tables.js';
3
4
  export type { BeginTransferParams, BlobChunkInput, BlobEntry, BlobEntryInput, BlobState, BlobStoreAPI, BlobTransfer, } from './types.js';
package/lib/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { BLOB_STORE, blobStoreDefinition, getBlobStore } from './definition.js';
2
+ export { InvalidCursorError } from './errors.js';
package/lib/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { BatchReadOptions } from '@hozon/db';
1
2
  export type BlobState = 'local' | 'partial' | 'remote-only';
2
3
  export type BlobEntryInput = {
3
4
  blobID: string;
@@ -39,6 +40,7 @@ export type BlobTransfer = {
39
40
  export type BlobStoreAPI = {
40
41
  insertEntry(entry: BlobEntryInput, chunks: Array<BlobChunkInput>): Promise<void>;
41
42
  getEntry(blobID: string): Promise<BlobEntry | null>;
43
+ getEntries(blobIDs: Array<string>, options?: BatchReadOptions): Promise<Map<string, BlobEntry | null>>;
42
44
  getChunkDigests(blobID: string): Promise<Array<Uint8Array>>;
43
45
  setPinned(blobID: string, pinned: boolean): Promise<void>;
44
46
  deleteEntry(blobID: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hozon/store-blob",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Content-addressed blob metadata and resumable transfer store for Hozon",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,14 +18,14 @@
18
18
  ],
19
19
  "dependencies": {
20
20
  "@hozon/adapter": "^0.1.0",
21
- "@hozon/db": "^0.2.2",
22
- "@sozai/codec": "^0.4.2"
21
+ "@hozon/db": "^0.3.0",
22
+ "@sozai/codec": "^0.4.3"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@hozon/node-sqlite": "^0.1.0",
26
- "@types/node": "^26.6.4",
27
- "del-cli": "^7.0.0",
28
- "vitest": "5.0.3"
26
+ "@types/node": "^26.6.5",
27
+ "del-cli": "^8.0.1",
28
+ "vitest": "5.0.5"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"