@axium/storage 0.19.2 → 0.20.1

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/db.json CHANGED
@@ -89,6 +89,16 @@
89
89
  }
90
90
  }
91
91
  }
92
+ },
93
+ {
94
+ "delta": true,
95
+ "alter_tables": {
96
+ "storage": {
97
+ "alter_columns": {
98
+ "size": { "type": "bigint" }
99
+ }
100
+ }
101
+ }
92
102
  }
93
103
  ],
94
104
  "wipe": ["storage", "acl.storage"],
@@ -26,7 +26,7 @@ cli.command('usage')
26
26
  .action(async () => {
27
27
  const { limits, itemCount, usedBytes } = await api.getUserStats(session().userId);
28
28
  console.log(`Items: ${itemCount} ${limits.user_items ? ' / ' + limits.user_items : ''}`);
29
- console.log(`Space: ${formatBytes(usedBytes)} ${limits.user_size ? ' / ' + formatBytes(limits.user_size * 1_000_000) : ''}`);
29
+ console.log(`Space: ${formatBytes(usedBytes)} ${limits.user_size ? ' / ' + formatBytes(limits.user_size * 1000000n) : ''}`);
30
30
  });
31
31
  cli.command('ls')
32
32
  .alias('list')
@@ -14,7 +14,7 @@ declare const StorageCache: z.ZodObject<{
14
14
  name: z.ZodString;
15
15
  userId: z.ZodUUID;
16
16
  parentId: z.ZodNullable<z.ZodUUID>;
17
- size: z.ZodInt;
17
+ size: z.ZodCoercedBigInt<unknown>;
18
18
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
19
19
  type: z.ZodString;
20
20
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
package/dist/common.d.ts CHANGED
@@ -23,7 +23,7 @@ export declare const StorageItemMetadata: z.ZodObject<{
23
23
  name: z.ZodString;
24
24
  userId: z.ZodUUID;
25
25
  parentId: z.ZodNullable<z.ZodUUID>;
26
- size: z.ZodInt;
26
+ size: z.ZodCoercedBigInt<unknown>;
27
27
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
28
28
  type: z.ZodString;
29
29
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -67,12 +67,12 @@ export declare const syncProtocolVersion = 0;
67
67
  export declare const StorageLimits: z.ZodObject<{
68
68
  item_size: z.ZodInt;
69
69
  user_items: z.ZodInt;
70
- user_size: z.ZodInt;
70
+ user_size: z.ZodCoercedBigInt<unknown>;
71
71
  }, z.core.$strip>;
72
72
  export interface StorageLimits extends z.infer<typeof StorageLimits> {
73
73
  }
74
74
  export declare const StorageStats: z.ZodObject<{
75
- usedBytes: z.ZodInt;
75
+ usedBytes: z.ZodCoercedBigInt<unknown>;
76
76
  itemCount: z.ZodInt;
77
77
  lastModified: z.ZodCoercedDate<unknown>;
78
78
  lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
@@ -83,9 +83,9 @@ export declare const UserStorageInfo: z.ZodObject<{
83
83
  limits: z.ZodObject<{
84
84
  item_size: z.ZodInt;
85
85
  user_items: z.ZodInt;
86
- user_size: z.ZodInt;
86
+ user_size: z.ZodCoercedBigInt<unknown>;
87
87
  }, z.core.$strip>;
88
- usedBytes: z.ZodInt;
88
+ usedBytes: z.ZodCoercedBigInt<unknown>;
89
89
  itemCount: z.ZodInt;
90
90
  lastModified: z.ZodCoercedDate<unknown>;
91
91
  lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
@@ -103,7 +103,7 @@ export declare const UserStorage: z.ZodObject<{
103
103
  name: z.ZodString;
104
104
  userId: z.ZodUUID;
105
105
  parentId: z.ZodNullable<z.ZodUUID>;
106
- size: z.ZodInt;
106
+ size: z.ZodCoercedBigInt<unknown>;
107
107
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
108
108
  type: z.ZodString;
109
109
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -137,9 +137,9 @@ export declare const UserStorage: z.ZodObject<{
137
137
  limits: z.ZodObject<{
138
138
  item_size: z.ZodInt;
139
139
  user_items: z.ZodInt;
140
- user_size: z.ZodInt;
140
+ user_size: z.ZodCoercedBigInt<unknown>;
141
141
  }, z.core.$strip>;
142
- usedBytes: z.ZodInt;
142
+ usedBytes: z.ZodCoercedBigInt<unknown>;
143
143
  itemCount: z.ZodInt;
144
144
  lastModified: z.ZodCoercedDate<unknown>;
145
145
  lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
@@ -208,7 +208,7 @@ export declare const StorageConfig: z.ZodObject<{
208
208
  limits: z.ZodObject<{
209
209
  item_size: z.ZodInt;
210
210
  user_items: z.ZodInt;
211
- user_size: z.ZodInt;
211
+ user_size: z.ZodCoercedBigInt<unknown>;
212
212
  }, z.core.$strip>;
213
213
  trash_duration: z.ZodNumber;
214
214
  temp_dir: z.ZodString;
@@ -221,7 +221,7 @@ declare module '@axium/core/plugins' {
221
221
  }
222
222
  export declare const StorageItemInit: z.ZodObject<{
223
223
  name: z.ZodString;
224
- size: z.ZodInt;
224
+ size: z.ZodCoercedBigInt<unknown>;
225
225
  type: z.ZodString;
226
226
  parentId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
227
227
  hash: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
@@ -249,7 +249,7 @@ export declare const UploadInitResult: z.ZodDiscriminatedUnion<[z.ZodObject<{
249
249
  name: z.ZodString;
250
250
  userId: z.ZodUUID;
251
251
  parentId: z.ZodNullable<z.ZodUUID>;
252
- size: z.ZodInt;
252
+ size: z.ZodCoercedBigInt<unknown>;
253
253
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
254
254
  type: z.ZodString;
255
255
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -288,9 +288,9 @@ declare const StorageAPI: {
288
288
  limits: z.ZodObject<{
289
289
  item_size: z.ZodInt;
290
290
  user_items: z.ZodInt;
291
- user_size: z.ZodInt;
291
+ user_size: z.ZodCoercedBigInt<unknown>;
292
292
  }, z.core.$strip>;
293
- usedBytes: z.ZodInt;
293
+ usedBytes: z.ZodCoercedBigInt<unknown>;
294
294
  itemCount: z.ZodInt;
295
295
  lastModified: z.ZodCoercedDate<unknown>;
296
296
  lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
@@ -311,7 +311,7 @@ declare const StorageAPI: {
311
311
  name: z.ZodString;
312
312
  userId: z.ZodUUID;
313
313
  parentId: z.ZodNullable<z.ZodUUID>;
314
- size: z.ZodInt;
314
+ size: z.ZodCoercedBigInt<unknown>;
315
315
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
316
316
  type: z.ZodString;
317
317
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -345,9 +345,9 @@ declare const StorageAPI: {
345
345
  limits: z.ZodObject<{
346
346
  item_size: z.ZodInt;
347
347
  user_items: z.ZodInt;
348
- user_size: z.ZodInt;
348
+ user_size: z.ZodCoercedBigInt<unknown>;
349
349
  }, z.core.$strip>;
350
- usedBytes: z.ZodInt;
350
+ usedBytes: z.ZodCoercedBigInt<unknown>;
351
351
  itemCount: z.ZodInt;
352
352
  lastModified: z.ZodCoercedDate<unknown>;
353
353
  lastTrashed: z.ZodNullable<z.ZodCoercedDate<unknown>>;
@@ -364,7 +364,7 @@ declare const StorageAPI: {
364
364
  name: z.ZodString;
365
365
  userId: z.ZodUUID;
366
366
  parentId: z.ZodNullable<z.ZodUUID>;
367
- size: z.ZodInt;
367
+ size: z.ZodCoercedBigInt<unknown>;
368
368
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
369
369
  type: z.ZodString;
370
370
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -407,7 +407,7 @@ declare const StorageAPI: {
407
407
  name: z.ZodString;
408
408
  userId: z.ZodUUID;
409
409
  parentId: z.ZodNullable<z.ZodUUID>;
410
- size: z.ZodInt;
410
+ size: z.ZodCoercedBigInt<unknown>;
411
411
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
412
412
  type: z.ZodString;
413
413
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -450,7 +450,7 @@ declare const StorageAPI: {
450
450
  name: z.ZodString;
451
451
  userId: z.ZodUUID;
452
452
  parentId: z.ZodNullable<z.ZodUUID>;
453
- size: z.ZodInt;
453
+ size: z.ZodCoercedBigInt<unknown>;
454
454
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
455
455
  type: z.ZodString;
456
456
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -495,7 +495,7 @@ declare const StorageAPI: {
495
495
  }, z.core.$strip>;
496
496
  readonly PUT: readonly [z.ZodObject<{
497
497
  name: z.ZodString;
498
- size: z.ZodInt;
498
+ size: z.ZodCoercedBigInt<unknown>;
499
499
  type: z.ZodString;
500
500
  parentId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
501
501
  hash: z.ZodOptional<z.ZodNullable<z.ZodCustomStringFormat<"hex">>>;
@@ -520,7 +520,7 @@ declare const StorageAPI: {
520
520
  name: z.ZodString;
521
521
  userId: z.ZodUUID;
522
522
  parentId: z.ZodNullable<z.ZodUUID>;
523
- size: z.ZodInt;
523
+ size: z.ZodCoercedBigInt<unknown>;
524
524
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
525
525
  type: z.ZodString;
526
526
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -575,7 +575,7 @@ declare const StorageAPI: {
575
575
  name: z.ZodString;
576
576
  userId: z.ZodUUID;
577
577
  parentId: z.ZodNullable<z.ZodUUID>;
578
- size: z.ZodInt;
578
+ size: z.ZodCoercedBigInt<unknown>;
579
579
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
580
580
  type: z.ZodString;
581
581
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -620,7 +620,7 @@ declare const StorageAPI: {
620
620
  name: z.ZodString;
621
621
  userId: z.ZodUUID;
622
622
  parentId: z.ZodNullable<z.ZodUUID>;
623
- size: z.ZodInt;
623
+ size: z.ZodCoercedBigInt<unknown>;
624
624
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
625
625
  type: z.ZodString;
626
626
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -661,7 +661,7 @@ declare const StorageAPI: {
661
661
  name: z.ZodString;
662
662
  userId: z.ZodUUID;
663
663
  parentId: z.ZodNullable<z.ZodUUID>;
664
- size: z.ZodInt;
664
+ size: z.ZodCoercedBigInt<unknown>;
665
665
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
666
666
  type: z.ZodString;
667
667
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -706,7 +706,7 @@ declare const StorageAPI: {
706
706
  name: z.ZodString;
707
707
  userId: z.ZodUUID;
708
708
  parentId: z.ZodNullable<z.ZodUUID>;
709
- size: z.ZodInt;
709
+ size: z.ZodCoercedBigInt<unknown>;
710
710
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
711
711
  type: z.ZodString;
712
712
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -749,7 +749,7 @@ declare const StorageAPI: {
749
749
  name: z.ZodString;
750
750
  userId: z.ZodUUID;
751
751
  parentId: z.ZodNullable<z.ZodUUID>;
752
- size: z.ZodInt;
752
+ size: z.ZodCoercedBigInt<unknown>;
753
753
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
754
754
  type: z.ZodString;
755
755
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -792,7 +792,7 @@ declare const StorageAPI: {
792
792
  name: z.ZodString;
793
793
  userId: z.ZodUUID;
794
794
  parentId: z.ZodNullable<z.ZodUUID>;
795
- size: z.ZodInt;
795
+ size: z.ZodCoercedBigInt<unknown>;
796
796
  trashedAt: z.ZodNullable<z.ZodCoercedDate<unknown>>;
797
797
  type: z.ZodString;
798
798
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
package/dist/common.js CHANGED
@@ -26,7 +26,7 @@ export const StorageItemMetadata = z.object({
26
26
  name: z.string(),
27
27
  userId: z.uuid(),
28
28
  parentId: z.uuid().nullable(),
29
- size: z.int().nonnegative(),
29
+ size: z.coerce.bigint().nonnegative(),
30
30
  trashedAt: z.coerce.date().nullable(),
31
31
  type: z.string(),
32
32
  metadata: z.record(z.string(), z.unknown()),
@@ -44,10 +44,10 @@ export const StorageLimits = z.object({
44
44
  /** Maximum number of items per user */
45
45
  user_items: z.int().nonnegative(),
46
46
  /** The maximum storage size per user in MB */
47
- user_size: z.int().nonnegative(),
47
+ user_size: z.coerce.bigint().nonnegative(),
48
48
  });
49
49
  export const StorageStats = z.object({
50
- usedBytes: z.int().nonnegative(),
50
+ usedBytes: z.coerce.bigint().nonnegative(),
51
51
  itemCount: z.int().nonnegative(),
52
52
  lastModified: z.coerce.date(),
53
53
  lastTrashed: z.coerce.date().nullable(),
@@ -126,7 +126,7 @@ export const StorageConfig = StoragePublicConfig.safeExtend({
126
126
  setServerConfig('@axium/storage', StorageConfig);
127
127
  export const StorageItemInit = z.object({
128
128
  name: z.string(),
129
- size: z.int().nonnegative(),
129
+ size: z.coerce.bigint().nonnegative(),
130
130
  type: z.string(),
131
131
  parentId: z.uuid().nullish(),
132
132
  hash: z.hex().nullish(),
@@ -25,8 +25,8 @@ addRoute({
25
25
  const batchHeaderSize = Number(req.headers.get('x-batch-header-size'));
26
26
  if (!Number.isSafeInteger(batchHeaderSize) || batchHeaderSize < 2)
27
27
  error(400, 'Invalid or missing header, X-Batch-Header-Size');
28
- const size = Number(req.headers.get('content-length'));
29
- if (Number.isNaN(size))
28
+ const size = BigInt(req.headers.get('content-length') || '-1');
29
+ if (size < 0n)
30
30
  error(411, 'Missing or invalid content length header');
31
31
  const raw = await req.bytes();
32
32
  if (raw.byteLength - batchHeaderSize > size) {
@@ -77,7 +77,8 @@ addRoute({
77
77
  acl.check(item.acl, changedIds.has(item.id) ? { write: true } : { manage: true });
78
78
  error(403, 'Missing permission for item: ' + item.id);
79
79
  }
80
- if (limits.user_size && (usage.usedBytes + size - items.reduce((sum, item) => sum + item.size, 0)) / 1_000_000 >= limits.user_size)
80
+ if (limits.user_size &&
81
+ (usage.usedBytes + size - items.reduce((sum, item) => sum + item.size, 0n)) / 1000000n >= limits.user_size)
81
82
  error(413, 'Not enough space');
82
83
  const tx = await database.startTransaction().execute();
83
84
  const results = new Map();
@@ -88,7 +89,7 @@ addRoute({
88
89
  const result = await tx
89
90
  .updateTable('storage')
90
91
  .where('id', '=', itemId)
91
- .set({ size, modifiedAt: new Date(), hash })
92
+ .set({ size: BigInt(size), modifiedAt: new Date(), hash })
92
93
  .returningAll()
93
94
  .executeTakeFirstOrThrow();
94
95
  writeFileSync(join(getConfig('@axium/storage').data, result.id), content);
@@ -15,7 +15,7 @@ cli.command('usage')
15
15
  .selectFrom('storage')
16
16
  .select(eb => eb.fn.sum('size').as('size'))
17
17
  .executeTakeFirstOrThrow();
18
- console.log(`${items} items totaling ${formatBytes(Number(size))}`);
18
+ console.log(`${items} items totaling ${formatBytes(BigInt(size))}`);
19
19
  });
20
20
  const _byteSize = (msg) => (v) => parseByteSize(v) ?? io.exit(msg);
21
21
  cli.command('query')
@@ -48,7 +48,7 @@ cli.command('query')
48
48
  }
49
49
  let validMinSize = false;
50
50
  if (opt.minSize !== undefined) {
51
- if (opt.minSize == 0)
51
+ if (opt.minSize == 0n)
52
52
  io.warn('Minimum size of 0 has no effect, ignoring.');
53
53
  else {
54
54
  query = query.where('size', '>=', opt.minSize);
@@ -50,6 +50,6 @@ export async function getLimits(userId) {
50
50
  catch {
51
51
  limits = structuredClone(getConfig('@axium/storage').limits);
52
52
  }
53
- limits.user_size ||= Number(await _unlimitedLimit());
53
+ limits.user_size ||= await _unlimitedLimit();
54
54
  return limits;
55
55
  }
package/dist/server/db.js CHANGED
@@ -25,9 +25,11 @@ export async function getUserStats(userId) {
25
25
  eb.fn.max('trashedAt').as('lastTrashed'),
26
26
  ])
27
27
  .executeTakeFirstOrThrow();
28
- result.usedBytes = Number(result.usedBytes || 0);
29
- result.itemCount = Number(result.itemCount);
30
- return result;
28
+ return {
29
+ ...result,
30
+ usedBytes: BigInt(result.usedBytes || 0n),
31
+ itemCount: Number(result.itemCount),
32
+ };
31
33
  }
32
34
  export async function get(itemId) {
33
35
  const result = await database
@@ -12,7 +12,7 @@ export function load() {
12
12
  export async function statusText() {
13
13
  const { storage: items } = await count('storage');
14
14
  const size = await getTotalUse();
15
- return `${items} items totaling ${formatBytes(Number(size))}`;
15
+ return `${items} items totaling ${formatBytes(size)}`;
16
16
  }
17
17
  export async function clean(opt) {
18
18
  start('Removing expired trash items');
@@ -16,7 +16,7 @@ export interface UploadInfo {
16
16
  file: string;
17
17
  fd: number;
18
18
  hash: Hash;
19
- uploadedBytes: number;
19
+ uploadedBytes: bigint;
20
20
  sessionId: string;
21
21
  userId: string;
22
22
  init: StorageItemInit;
@@ -31,11 +31,11 @@ export async function checkNewItem(init, session) {
31
31
  : null;
32
32
  if (parentId)
33
33
  await authSessionForItem('storage', parentId, { write: true }, session);
34
- if (Number.isNaN(size))
34
+ if (BigInt(size) < 0n)
35
35
  error(411, 'Missing or invalid content length');
36
36
  if (limits.user_items && usage.itemCount >= limits.user_items)
37
37
  error(409, 'Too many items');
38
- if (limits.user_size && (usage.usedBytes + size) / 1_000_000 >= limits.user_size)
38
+ if (limits.user_size && (usage.usedBytes + size) / 1000000n >= limits.user_size)
39
39
  error(413, 'Not enough space');
40
40
  if (limits.item_size && size > limits.item_size * 1_000_000)
41
41
  error(413, 'File size exceeds maximum size');
@@ -120,7 +120,7 @@ export function startUpload(init, session) {
120
120
  hash: createHash('BLAKE2b512'),
121
121
  file,
122
122
  fd,
123
- uploadedBytes: 0,
123
+ uploadedBytes: 0n,
124
124
  sessionId: session.id,
125
125
  userId: session.userId,
126
126
  init,
@@ -57,13 +57,22 @@ import { database } from '@axium/server/database';
57
57
  import { error, withError } from '@axium/server/requests';
58
58
  import { addRoute } from '@axium/server/routes';
59
59
  import { createHash } from 'node:crypto';
60
- import { closeSync, openSync, readFileSync, readSync, renameSync, unlinkSync, writeFileSync, writeSync } from 'node:fs';
60
+ import { closeSync, copyFileSync, openSync, readSync, renameSync, unlinkSync, writeFileSync, writeSync } from 'node:fs';
61
61
  import { join } from 'node:path/posix';
62
62
  import * as z from 'zod';
63
63
  import '../polyfills.js';
64
64
  import { getLimits } from './config.js';
65
65
  import { getUserStats, parseItem } from './db.js';
66
66
  import { checkNewItem, createNewItem, requireUpload } from './item.js';
67
+ function contentDispositionFor(name) {
68
+ const fallback = name
69
+ .replace(/[\r\n]/g, '')
70
+ .replace(/[^\x20-\x7E]/g, '_')
71
+ .trim()
72
+ .replace(/[\\"]/g, '\\$&') || 'download';
73
+ const encoded = encodeURIComponent(name.replace(/[\r\n]/g, '')).replace(/['()*]/g, char => '%' + char.charCodeAt(0).toString(16).toUpperCase());
74
+ return `attachment; filename="${fallback}"; filename*=UTF-8''${encoded}`;
75
+ }
67
76
  addRoute({
68
77
  path: '/raw/storage',
69
78
  async PUT(request) {
@@ -73,7 +82,7 @@ addRoute({
73
82
  const { userId } = session;
74
83
  const name = request.headers.get('x-name'); // checked in `checkNewItem`
75
84
  const parentId = request.headers.get('x-parent');
76
- const size = Number(request.headers.get('x-size'));
85
+ const size = BigInt(request.headers.get('x-size') || -1);
77
86
  const type = request.headers.get('content-type') || 'application/octet-stream';
78
87
  const content = await request.bytes();
79
88
  if (content.byteLength > size) {
@@ -92,22 +101,22 @@ addRoute({
92
101
  if (!getConfig('@axium/storage').enabled)
93
102
  error(503, 'User storage is disabled');
94
103
  const upload = await requireUpload(request);
95
- const size = Number(request.headers.get('content-length'));
96
- if (Number.isNaN(size))
104
+ const size = BigInt(request.headers.get('content-length') || -1);
105
+ if (size < 0n)
97
106
  error(411, 'Missing or invalid content length');
98
107
  if (upload.uploadedBytes + size > upload.init.size)
99
108
  error(413, 'Upload exceeds allowed size');
100
109
  const content = await request.bytes();
101
- if (content.byteLength != size) {
110
+ if (content.byteLength != Number(size)) {
102
111
  await audit('storage_size_mismatch', upload.userId, { item: null });
103
112
  error(400, `Content length mismatch: expected ${size}, got ${content.byteLength}`);
104
113
  }
105
- const offset = Number(request.headers.get('x-offset'));
114
+ const offset = BigInt(request.headers.get('x-offset') || -1);
106
115
  if (offset != upload.uploadedBytes)
107
116
  error(400, `Expected offset ${upload.uploadedBytes} but got ${offset}`);
108
117
  writeSync(upload.fd, content); // opened with 'a', this appends
109
118
  upload.hash.update(content);
110
- upload.uploadedBytes += size;
119
+ upload.uploadedBytes += BigInt(size);
111
120
  if (upload.uploadedBytes != upload.init.size)
112
121
  return new Response(null, { status: 204 });
113
122
  const hash = upload.hash.digest();
@@ -122,7 +131,7 @@ addRoute({
122
131
  catch (e) {
123
132
  if (e.code != 'EXDEV')
124
133
  throw e;
125
- writeFileSync(path, readFileSync(upload.file));
134
+ copyFileSync(upload.file, path);
126
135
  }
127
136
  });
128
137
  try {
@@ -149,14 +158,14 @@ addRoute({
149
158
  const range = request.headers.get('range');
150
159
  const fd = openSync(path, 'r');
151
160
  const _ = __addDisposableResource(env_1, { [Symbol.dispose]: () => closeSync(fd) }, false);
152
- let start = 0, end = item.size - 1, length = item.size;
161
+ let start = 0, end = Number(item.size - 1n), length = Number(item.size);
153
162
  if (range) {
154
- const [_start, _end = item.size - 1] = range
163
+ const [_start, _end = end] = range
155
164
  .replace(/bytes=/, '')
156
165
  .split('-')
157
166
  .map(val => (val && Number.isSafeInteger(parseInt(val)) ? parseInt(val) : undefined));
158
- start = typeof _start == 'number' ? _start : item.size - _end;
159
- end = typeof _start == 'number' ? _end : item.size - 1;
167
+ start = typeof _start == 'number' ? _start : Number(item.size) - _end;
168
+ end = typeof _start == 'number' ? _end : end;
160
169
  length = end - start + 1;
161
170
  }
162
171
  if (start >= item.size || end >= item.size || start > end || start < 0) {
@@ -168,13 +177,13 @@ addRoute({
168
177
  const content = new Uint8Array(length);
169
178
  readSync(fd, content, 0, length, start);
170
179
  return new Response(content, {
171
- status: length == item.size ? 200 : 206,
180
+ status: BigInt(length) == item.size ? 200 : 206,
172
181
  headers: {
173
182
  'Content-Range': `bytes ${start}-${end}/${item.size}`,
174
183
  'Accept-Ranges': 'bytes',
175
184
  'Content-Length': String(length),
176
185
  'Content-Type': item.type,
177
- 'Content-Disposition': `attachment; filename="${item.name}"`,
186
+ 'Content-Disposition': contentDispositionFor(item.name),
178
187
  },
179
188
  });
180
189
  }
@@ -205,7 +214,7 @@ addRoute({
205
214
  if (Number.isNaN(size))
206
215
  error(411, 'Missing or invalid content length header');
207
216
  const [usage, limits] = await Promise.all([getUserStats(item.userId), getLimits(item.userId)]).catch(withError('Could not fetch usage and/or limits'));
208
- if (limits.user_size && (usage.usedBytes + size - item.size) / 1_000_000 >= limits.user_size)
217
+ if (limits.user_size && (usage.usedBytes + BigInt(size) - item.size) / 1000000n >= limits.user_size)
209
218
  error(413, 'Not enough space');
210
219
  if (limits.item_size && size > limits.item_size * 1_000_000)
211
220
  error(413, 'File size exceeds maximum size');
@@ -220,7 +229,7 @@ addRoute({
220
229
  const result = await tx
221
230
  .updateTable('storage')
222
231
  .where('id', '=', itemId)
223
- .set({ size, modifiedAt: new Date(), hash })
232
+ .set({ size: BigInt(size), modifiedAt: new Date(), hash })
224
233
  .returningAll()
225
234
  .executeTakeFirstOrThrow();
226
235
  writeFileSync(join(getConfig('@axium/storage').data, result.id), content);
package/lib/Add.svelte CHANGED
@@ -38,7 +38,6 @@
38
38
  <span class="menu-item" onclick={() => uploadDialog.showModal()}><Icon i="upload" />{text('storage.Add.upload')}</span>
39
39
  {@render _item('inode/directory', text('storage.Add.new_folder'))}
40
40
  {@render _item('text/plain', text('storage.Add.plain_text'))}
41
- {@render _item('text/x-uri', text('storage.Add.url'), true)}
42
41
  </Popover>
43
42
 
44
43
  <FormDialog
@@ -70,11 +70,11 @@
70
70
  </div>
71
71
  <div class="preview-content">
72
72
  {#if item.type.startsWith('image/')}
73
- <img src={item.dataURL} alt={item.name} width="100%" />
73
+ <img src={item.dataURL} alt={item.name} />
74
74
  {:else if item.type.startsWith('audio/')}
75
75
  <audio src={item.dataURL} controls></audio>
76
76
  {:else if item.type.startsWith('video/')}
77
- <video src={item.dataURL} controls width="100%">
77
+ <video src={item.dataURL} controls>
78
78
  <track kind="captions" />
79
79
  </video>
80
80
  {:else if item.type == 'application/pdf'}
@@ -195,7 +195,12 @@
195
195
 
196
196
  .preview-content {
197
197
  position: absolute;
198
- inset: 3em 10em 0;
198
+ inset: 3em 10em 1em;
199
+ display: flex;
200
+ text-align: center;
201
+ align-items: center;
202
+ justify-content: center;
203
+ flex-direction: column;
199
204
 
200
205
  .full-fill {
201
206
  position: absolute;
@@ -211,6 +216,12 @@
211
216
  background-color: var(--bg-menu);
212
217
  font-family: monospace;
213
218
  }
219
+
220
+ img,
221
+ video {
222
+ max-width: 100%;
223
+ max-height: 100%;
224
+ }
214
225
  }
215
226
 
216
227
  .no-preview {
package/lib/Usage.svelte CHANGED
@@ -15,11 +15,11 @@
15
15
  <p>
16
16
  <a href="/files/usage">
17
17
  <NumberBar
18
- max={info.limits.user_size && info.limits.user_size * 1_000_000}
18
+ max={info.limits.user_size && info.limits.user_size * 1_000_000n}
19
19
  value={info.usedBytes}
20
20
  text="{formatBytes(info.usedBytes)} {!info.limits.user_size
21
21
  ? ''
22
- : '/ ' + formatBytes(info.limits.user_size * 1_000_000)}"
22
+ : '/ ' + formatBytes(info.limits.user_size * 1_000_000n)}"
23
23
  />
24
24
  </a>
25
25
  </p>
package/locales/en.json CHANGED
@@ -70,8 +70,7 @@
70
70
  "new_folder": "New Folder",
71
71
  "plain_text": "Plain Text",
72
72
  "text": "Add",
73
- "upload": "Upload",
74
- "url": "URL"
73
+ "upload": "Upload"
75
74
  },
76
75
  "List": {
77
76
  "copy_link": "Copy Link",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/storage",
3
- "version": "0.19.2",
3
+ "version": "0.20.1",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "description": "User file storage for Axium",
6
6
  "funding": {
@@ -40,8 +40,8 @@
40
40
  "build": "tsc"
41
41
  },
42
42
  "peerDependencies": {
43
- "@axium/client": ">=0.19.0",
44
- "@axium/core": ">=0.19.0",
43
+ "@axium/client": ">=0.19.1",
44
+ "@axium/core": ">=0.22.0",
45
45
  "@axium/server": ">=0.39.0",
46
46
  "@sveltejs/kit": "^2.27.3",
47
47
  "utilium": "^2.6.3"
@@ -12,7 +12,7 @@
12
12
 
13
13
  let barText = $derived(
14
14
  limits.user_size
15
- ? text('page.files.usage.bar_text', { used: formatBytes(usedBytes), total: formatBytes(limits.user_size * 1_000_000) })
15
+ ? text('page.files.usage.bar_text', { used: formatBytes(usedBytes), total: formatBytes(limits.user_size * 1_000_000n) })
16
16
  : text('page.files.usage.bar_text_unlimited', { used: formatBytes(usedBytes) })
17
17
  );
18
18
  </script>
@@ -23,6 +23,6 @@
23
23
 
24
24
  <h2>{text('page.files.usage.heading')}</h2>
25
25
 
26
- <p><NumberBar max={limits.user_size * 1_000_000} value={usedBytes} text={barText} /></p>
26
+ <p><NumberBar max={limits.user_size * 1_000_000n} value={usedBytes} text={barText} /></p>
27
27
 
28
28
  <List bind:items emptyText={text('page.files.usage.empty')} user={data.session?.user} />