@basaltkit/files 1.0.1 → 1.1.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/files.d.ts +11 -1
- package/dist/files.js +13 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +5 -4
package/dist/files.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { BasaltError, type DurationInput, type HookBus } from '@basaltkit/core';
|
|
2
2
|
import type { Disk } from '@basaltkit/storage';
|
|
3
3
|
import { type FileRecord, type FileStore } from './store.js';
|
|
4
|
+
/** Default upload cap (25 MiB) applied when `validate.maxSize` is not set. */
|
|
5
|
+
export declare const DEFAULT_MAX_FILE_SIZE: number;
|
|
4
6
|
export declare class FileTooLargeError extends BasaltError {
|
|
5
7
|
readonly status = 413;
|
|
6
8
|
constructor(size: number, max: number);
|
|
@@ -68,7 +70,15 @@ export declare class Files {
|
|
|
68
70
|
record: FileRecord;
|
|
69
71
|
content: Buffer;
|
|
70
72
|
}>;
|
|
71
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Signed download URL — served `Content-Disposition: attachment` by default
|
|
75
|
+
* so an uploaded HTML/SVG file can never render top-level on the storage
|
|
76
|
+
* origin; pass `{ disposition: 'inline' }` when in-browser rendering is
|
|
77
|
+
* deliberate (embedded <img>/<video> uses render regardless).
|
|
78
|
+
*/
|
|
79
|
+
temporaryUrl(id: string, expiresIn: DurationInput, tenantId?: string, options?: {
|
|
80
|
+
disposition?: 'attachment' | 'inline';
|
|
81
|
+
}): Promise<string>;
|
|
72
82
|
delete(id: string, tenantId?: string): Promise<void>;
|
|
73
83
|
/** Records the result of an out-of-band scan (antivirus, moderation, …). */
|
|
74
84
|
markScanned(id: string, result: {
|
package/dist/files.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { BasaltError, runWithContext, tryCtx } from '@basaltkit/core';
|
|
3
3
|
import { MemoryFileStore } from './store.js';
|
|
4
|
+
/** Default upload cap (25 MiB) applied when `validate.maxSize` is not set. */
|
|
5
|
+
export const DEFAULT_MAX_FILE_SIZE = 25 * 1024 * 1024;
|
|
4
6
|
export class FileTooLargeError extends BasaltError {
|
|
5
7
|
status = 413;
|
|
6
8
|
constructor(size, max) {
|
|
@@ -52,7 +54,9 @@ export class Files {
|
|
|
52
54
|
this.disk = options.disk;
|
|
53
55
|
this.store = options.store ?? new MemoryFileStore();
|
|
54
56
|
this.hooks = options.hooks;
|
|
55
|
-
|
|
57
|
+
// Secure by default (review 2026-08-b, S-3): uploads are capped even when
|
|
58
|
+
// the app configures nothing. Raise (or set Infinity) via validate.maxSize.
|
|
59
|
+
this.validation = { maxSize: DEFAULT_MAX_FILE_SIZE, ...options.validate };
|
|
56
60
|
this.maxTotalBytes = options.maxTotalBytes;
|
|
57
61
|
this.checkQuota = options.checkQuota;
|
|
58
62
|
this.now = options.now ?? Date.now;
|
|
@@ -96,12 +100,18 @@ export class Files {
|
|
|
96
100
|
const content = await this.inTenant(resolved, () => this.disk.get(record.path));
|
|
97
101
|
return { record, content };
|
|
98
102
|
}
|
|
99
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Signed download URL — served `Content-Disposition: attachment` by default
|
|
105
|
+
* so an uploaded HTML/SVG file can never render top-level on the storage
|
|
106
|
+
* origin; pass `{ disposition: 'inline' }` when in-browser rendering is
|
|
107
|
+
* deliberate (embedded <img>/<video> uses render regardless).
|
|
108
|
+
*/
|
|
109
|
+
async temporaryUrl(id, expiresIn, tenantId, options = {}) {
|
|
100
110
|
const resolved = this.tenant(tenantId);
|
|
101
111
|
const record = await this.store.find(resolved, id);
|
|
102
112
|
if (!record)
|
|
103
113
|
throw new FileNotFoundError();
|
|
104
|
-
return this.inTenant(resolved, () => this.disk.temporaryUrl(record.path, expiresIn));
|
|
114
|
+
return this.inTenant(resolved, () => this.disk.temporaryUrl(record.path, expiresIn, options));
|
|
105
115
|
}
|
|
106
116
|
async delete(id, tenantId) {
|
|
107
117
|
const resolved = this.tenant(tenantId);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Files, FileTooLargeError, FileTypeNotAllowedError, StorageQuotaExceededError, FileNotFoundError, FileTenantRequiredError, type FilesOptions, type FileValidation, type UploadInput, } from './files.js';
|
|
1
|
+
export { Files, DEFAULT_MAX_FILE_SIZE, FileTooLargeError, FileTypeNotAllowedError, StorageQuotaExceededError, FileNotFoundError, FileTenantRequiredError, type FilesOptions, type FileValidation, type UploadInput, } from './files.js';
|
|
2
2
|
export { MemoryFileStore, type FileRecord, type FileStore, type FilePatch, } from './store.js';
|
|
3
3
|
export { filesPlugin, fileRoutes, FILES, type FilesPluginOptions } from './plugin.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Files, FileTooLargeError, FileTypeNotAllowedError, StorageQuotaExceededError, FileNotFoundError, FileTenantRequiredError, } from './files.js';
|
|
1
|
+
export { Files, DEFAULT_MAX_FILE_SIZE, FileTooLargeError, FileTypeNotAllowedError, StorageQuotaExceededError, FileNotFoundError, FileTenantRequiredError, } from './files.js';
|
|
2
2
|
export { MemoryFileStore, } from './store.js';
|
|
3
3
|
export { filesPlugin, fileRoutes, FILES } from './plugin.js';
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Disk } from '@basaltkit/storage';
|
|
2
|
-
import { type BasaltRoute } from '@basaltkit/
|
|
2
|
+
import { type BasaltRoute } from '@basaltkit/http';
|
|
3
3
|
import { Files, type FileValidation, type FilesOptions } from './files.js';
|
|
4
4
|
import type { FileRecord, FileStore } from './store.js';
|
|
5
5
|
declare module '@basaltkit/core' {
|
package/dist/plugin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createToken, ctx, definePlugin } from '@basaltkit/core';
|
|
2
2
|
import { STORAGE } from '@basaltkit/storage';
|
|
3
|
-
import { route } from '@basaltkit/
|
|
3
|
+
import { route } from '@basaltkit/http';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { Files } from './files.js';
|
|
6
6
|
export const FILES = createToken('files');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basaltkit/files",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "File uploads for Basalt: validation (type/size), per-tenant storage quota, metadata records, signed URLs, and hooks (antivirus/thumbnails) over @basaltkit/storage.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@basaltkit/core": "^1.
|
|
18
|
-
"@basaltkit/
|
|
19
|
-
"@basaltkit/
|
|
17
|
+
"@basaltkit/core": "^1.3.0",
|
|
18
|
+
"@basaltkit/http": "^1.10.0",
|
|
19
|
+
"@basaltkit/storage": "^1.3.0"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"zod": "^3.24.0 || ^4.0.0"
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"typescript": "^7.0.2",
|
|
27
27
|
"vitest": "^4.1.11",
|
|
28
28
|
"zod": "^3.24.0 || ^4.0.0",
|
|
29
|
+
"@basaltkit/fastify": "^1.7.0",
|
|
29
30
|
"@basaltkit/tsconfig": "^0.24.0"
|
|
30
31
|
},
|
|
31
32
|
"publishConfig": {
|