@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 +2 -2
- package/lib/api.js +187 -148
- package/lib/errors.d.ts +3 -0
- package/lib/errors.js +6 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/types.d.ts +2 -0
- package/package.json +6 -6
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
|
|
22
|
+
throw new InvalidCursorError();
|
|
22
23
|
}
|
|
23
|
-
|
|
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
|
-
|
|
41
|
-
await trx
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
await
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
'
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
})).
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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(
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
}
|
package/lib/errors.d.ts
ADDED
package/lib/errors.js
ADDED
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
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.
|
|
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.
|
|
22
|
-
"@sozai/codec": "^0.4.
|
|
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.
|
|
27
|
-
"del-cli": "^
|
|
28
|
-
"vitest": "5.0.
|
|
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"
|