@axium/storage 0.2.4 → 0.3.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/dist/common.d.ts +2 -1
- package/dist/server.d.ts +9 -3
- package/dist/server.js +64 -39
- package/package.json +1 -1
package/dist/common.d.ts
CHANGED
|
@@ -48,7 +48,8 @@ export type StorageItemUpdate = z.infer<typeof StorageItemUpdate>;
|
|
|
48
48
|
export interface StorageItemMetadata<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
49
49
|
createdAt: Date;
|
|
50
50
|
dataURL: string;
|
|
51
|
-
hash
|
|
51
|
+
/** The hash of the file, or null if it is a directory */
|
|
52
|
+
hash: string | null;
|
|
52
53
|
id: string;
|
|
53
54
|
immutable: boolean;
|
|
54
55
|
modifiedAt: Date;
|
package/dist/server.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ declare module '@axium/server/database' {
|
|
|
6
6
|
interface Schema {
|
|
7
7
|
storage: {
|
|
8
8
|
createdAt: Generated<Date>;
|
|
9
|
-
hash: Uint8Array;
|
|
9
|
+
hash: Uint8Array | null;
|
|
10
10
|
id: Generated<string>;
|
|
11
11
|
immutable: Generated<boolean>;
|
|
12
12
|
modifiedAt: Generated<Date>;
|
|
@@ -24,6 +24,11 @@ declare module '@axium/server/database' {
|
|
|
24
24
|
storage: ColumnTypes<Schema['storage']>;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* @internal A storage item selected from the database.
|
|
29
|
+
*/
|
|
30
|
+
interface SelectedItem extends Selectable<Schema['storage']> {
|
|
31
|
+
}
|
|
27
32
|
declare module '@axium/server/config' {
|
|
28
33
|
interface Config {
|
|
29
34
|
storage: {
|
|
@@ -52,15 +57,16 @@ declare module '@axium/server/config' {
|
|
|
52
57
|
export interface StorageItem extends StorageItemMetadata {
|
|
53
58
|
data: Uint8Array<ArrayBufferLike>;
|
|
54
59
|
}
|
|
55
|
-
export declare function parseItem<T extends
|
|
60
|
+
export declare function parseItem<T extends SelectedItem>(item: T): Omit<T, keyof Schema['storage']> & StorageItemMetadata;
|
|
56
61
|
/**
|
|
57
62
|
* Returns the current usage of the storage for a user in bytes.
|
|
58
63
|
*/
|
|
59
64
|
export declare function currentUsage(userId: string): Promise<StorageUsage>;
|
|
60
|
-
export declare function get
|
|
65
|
+
export declare function get(itemId: string): Promise<StorageItemMetadata>;
|
|
61
66
|
export type ExternalLimitHandler = (userId?: string) => StorageLimits | Promise<StorageLimits>;
|
|
62
67
|
/**
|
|
63
68
|
* Define the handler to get limits for a user externally.
|
|
64
69
|
*/
|
|
65
70
|
export declare function useLimits(handler: ExternalLimitHandler): void;
|
|
66
71
|
export declare function getLimits(userId?: string): Promise<StorageLimits>;
|
|
72
|
+
export {};
|
package/dist/server.js
CHANGED
|
@@ -8,7 +8,6 @@ import { addRoute } from '@axium/server/routes';
|
|
|
8
8
|
import { error } from '@sveltejs/kit';
|
|
9
9
|
import { createHash } from 'node:crypto';
|
|
10
10
|
import { linkSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
11
|
-
import { writeFile } from 'node:fs/promises';
|
|
12
11
|
import { join } from 'node:path/posix';
|
|
13
12
|
import * as z from 'zod';
|
|
14
13
|
import { StorageItemUpdate } from './common.js';
|
|
@@ -50,9 +49,8 @@ addConfigDefaults({
|
|
|
50
49
|
export function parseItem(item) {
|
|
51
50
|
return {
|
|
52
51
|
...item,
|
|
53
|
-
metadata: item.metadata,
|
|
54
52
|
dataURL: `/raw/storage/${item.id}`,
|
|
55
|
-
hash: item.hash
|
|
53
|
+
hash: item.hash?.toHex(),
|
|
56
54
|
};
|
|
57
55
|
}
|
|
58
56
|
/**
|
|
@@ -140,6 +138,8 @@ addRoute({
|
|
|
140
138
|
.returningAll()
|
|
141
139
|
.executeTakeFirstOrThrow()
|
|
142
140
|
.catch(withError('Could not delete item'));
|
|
141
|
+
if (item.type == 'inode/directory')
|
|
142
|
+
return item;
|
|
143
143
|
const { count } = await database
|
|
144
144
|
.selectFrom('storage')
|
|
145
145
|
.where('hash', '=', Uint8Array.fromHex(item.hash))
|
|
@@ -191,6 +191,8 @@ addRoute({
|
|
|
191
191
|
.parseAsync(maybeParentId)
|
|
192
192
|
.catch(() => error(400, 'Invalid parent ID'))
|
|
193
193
|
: null;
|
|
194
|
+
if (parentId)
|
|
195
|
+
await checkAuthForItem(event, 'storage', parentId, Permission.Edit);
|
|
194
196
|
const size = Number(event.request.headers.get('content-length'));
|
|
195
197
|
if (Number.isNaN(size))
|
|
196
198
|
error(411, 'Missing or invalid content length header');
|
|
@@ -205,33 +207,48 @@ addRoute({
|
|
|
205
207
|
if (content.byteLength > size)
|
|
206
208
|
error(400, 'Content length does not match size header');
|
|
207
209
|
const type = event.request.headers.get('content-type') || 'application/octet-stream';
|
|
210
|
+
const isDirectory = type == 'inode/directory';
|
|
211
|
+
if (isDirectory && size > 0)
|
|
212
|
+
error(400, 'Directories can not have content');
|
|
208
213
|
const useCAS = config.storage.cas.enabled &&
|
|
214
|
+
!isDirectory &&
|
|
209
215
|
(defaultCASMime.some(pattern => pattern.test(type)) || config.storage.cas.include.some(mime => type.match(mime)));
|
|
210
|
-
const hash = createHash('BLAKE2b512').update(content).digest();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
216
|
+
const hash = isDirectory ? null : createHash('BLAKE2b512').update(content).digest();
|
|
217
|
+
const tx = await database.startTransaction().execute();
|
|
218
|
+
try {
|
|
219
|
+
const item = parseItem(await tx
|
|
220
|
+
.insertInto('storage')
|
|
221
|
+
.values({ userId, hash, name, size, type, immutable: useCAS, parentId })
|
|
222
|
+
.returningAll()
|
|
223
|
+
.executeTakeFirstOrThrow());
|
|
224
|
+
const path = join(config.storage.data, item.id);
|
|
225
|
+
if (!useCAS) {
|
|
226
|
+
if (!isDirectory)
|
|
227
|
+
writeFileSync(path, content);
|
|
228
|
+
return item;
|
|
229
|
+
}
|
|
230
|
+
const existing = await tx
|
|
231
|
+
.selectFrom('storage')
|
|
232
|
+
.select('id')
|
|
233
|
+
.where('hash', '=', hash)
|
|
234
|
+
.where('id', '!=', item.id)
|
|
235
|
+
.limit(1)
|
|
236
|
+
.executeTakeFirst();
|
|
237
|
+
if (!existing) {
|
|
238
|
+
if (!isDirectory)
|
|
239
|
+
writeFileSync(path, content);
|
|
240
|
+
return item;
|
|
241
|
+
}
|
|
242
|
+
linkSync(join(config.storage.data, existing.id), path);
|
|
243
|
+
return item;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
await tx.rollback().execute();
|
|
247
|
+
throw withError('Could not create item', 500)(error);
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
await tx.commit().execute();
|
|
251
|
+
}
|
|
235
252
|
},
|
|
236
253
|
});
|
|
237
254
|
addRoute({
|
|
@@ -258,7 +275,9 @@ addRoute({
|
|
|
258
275
|
const itemId = event.params.id;
|
|
259
276
|
const { item } = await checkAuthForItem(event, 'storage', itemId, Permission.Edit);
|
|
260
277
|
if (item.immutable)
|
|
261
|
-
error(
|
|
278
|
+
error(405, 'Item is immutable');
|
|
279
|
+
if (item.type == 'inode/directory')
|
|
280
|
+
error(409, 'Directories do not have content');
|
|
262
281
|
if (item.trashedAt)
|
|
263
282
|
error(410, 'Trashed items can not be changed');
|
|
264
283
|
const type = event.request.headers.get('content-type') || 'application/octet-stream';
|
|
@@ -278,16 +297,22 @@ addRoute({
|
|
|
278
297
|
if (content.byteLength > size)
|
|
279
298
|
error(400, 'Content length does not match size header');
|
|
280
299
|
const hash = createHash('BLAKE2b512').update(content).digest();
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
const tx = await database.startTransaction().execute();
|
|
301
|
+
try {
|
|
302
|
+
const result = await tx
|
|
303
|
+
.updateTable('storage')
|
|
304
|
+
.where('id', '=', itemId)
|
|
305
|
+
.set({ size, modifiedAt: new Date(), hash })
|
|
306
|
+
.returningAll()
|
|
307
|
+
.executeTakeFirstOrThrow();
|
|
308
|
+
writeFileSync(join(config.storage.data, result.id), content);
|
|
309
|
+
await tx.commit().execute();
|
|
310
|
+
return parseItem(result);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
await tx.rollback().execute();
|
|
314
|
+
throw withError('Could not update item', 500)(error);
|
|
315
|
+
}
|
|
291
316
|
},
|
|
292
317
|
});
|
|
293
318
|
addRoute({
|