uploaderkit 1.0.0 → 2.0.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/LICENSE +21 -0
- package/NOTICE +16 -0
- package/README.es.md +1140 -0
- package/README.md +1135 -0
- package/dist/FileViewer-ChTf7-uY.d.cts +49 -0
- package/dist/FileViewer-nuD-GEdJ.d.ts +49 -0
- package/dist/adapters/gcs.cjs +83 -0
- package/dist/adapters/gcs.cjs.map +1 -0
- package/dist/adapters/gcs.d.cts +31 -0
- package/dist/adapters/gcs.d.ts +31 -0
- package/dist/adapters/gcs.js +81 -0
- package/dist/adapters/gcs.js.map +1 -0
- package/dist/adapters/memory.cjs +27 -0
- package/dist/adapters/memory.cjs.map +1 -0
- package/dist/adapters/memory.d.cts +18 -0
- package/dist/adapters/memory.d.ts +18 -0
- package/dist/adapters/memory.js +25 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/s3.cjs +75 -0
- package/dist/adapters/s3.cjs.map +1 -0
- package/dist/adapters/s3.d.cts +27 -0
- package/dist/adapters/s3.d.ts +27 -0
- package/dist/adapters/s3.js +73 -0
- package/dist/adapters/s3.js.map +1 -0
- package/dist/chunk-3FI44IOW.js +150 -0
- package/dist/chunk-3FI44IOW.js.map +1 -0
- package/dist/chunk-H7BRW5IY.js +308 -0
- package/dist/chunk-H7BRW5IY.js.map +1 -0
- package/dist/chunk-PDKAF4GX.js +707 -0
- package/dist/chunk-PDKAF4GX.js.map +1 -0
- package/dist/chunk-PTEX7F4R.js +309 -0
- package/dist/chunk-PTEX7F4R.js.map +1 -0
- package/dist/chunk-T5YZG6JW.js +511 -0
- package/dist/chunk-T5YZG6JW.js.map +1 -0
- package/dist/index.cjs +220 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -170
- package/dist/index.d.ts +57 -170
- package/dist/index.js +1 -324
- package/dist/index.js.map +1 -1
- package/dist/presets.cjs +1665 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +100 -0
- package/dist/presets.d.ts +100 -0
- package/dist/presets.js +405 -0
- package/dist/presets.js.map +1 -0
- package/dist/react.cjs +981 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +106 -0
- package/dist/react.d.ts +106 -0
- package/dist/react.js +5 -0
- package/dist/react.js.map +1 -0
- package/dist/server/express.cjs +179 -0
- package/dist/server/express.cjs.map +1 -0
- package/dist/server/express.d.cts +64 -0
- package/dist/server/express.d.ts +64 -0
- package/dist/server/express.js +111 -0
- package/dist/server/express.js.map +1 -0
- package/dist/server/next.cjs +165 -0
- package/dist/server/next.cjs.map +1 -0
- package/dist/server/next.d.cts +38 -0
- package/dist/server/next.d.ts +38 -0
- package/dist/server/next.js +107 -0
- package/dist/server/next.js.map +1 -0
- package/dist/server.cjs +550 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +19 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.js +80 -0
- package/dist/server.js.map +1 -0
- package/dist/storage-CYkSHWZX.d.cts +133 -0
- package/dist/storage-Qc9epG0G.d.ts +133 -0
- package/dist/types-BSlJJwti.d.cts +341 -0
- package/dist/types-BSlJJwti.d.ts +341 -0
- package/dist/ui.cjs +2325 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +331 -0
- package/dist/ui.d.ts +331 -0
- package/dist/ui.js +749 -0
- package/dist/ui.js.map +1 -0
- package/dist/useSlottedUploader-BzT5jV8c.d.cts +139 -0
- package/dist/useSlottedUploader-IkG5RhEO.d.ts +139 -0
- package/dist/useUploader-BiBdS-7y.d.cts +117 -0
- package/dist/useUploader-CQHpj_oI.d.ts +117 -0
- package/package.json +189 -3
- package/tailwind.css +80 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every user-facing string the package renders, so no copy is hardcoded
|
|
3
|
+
* inside a component and apps translate or reword without forking.
|
|
4
|
+
*
|
|
5
|
+
* English is the default so the kit ships globalized; `ES_LABELS` ships for
|
|
6
|
+
* Spanish apps, selected once through `UploaderProvider language='es'`. Hooks
|
|
7
|
+
* take `labels` in their options, components as a prop — always a `Partial`,
|
|
8
|
+
* merged over the provider's base.
|
|
9
|
+
*/
|
|
10
|
+
type UploaderLabels = {
|
|
11
|
+
/** Prompt inside the single dropzone of `Uploader` (fine pointers). */
|
|
12
|
+
dropPrompt: string;
|
|
13
|
+
/** Same zone on a touch device, where dragging barely exists. */
|
|
14
|
+
tapPrompt: string;
|
|
15
|
+
/** Prompt inside the bulk dropzone of `SlottedUploader` (fine pointers). */
|
|
16
|
+
bulkDropPrompt: string;
|
|
17
|
+
/** Same bulk zone on a touch device. */
|
|
18
|
+
bulkTapPrompt: string;
|
|
19
|
+
/** Button that fires the batch when `uploadOn` is `'manual'`. */
|
|
20
|
+
uploadAll: string;
|
|
21
|
+
/** Row action: preview a file. */
|
|
22
|
+
view: string;
|
|
23
|
+
/** Row action: forget a file. */
|
|
24
|
+
remove: string;
|
|
25
|
+
/** Row action: abort an in-flight upload. */
|
|
26
|
+
cancel: string;
|
|
27
|
+
/** Suffix on a row the server confirmed. */
|
|
28
|
+
ready: string;
|
|
29
|
+
/**
|
|
30
|
+
* Suffix on a row holding a picked file that has NOT been sent yet.
|
|
31
|
+
* Distinct from {@link UploaderLabels.ready}, which means the server
|
|
32
|
+
* confirmed it — two states, two strings.
|
|
33
|
+
*/
|
|
34
|
+
staged: string;
|
|
35
|
+
/** Slot action when the position is empty. */
|
|
36
|
+
upload: string;
|
|
37
|
+
/** Slot action when the position is filled. */
|
|
38
|
+
replace: string;
|
|
39
|
+
/** Pill shown while a file hovers over a filled slot row. */
|
|
40
|
+
dropToReplace: string;
|
|
41
|
+
/** Viewer: aria-label of the previous-image arrow. */
|
|
42
|
+
previous: string;
|
|
43
|
+
/** Viewer: aria-label of the next-image arrow. */
|
|
44
|
+
next: string;
|
|
45
|
+
/** Fallback when a strategy rejects without a message. */
|
|
46
|
+
uploadFailed: string;
|
|
47
|
+
/** The request never reached the server — offline, DNS, CORS preflight. */
|
|
48
|
+
uploadNetworkFailed: string;
|
|
49
|
+
/** Message when the DELETE transport refused or failed. */
|
|
50
|
+
removeFailed: string;
|
|
51
|
+
/** A stored file could not be fetched for preview or for bytes. */
|
|
52
|
+
readFailed: string;
|
|
53
|
+
/** A batch past `maxFiles`. Receives the cap. */
|
|
54
|
+
maxFilesReached: (max: number) => string;
|
|
55
|
+
/** A file picked for a slot whose extension the slot rejects. */
|
|
56
|
+
slotFormatNotAllowed: (slotLabel: string, extensions: string[]) => string;
|
|
57
|
+
/** A bulk-dropped file no slot matched. */
|
|
58
|
+
fileWithoutSlot: (fileName: string) => string;
|
|
59
|
+
/** Viewer: while the URL resolves. */
|
|
60
|
+
viewerLoading: string;
|
|
61
|
+
/** Viewer: the URL could not be resolved (expired signature, network). */
|
|
62
|
+
viewerError: string;
|
|
63
|
+
/** Second line of the error panel — what the reader can do about it. */
|
|
64
|
+
viewerErrorHint: string;
|
|
65
|
+
/** Viewer: button that retries the resolution. */
|
|
66
|
+
viewerRetry: string;
|
|
67
|
+
/** Viewer: open the file in a browser tab. */
|
|
68
|
+
openInTab: string;
|
|
69
|
+
/** Header action; the long `download` names a file, this labels a button. */
|
|
70
|
+
downloadShort: string;
|
|
71
|
+
/** Viewer: close button. */
|
|
72
|
+
close: string;
|
|
73
|
+
/** Viewer: formats without an inline preview. */
|
|
74
|
+
noPreview: string;
|
|
75
|
+
/** Viewer: download button. Receives the file name. */
|
|
76
|
+
download: (fileName: string) => string;
|
|
77
|
+
/** Viewer: fallback title when the file carries no name. */
|
|
78
|
+
filePreview: string;
|
|
79
|
+
/** Confirm dialog: affirmative button. */
|
|
80
|
+
confirm: string;
|
|
81
|
+
/** Confirm dialog heading before forgetting a file. */
|
|
82
|
+
confirmRemoveTitle: string;
|
|
83
|
+
/** Confirm dialog body before forgetting. Receives the file name. */
|
|
84
|
+
confirmRemoveMessage: (fileName: string) => string;
|
|
85
|
+
/** Confirm dialog heading before replacing a slot's file. */
|
|
86
|
+
confirmReplaceTitle: string;
|
|
87
|
+
/** Confirm dialog body before replacing. Receives current and incoming names. */
|
|
88
|
+
confirmReplaceMessage: (current: string, incoming: string) => string;
|
|
89
|
+
/** `DropAnywhereOverlay` heading while a drag is over the window. */
|
|
90
|
+
dropAnywhereTitle: string;
|
|
91
|
+
/** `DropAnywhereOverlay` second line. */
|
|
92
|
+
dropAnywhereHint: string;
|
|
93
|
+
/** `AvatarUploader`: hover label and aria-label of the picture. */
|
|
94
|
+
avatarChange: string;
|
|
95
|
+
/** `AvatarUploader`: overlay while a file hovers the picture. */
|
|
96
|
+
avatarDropHere: string;
|
|
97
|
+
/** `AvatarUploader`: the remove link under the picture. */
|
|
98
|
+
avatarRemove: string;
|
|
99
|
+
/** `GalleryUploader`: caption of the add tile. */
|
|
100
|
+
galleryAdd: string;
|
|
101
|
+
/** A name the extension check cannot read at all. */
|
|
102
|
+
fileHasNoExtension: string;
|
|
103
|
+
/** The extension is not on the scope's list. Receives that list. */
|
|
104
|
+
formatNotAllowed: (extensions: string[]) => string;
|
|
105
|
+
/** Zero bytes — a failed drag, a cancelled save. */
|
|
106
|
+
fileIsEmpty: string;
|
|
107
|
+
/** Over the scope ceiling. Receives both sizes already formatted. */
|
|
108
|
+
fileTooLarge: (size: string, max: string) => string;
|
|
109
|
+
/** Fewer bytes than the signature needs: a truncated transfer. */
|
|
110
|
+
fileIncomplete: string;
|
|
111
|
+
/** The leading bytes contradict the extension — renamed, or corrupt. */
|
|
112
|
+
contentDoesNotMatchExtension: string;
|
|
113
|
+
/** 400: a route parameter or body field the handler needs is missing. */
|
|
114
|
+
requestIncomplete: string;
|
|
115
|
+
/** 401: `getUser` answered null. */
|
|
116
|
+
unauthorized: string;
|
|
117
|
+
/** 400: an upload arrived with no file part. */
|
|
118
|
+
fileRequired: string;
|
|
119
|
+
/** 404: the URL named a scope the registry does not declare. */
|
|
120
|
+
unknownScope: string;
|
|
121
|
+
/** 500: the catch-all when a handler throws something unrecognized. */
|
|
122
|
+
processingFailed: string;
|
|
123
|
+
/** Download name for a key that ends in nothing usable. */
|
|
124
|
+
unnamedFile: string;
|
|
125
|
+
};
|
|
126
|
+
/** Spanish copy — select it once: `<UploaderProvider language='es'>`. */
|
|
127
|
+
declare const ES_LABELS: UploaderLabels;
|
|
128
|
+
/** English copy — the package default, so the kit ships globalized. */
|
|
129
|
+
declare const DEFAULT_LABELS: UploaderLabels;
|
|
130
|
+
/** The merge every entry point runs: partial overrides over the default. */
|
|
131
|
+
declare const resolveLabels: (labels?: Partial<UploaderLabels>, base?: UploaderLabels) => UploaderLabels;
|
|
132
|
+
/**
|
|
133
|
+
* @deprecated English IS {@link DEFAULT_LABELS} now. A shallow copy, kept so
|
|
134
|
+
* 1.x consumers passing it keep working.
|
|
135
|
+
*/
|
|
136
|
+
declare const EN_LABELS: UploaderLabels;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Every type in this file must be usable from a browser and from a server.
|
|
140
|
+
* That is what lets one scope definition drive the client's guards and the
|
|
141
|
+
* server's guards without the two drifting apart.
|
|
142
|
+
*/
|
|
143
|
+
type ImageExtension = 'png' | 'jpg' | 'jpeg' | 'svg' | 'webp' | 'gif' | 'ico' | 'heic';
|
|
144
|
+
type CertificateExtension = 'cer' | 'crt' | 'pem';
|
|
145
|
+
type KeyExtension = 'key';
|
|
146
|
+
type DocumentExtension = 'pdf' | 'doc' | 'docx' | 'xls' | 'xlsx' | 'csv' | 'txt';
|
|
147
|
+
type DataExtension = 'xml' | 'json';
|
|
148
|
+
type VideoExtension = 'mp4' | 'mov' | 'avi' | 'webm';
|
|
149
|
+
type AudioExtension = 'mp3' | 'wav' | 'ogg' | 'aac';
|
|
150
|
+
type FileExtension = ImageExtension | CertificateExtension | KeyExtension | DocumentExtension | DataExtension | VideoExtension | AudioExtension;
|
|
151
|
+
/** Validation preset. Picks extensions, size ceiling and header checking. */
|
|
152
|
+
type FileCategory = 'image' | 'certificate' | 'key' | 'pdf' | 'document' | 'data' | 'video' | 'audio' | 'any';
|
|
153
|
+
type FileCategoryConfig = {
|
|
154
|
+
extensions: FileExtension[];
|
|
155
|
+
maxBytes: number;
|
|
156
|
+
/** Read the leading bytes to catch an .exe renamed to .pdf. */
|
|
157
|
+
validateMagicNumbers: boolean;
|
|
158
|
+
/** Lower-case singular noun used in error messages. */
|
|
159
|
+
label: string;
|
|
160
|
+
/** Value for an `<input accept="…">`. */
|
|
161
|
+
accept: string;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* The subset of the DOM `File` this package needs. A browser `File`, a Node 18+
|
|
165
|
+
* `File` and a multer memory file (via `fromMulterFile`) all satisfy it, so
|
|
166
|
+
* `validateFile` is the same call on both sides of the wire.
|
|
167
|
+
*/
|
|
168
|
+
type FileLike = {
|
|
169
|
+
name: string;
|
|
170
|
+
size: number;
|
|
171
|
+
/** MIME type as reported by the source. Never trusted on its own. */
|
|
172
|
+
type: string;
|
|
173
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
174
|
+
};
|
|
175
|
+
type UploadStatus = 'idle' | 'validating' | 'uploading' | 'success' | 'error';
|
|
176
|
+
/** Machine-readable reason a file was rejected. Pair with `message` for UI. */
|
|
177
|
+
type ValidationCode = 'extension-not-allowed' | 'too-large' | 'empty-file' | 'magic-number-mismatch' | 'mime-not-allowed' | 'custom';
|
|
178
|
+
type ValidationResult = {
|
|
179
|
+
valid: true;
|
|
180
|
+
} | {
|
|
181
|
+
valid: false;
|
|
182
|
+
code: ValidationCode;
|
|
183
|
+
message: string;
|
|
184
|
+
};
|
|
185
|
+
type ValidationOptions = {
|
|
186
|
+
maxBytes?: number;
|
|
187
|
+
allowedExtensions?: FileExtension[];
|
|
188
|
+
validateMagicNumbers?: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* Copy for the messages these checks produce. Omitted means English — the
|
|
191
|
+
* same rule the rest of the package follows.
|
|
192
|
+
*/
|
|
193
|
+
labels?: Partial<UploaderLabels>;
|
|
194
|
+
/** Runs last, only when every built-in check passed. */
|
|
195
|
+
customValidation?: (file: FileLike) => Promise<ValidationResult>;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Public files are served straight from their URL; private files are only ever
|
|
199
|
+
* reachable through a signed, expiring URL minted by the server.
|
|
200
|
+
*/
|
|
201
|
+
type Visibility = 'public' | 'private';
|
|
202
|
+
/** Client-side image pipeline applied before upload. */
|
|
203
|
+
type CompressOptions = {
|
|
204
|
+
maxWidth?: number;
|
|
205
|
+
maxHeight?: number;
|
|
206
|
+
/** 0–1. Ignored by formats without lossy encoding. */
|
|
207
|
+
quality?: number;
|
|
208
|
+
/**
|
|
209
|
+
* Drop EXIF metadata. Defaults to true: camera photos carry GPS
|
|
210
|
+
* coordinates, and a public bucket is the wrong place for them.
|
|
211
|
+
*/
|
|
212
|
+
stripExif?: boolean;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* What an upload does to what the entity already had.
|
|
216
|
+
*
|
|
217
|
+
* - `'entity'` — after the put, every other object under the entity's prefix
|
|
218
|
+
* is deleted. The scope holds exactly one file and leaves no history.
|
|
219
|
+
* - `'key'` — only the object at the same key is replaced, siblings stay.
|
|
220
|
+
* - `false` — nothing is removed; the bucket keeps every version.
|
|
221
|
+
*/
|
|
222
|
+
type ReplaceMode = 'entity' | 'key' | false;
|
|
223
|
+
/**
|
|
224
|
+
* A named destination. The single source of truth for where a file lands, who
|
|
225
|
+
* may read it, and what is accepted there.
|
|
226
|
+
*/
|
|
227
|
+
type ScopeConfig = {
|
|
228
|
+
/**
|
|
229
|
+
* Storage key for an upload. Receives the owning entity and the incoming
|
|
230
|
+
* file so the caller controls collisions and folder shape.
|
|
231
|
+
*/
|
|
232
|
+
path: (entityId: string, file: FileLike) => string;
|
|
233
|
+
visibility: Visibility;
|
|
234
|
+
/** Extensions accepted here. Narrower than the category preset, never wider. */
|
|
235
|
+
accept: FileExtension[];
|
|
236
|
+
maxBytes: number;
|
|
237
|
+
category?: FileCategory;
|
|
238
|
+
/** Hand the bytes to the app's cipher before they leave the server. */
|
|
239
|
+
encrypt?: boolean;
|
|
240
|
+
compress?: CompressOptions;
|
|
241
|
+
/** Replace the object at the same key instead of adding a new one. */
|
|
242
|
+
overwrite?: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* How many files one entity may hold here — on the scope, not on the
|
|
245
|
+
* uploader, so the server knows the arity too.
|
|
246
|
+
*
|
|
247
|
+
* @defaultValue 1
|
|
248
|
+
* @see AGENTS.md §3 — "Arity lives on the scope"
|
|
249
|
+
*/
|
|
250
|
+
maxFiles?: number;
|
|
251
|
+
/**
|
|
252
|
+
* Overrides the derived replace mode. Rarely needed: the default already
|
|
253
|
+
* leaves no orphans.
|
|
254
|
+
*/
|
|
255
|
+
replace?: ReplaceMode;
|
|
256
|
+
/**
|
|
257
|
+
* Objects an `'entity'` replace may delete. Defaults to the folder of the
|
|
258
|
+
* resolved key. Set it when that folder is shared with another scope.
|
|
259
|
+
*/
|
|
260
|
+
prefix?: (entityId: string) => string;
|
|
261
|
+
/**
|
|
262
|
+
* This scope keeps its objects as HISTORY: the server refuses the DELETE
|
|
263
|
+
* route for it, so no client — buggy or malicious — can destroy the trail.
|
|
264
|
+
* The default is the opposite: removals are expected to delete from
|
|
265
|
+
* storage, because a scope with stable keys can never reclaim an orphan.
|
|
266
|
+
*/
|
|
267
|
+
keepOnRemove?: boolean;
|
|
268
|
+
/** Documentation-only today; drives lifecycle rules once adapters read it. */
|
|
269
|
+
retention?: string;
|
|
270
|
+
/** Free-form tags forwarded to the provider when it supports metadata. */
|
|
271
|
+
metadata?: Record<string, string>;
|
|
272
|
+
};
|
|
273
|
+
type ScopeRegistry<T extends Record<string, ScopeConfig>> = {
|
|
274
|
+
scopes: T;
|
|
275
|
+
names: (keyof T & string)[];
|
|
276
|
+
get(name: string): ScopeConfig;
|
|
277
|
+
has(name: string): boolean;
|
|
278
|
+
/** `<input accept>` string for a scope, derived from its extensions. */
|
|
279
|
+
accept(name: keyof T & string): string;
|
|
280
|
+
};
|
|
281
|
+
/** What the app persists after a successful upload. */
|
|
282
|
+
type StoredFile = {
|
|
283
|
+
/** Provider key. The handle for delete, re-sign and download. */
|
|
284
|
+
key: string;
|
|
285
|
+
/** Public URL, or a signed expiring URL when the scope is private. */
|
|
286
|
+
url: string;
|
|
287
|
+
scope: string;
|
|
288
|
+
entityId: string;
|
|
289
|
+
fileName: string;
|
|
290
|
+
mimeType: string;
|
|
291
|
+
size: number;
|
|
292
|
+
/** Enables dedupe and integrity checks when the provider reports it. */
|
|
293
|
+
checksum?: string;
|
|
294
|
+
uploadedAt: number;
|
|
295
|
+
uploadedBy?: string;
|
|
296
|
+
};
|
|
297
|
+
type PutInput = {
|
|
298
|
+
key: string;
|
|
299
|
+
body: Uint8Array;
|
|
300
|
+
contentType: string;
|
|
301
|
+
visibility: Visibility;
|
|
302
|
+
metadata?: Record<string, string>;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* What a provider can actually do. Declared rather than discovered so a scope
|
|
306
|
+
* that needs signed URLs fails at boot, not the first time a user opens a file.
|
|
307
|
+
*/
|
|
308
|
+
type ProviderCapabilities = {
|
|
309
|
+
signedUrl: boolean;
|
|
310
|
+
resumable: boolean;
|
|
311
|
+
rangeRead: boolean;
|
|
312
|
+
};
|
|
313
|
+
type SignedUrlOptions = {
|
|
314
|
+
/** Seconds until the URL stops working. */
|
|
315
|
+
expiresIn: number;
|
|
316
|
+
/** Force a download instead of inline rendering. */
|
|
317
|
+
download?: boolean;
|
|
318
|
+
};
|
|
319
|
+
type StorageProvider = {
|
|
320
|
+
name: string;
|
|
321
|
+
capabilities: ProviderCapabilities;
|
|
322
|
+
put(input: PutInput): Promise<{
|
|
323
|
+
key: string;
|
|
324
|
+
url: string;
|
|
325
|
+
checksum?: string;
|
|
326
|
+
}>;
|
|
327
|
+
get(key: string): Promise<Uint8Array>;
|
|
328
|
+
delete(key: string): Promise<boolean>;
|
|
329
|
+
signedUrl?(key: string, options: SignedUrlOptions): Promise<string>;
|
|
330
|
+
list(prefix: string): Promise<{
|
|
331
|
+
key: string;
|
|
332
|
+
size: number;
|
|
333
|
+
}[]>;
|
|
334
|
+
};
|
|
335
|
+
/** Injected cipher for `encrypt: true` scopes. The package never ships one. */
|
|
336
|
+
type CryptoHooks = {
|
|
337
|
+
encrypt: (data: Uint8Array) => Uint8Array | Promise<Uint8Array>;
|
|
338
|
+
decrypt: (data: Uint8Array) => Uint8Array | Promise<Uint8Array>;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export { type AudioExtension as A, type CertificateExtension as C, DEFAULT_LABELS as D, EN_LABELS as E, type FileCategory as F, type ImageExtension as I, type KeyExtension as K, type ProviderCapabilities as P, type ReplaceMode as R, type StorageProvider as S, type UploaderLabels as U, type Visibility as V, type FileCategoryConfig as a, type FileExtension as b, type FileLike as c, type ScopeConfig as d, type ScopeRegistry as e, type ValidationResult as f, type ValidationOptions as g, type CompressOptions as h, type CryptoHooks as i, type DataExtension as j, type DocumentExtension as k, ES_LABELS as l, type PutInput as m, type SignedUrlOptions as n, type StoredFile as o, type UploadStatus as p, type ValidationCode as q, type VideoExtension as r, resolveLabels as s };
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every user-facing string the package renders, so no copy is hardcoded
|
|
3
|
+
* inside a component and apps translate or reword without forking.
|
|
4
|
+
*
|
|
5
|
+
* English is the default so the kit ships globalized; `ES_LABELS` ships for
|
|
6
|
+
* Spanish apps, selected once through `UploaderProvider language='es'`. Hooks
|
|
7
|
+
* take `labels` in their options, components as a prop — always a `Partial`,
|
|
8
|
+
* merged over the provider's base.
|
|
9
|
+
*/
|
|
10
|
+
type UploaderLabels = {
|
|
11
|
+
/** Prompt inside the single dropzone of `Uploader` (fine pointers). */
|
|
12
|
+
dropPrompt: string;
|
|
13
|
+
/** Same zone on a touch device, where dragging barely exists. */
|
|
14
|
+
tapPrompt: string;
|
|
15
|
+
/** Prompt inside the bulk dropzone of `SlottedUploader` (fine pointers). */
|
|
16
|
+
bulkDropPrompt: string;
|
|
17
|
+
/** Same bulk zone on a touch device. */
|
|
18
|
+
bulkTapPrompt: string;
|
|
19
|
+
/** Button that fires the batch when `uploadOn` is `'manual'`. */
|
|
20
|
+
uploadAll: string;
|
|
21
|
+
/** Row action: preview a file. */
|
|
22
|
+
view: string;
|
|
23
|
+
/** Row action: forget a file. */
|
|
24
|
+
remove: string;
|
|
25
|
+
/** Row action: abort an in-flight upload. */
|
|
26
|
+
cancel: string;
|
|
27
|
+
/** Suffix on a row the server confirmed. */
|
|
28
|
+
ready: string;
|
|
29
|
+
/**
|
|
30
|
+
* Suffix on a row holding a picked file that has NOT been sent yet.
|
|
31
|
+
* Distinct from {@link UploaderLabels.ready}, which means the server
|
|
32
|
+
* confirmed it — two states, two strings.
|
|
33
|
+
*/
|
|
34
|
+
staged: string;
|
|
35
|
+
/** Slot action when the position is empty. */
|
|
36
|
+
upload: string;
|
|
37
|
+
/** Slot action when the position is filled. */
|
|
38
|
+
replace: string;
|
|
39
|
+
/** Pill shown while a file hovers over a filled slot row. */
|
|
40
|
+
dropToReplace: string;
|
|
41
|
+
/** Viewer: aria-label of the previous-image arrow. */
|
|
42
|
+
previous: string;
|
|
43
|
+
/** Viewer: aria-label of the next-image arrow. */
|
|
44
|
+
next: string;
|
|
45
|
+
/** Fallback when a strategy rejects without a message. */
|
|
46
|
+
uploadFailed: string;
|
|
47
|
+
/** The request never reached the server — offline, DNS, CORS preflight. */
|
|
48
|
+
uploadNetworkFailed: string;
|
|
49
|
+
/** Message when the DELETE transport refused or failed. */
|
|
50
|
+
removeFailed: string;
|
|
51
|
+
/** A stored file could not be fetched for preview or for bytes. */
|
|
52
|
+
readFailed: string;
|
|
53
|
+
/** A batch past `maxFiles`. Receives the cap. */
|
|
54
|
+
maxFilesReached: (max: number) => string;
|
|
55
|
+
/** A file picked for a slot whose extension the slot rejects. */
|
|
56
|
+
slotFormatNotAllowed: (slotLabel: string, extensions: string[]) => string;
|
|
57
|
+
/** A bulk-dropped file no slot matched. */
|
|
58
|
+
fileWithoutSlot: (fileName: string) => string;
|
|
59
|
+
/** Viewer: while the URL resolves. */
|
|
60
|
+
viewerLoading: string;
|
|
61
|
+
/** Viewer: the URL could not be resolved (expired signature, network). */
|
|
62
|
+
viewerError: string;
|
|
63
|
+
/** Second line of the error panel — what the reader can do about it. */
|
|
64
|
+
viewerErrorHint: string;
|
|
65
|
+
/** Viewer: button that retries the resolution. */
|
|
66
|
+
viewerRetry: string;
|
|
67
|
+
/** Viewer: open the file in a browser tab. */
|
|
68
|
+
openInTab: string;
|
|
69
|
+
/** Header action; the long `download` names a file, this labels a button. */
|
|
70
|
+
downloadShort: string;
|
|
71
|
+
/** Viewer: close button. */
|
|
72
|
+
close: string;
|
|
73
|
+
/** Viewer: formats without an inline preview. */
|
|
74
|
+
noPreview: string;
|
|
75
|
+
/** Viewer: download button. Receives the file name. */
|
|
76
|
+
download: (fileName: string) => string;
|
|
77
|
+
/** Viewer: fallback title when the file carries no name. */
|
|
78
|
+
filePreview: string;
|
|
79
|
+
/** Confirm dialog: affirmative button. */
|
|
80
|
+
confirm: string;
|
|
81
|
+
/** Confirm dialog heading before forgetting a file. */
|
|
82
|
+
confirmRemoveTitle: string;
|
|
83
|
+
/** Confirm dialog body before forgetting. Receives the file name. */
|
|
84
|
+
confirmRemoveMessage: (fileName: string) => string;
|
|
85
|
+
/** Confirm dialog heading before replacing a slot's file. */
|
|
86
|
+
confirmReplaceTitle: string;
|
|
87
|
+
/** Confirm dialog body before replacing. Receives current and incoming names. */
|
|
88
|
+
confirmReplaceMessage: (current: string, incoming: string) => string;
|
|
89
|
+
/** `DropAnywhereOverlay` heading while a drag is over the window. */
|
|
90
|
+
dropAnywhereTitle: string;
|
|
91
|
+
/** `DropAnywhereOverlay` second line. */
|
|
92
|
+
dropAnywhereHint: string;
|
|
93
|
+
/** `AvatarUploader`: hover label and aria-label of the picture. */
|
|
94
|
+
avatarChange: string;
|
|
95
|
+
/** `AvatarUploader`: overlay while a file hovers the picture. */
|
|
96
|
+
avatarDropHere: string;
|
|
97
|
+
/** `AvatarUploader`: the remove link under the picture. */
|
|
98
|
+
avatarRemove: string;
|
|
99
|
+
/** `GalleryUploader`: caption of the add tile. */
|
|
100
|
+
galleryAdd: string;
|
|
101
|
+
/** A name the extension check cannot read at all. */
|
|
102
|
+
fileHasNoExtension: string;
|
|
103
|
+
/** The extension is not on the scope's list. Receives that list. */
|
|
104
|
+
formatNotAllowed: (extensions: string[]) => string;
|
|
105
|
+
/** Zero bytes — a failed drag, a cancelled save. */
|
|
106
|
+
fileIsEmpty: string;
|
|
107
|
+
/** Over the scope ceiling. Receives both sizes already formatted. */
|
|
108
|
+
fileTooLarge: (size: string, max: string) => string;
|
|
109
|
+
/** Fewer bytes than the signature needs: a truncated transfer. */
|
|
110
|
+
fileIncomplete: string;
|
|
111
|
+
/** The leading bytes contradict the extension — renamed, or corrupt. */
|
|
112
|
+
contentDoesNotMatchExtension: string;
|
|
113
|
+
/** 400: a route parameter or body field the handler needs is missing. */
|
|
114
|
+
requestIncomplete: string;
|
|
115
|
+
/** 401: `getUser` answered null. */
|
|
116
|
+
unauthorized: string;
|
|
117
|
+
/** 400: an upload arrived with no file part. */
|
|
118
|
+
fileRequired: string;
|
|
119
|
+
/** 404: the URL named a scope the registry does not declare. */
|
|
120
|
+
unknownScope: string;
|
|
121
|
+
/** 500: the catch-all when a handler throws something unrecognized. */
|
|
122
|
+
processingFailed: string;
|
|
123
|
+
/** Download name for a key that ends in nothing usable. */
|
|
124
|
+
unnamedFile: string;
|
|
125
|
+
};
|
|
126
|
+
/** Spanish copy — select it once: `<UploaderProvider language='es'>`. */
|
|
127
|
+
declare const ES_LABELS: UploaderLabels;
|
|
128
|
+
/** English copy — the package default, so the kit ships globalized. */
|
|
129
|
+
declare const DEFAULT_LABELS: UploaderLabels;
|
|
130
|
+
/** The merge every entry point runs: partial overrides over the default. */
|
|
131
|
+
declare const resolveLabels: (labels?: Partial<UploaderLabels>, base?: UploaderLabels) => UploaderLabels;
|
|
132
|
+
/**
|
|
133
|
+
* @deprecated English IS {@link DEFAULT_LABELS} now. A shallow copy, kept so
|
|
134
|
+
* 1.x consumers passing it keep working.
|
|
135
|
+
*/
|
|
136
|
+
declare const EN_LABELS: UploaderLabels;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Every type in this file must be usable from a browser and from a server.
|
|
140
|
+
* That is what lets one scope definition drive the client's guards and the
|
|
141
|
+
* server's guards without the two drifting apart.
|
|
142
|
+
*/
|
|
143
|
+
type ImageExtension = 'png' | 'jpg' | 'jpeg' | 'svg' | 'webp' | 'gif' | 'ico' | 'heic';
|
|
144
|
+
type CertificateExtension = 'cer' | 'crt' | 'pem';
|
|
145
|
+
type KeyExtension = 'key';
|
|
146
|
+
type DocumentExtension = 'pdf' | 'doc' | 'docx' | 'xls' | 'xlsx' | 'csv' | 'txt';
|
|
147
|
+
type DataExtension = 'xml' | 'json';
|
|
148
|
+
type VideoExtension = 'mp4' | 'mov' | 'avi' | 'webm';
|
|
149
|
+
type AudioExtension = 'mp3' | 'wav' | 'ogg' | 'aac';
|
|
150
|
+
type FileExtension = ImageExtension | CertificateExtension | KeyExtension | DocumentExtension | DataExtension | VideoExtension | AudioExtension;
|
|
151
|
+
/** Validation preset. Picks extensions, size ceiling and header checking. */
|
|
152
|
+
type FileCategory = 'image' | 'certificate' | 'key' | 'pdf' | 'document' | 'data' | 'video' | 'audio' | 'any';
|
|
153
|
+
type FileCategoryConfig = {
|
|
154
|
+
extensions: FileExtension[];
|
|
155
|
+
maxBytes: number;
|
|
156
|
+
/** Read the leading bytes to catch an .exe renamed to .pdf. */
|
|
157
|
+
validateMagicNumbers: boolean;
|
|
158
|
+
/** Lower-case singular noun used in error messages. */
|
|
159
|
+
label: string;
|
|
160
|
+
/** Value for an `<input accept="…">`. */
|
|
161
|
+
accept: string;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* The subset of the DOM `File` this package needs. A browser `File`, a Node 18+
|
|
165
|
+
* `File` and a multer memory file (via `fromMulterFile`) all satisfy it, so
|
|
166
|
+
* `validateFile` is the same call on both sides of the wire.
|
|
167
|
+
*/
|
|
168
|
+
type FileLike = {
|
|
169
|
+
name: string;
|
|
170
|
+
size: number;
|
|
171
|
+
/** MIME type as reported by the source. Never trusted on its own. */
|
|
172
|
+
type: string;
|
|
173
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
174
|
+
};
|
|
175
|
+
type UploadStatus = 'idle' | 'validating' | 'uploading' | 'success' | 'error';
|
|
176
|
+
/** Machine-readable reason a file was rejected. Pair with `message` for UI. */
|
|
177
|
+
type ValidationCode = 'extension-not-allowed' | 'too-large' | 'empty-file' | 'magic-number-mismatch' | 'mime-not-allowed' | 'custom';
|
|
178
|
+
type ValidationResult = {
|
|
179
|
+
valid: true;
|
|
180
|
+
} | {
|
|
181
|
+
valid: false;
|
|
182
|
+
code: ValidationCode;
|
|
183
|
+
message: string;
|
|
184
|
+
};
|
|
185
|
+
type ValidationOptions = {
|
|
186
|
+
maxBytes?: number;
|
|
187
|
+
allowedExtensions?: FileExtension[];
|
|
188
|
+
validateMagicNumbers?: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* Copy for the messages these checks produce. Omitted means English — the
|
|
191
|
+
* same rule the rest of the package follows.
|
|
192
|
+
*/
|
|
193
|
+
labels?: Partial<UploaderLabels>;
|
|
194
|
+
/** Runs last, only when every built-in check passed. */
|
|
195
|
+
customValidation?: (file: FileLike) => Promise<ValidationResult>;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Public files are served straight from their URL; private files are only ever
|
|
199
|
+
* reachable through a signed, expiring URL minted by the server.
|
|
200
|
+
*/
|
|
201
|
+
type Visibility = 'public' | 'private';
|
|
202
|
+
/** Client-side image pipeline applied before upload. */
|
|
203
|
+
type CompressOptions = {
|
|
204
|
+
maxWidth?: number;
|
|
205
|
+
maxHeight?: number;
|
|
206
|
+
/** 0–1. Ignored by formats without lossy encoding. */
|
|
207
|
+
quality?: number;
|
|
208
|
+
/**
|
|
209
|
+
* Drop EXIF metadata. Defaults to true: camera photos carry GPS
|
|
210
|
+
* coordinates, and a public bucket is the wrong place for them.
|
|
211
|
+
*/
|
|
212
|
+
stripExif?: boolean;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* What an upload does to what the entity already had.
|
|
216
|
+
*
|
|
217
|
+
* - `'entity'` — after the put, every other object under the entity's prefix
|
|
218
|
+
* is deleted. The scope holds exactly one file and leaves no history.
|
|
219
|
+
* - `'key'` — only the object at the same key is replaced, siblings stay.
|
|
220
|
+
* - `false` — nothing is removed; the bucket keeps every version.
|
|
221
|
+
*/
|
|
222
|
+
type ReplaceMode = 'entity' | 'key' | false;
|
|
223
|
+
/**
|
|
224
|
+
* A named destination. The single source of truth for where a file lands, who
|
|
225
|
+
* may read it, and what is accepted there.
|
|
226
|
+
*/
|
|
227
|
+
type ScopeConfig = {
|
|
228
|
+
/**
|
|
229
|
+
* Storage key for an upload. Receives the owning entity and the incoming
|
|
230
|
+
* file so the caller controls collisions and folder shape.
|
|
231
|
+
*/
|
|
232
|
+
path: (entityId: string, file: FileLike) => string;
|
|
233
|
+
visibility: Visibility;
|
|
234
|
+
/** Extensions accepted here. Narrower than the category preset, never wider. */
|
|
235
|
+
accept: FileExtension[];
|
|
236
|
+
maxBytes: number;
|
|
237
|
+
category?: FileCategory;
|
|
238
|
+
/** Hand the bytes to the app's cipher before they leave the server. */
|
|
239
|
+
encrypt?: boolean;
|
|
240
|
+
compress?: CompressOptions;
|
|
241
|
+
/** Replace the object at the same key instead of adding a new one. */
|
|
242
|
+
overwrite?: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* How many files one entity may hold here — on the scope, not on the
|
|
245
|
+
* uploader, so the server knows the arity too.
|
|
246
|
+
*
|
|
247
|
+
* @defaultValue 1
|
|
248
|
+
* @see AGENTS.md §3 — "Arity lives on the scope"
|
|
249
|
+
*/
|
|
250
|
+
maxFiles?: number;
|
|
251
|
+
/**
|
|
252
|
+
* Overrides the derived replace mode. Rarely needed: the default already
|
|
253
|
+
* leaves no orphans.
|
|
254
|
+
*/
|
|
255
|
+
replace?: ReplaceMode;
|
|
256
|
+
/**
|
|
257
|
+
* Objects an `'entity'` replace may delete. Defaults to the folder of the
|
|
258
|
+
* resolved key. Set it when that folder is shared with another scope.
|
|
259
|
+
*/
|
|
260
|
+
prefix?: (entityId: string) => string;
|
|
261
|
+
/**
|
|
262
|
+
* This scope keeps its objects as HISTORY: the server refuses the DELETE
|
|
263
|
+
* route for it, so no client — buggy or malicious — can destroy the trail.
|
|
264
|
+
* The default is the opposite: removals are expected to delete from
|
|
265
|
+
* storage, because a scope with stable keys can never reclaim an orphan.
|
|
266
|
+
*/
|
|
267
|
+
keepOnRemove?: boolean;
|
|
268
|
+
/** Documentation-only today; drives lifecycle rules once adapters read it. */
|
|
269
|
+
retention?: string;
|
|
270
|
+
/** Free-form tags forwarded to the provider when it supports metadata. */
|
|
271
|
+
metadata?: Record<string, string>;
|
|
272
|
+
};
|
|
273
|
+
type ScopeRegistry<T extends Record<string, ScopeConfig>> = {
|
|
274
|
+
scopes: T;
|
|
275
|
+
names: (keyof T & string)[];
|
|
276
|
+
get(name: string): ScopeConfig;
|
|
277
|
+
has(name: string): boolean;
|
|
278
|
+
/** `<input accept>` string for a scope, derived from its extensions. */
|
|
279
|
+
accept(name: keyof T & string): string;
|
|
280
|
+
};
|
|
281
|
+
/** What the app persists after a successful upload. */
|
|
282
|
+
type StoredFile = {
|
|
283
|
+
/** Provider key. The handle for delete, re-sign and download. */
|
|
284
|
+
key: string;
|
|
285
|
+
/** Public URL, or a signed expiring URL when the scope is private. */
|
|
286
|
+
url: string;
|
|
287
|
+
scope: string;
|
|
288
|
+
entityId: string;
|
|
289
|
+
fileName: string;
|
|
290
|
+
mimeType: string;
|
|
291
|
+
size: number;
|
|
292
|
+
/** Enables dedupe and integrity checks when the provider reports it. */
|
|
293
|
+
checksum?: string;
|
|
294
|
+
uploadedAt: number;
|
|
295
|
+
uploadedBy?: string;
|
|
296
|
+
};
|
|
297
|
+
type PutInput = {
|
|
298
|
+
key: string;
|
|
299
|
+
body: Uint8Array;
|
|
300
|
+
contentType: string;
|
|
301
|
+
visibility: Visibility;
|
|
302
|
+
metadata?: Record<string, string>;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* What a provider can actually do. Declared rather than discovered so a scope
|
|
306
|
+
* that needs signed URLs fails at boot, not the first time a user opens a file.
|
|
307
|
+
*/
|
|
308
|
+
type ProviderCapabilities = {
|
|
309
|
+
signedUrl: boolean;
|
|
310
|
+
resumable: boolean;
|
|
311
|
+
rangeRead: boolean;
|
|
312
|
+
};
|
|
313
|
+
type SignedUrlOptions = {
|
|
314
|
+
/** Seconds until the URL stops working. */
|
|
315
|
+
expiresIn: number;
|
|
316
|
+
/** Force a download instead of inline rendering. */
|
|
317
|
+
download?: boolean;
|
|
318
|
+
};
|
|
319
|
+
type StorageProvider = {
|
|
320
|
+
name: string;
|
|
321
|
+
capabilities: ProviderCapabilities;
|
|
322
|
+
put(input: PutInput): Promise<{
|
|
323
|
+
key: string;
|
|
324
|
+
url: string;
|
|
325
|
+
checksum?: string;
|
|
326
|
+
}>;
|
|
327
|
+
get(key: string): Promise<Uint8Array>;
|
|
328
|
+
delete(key: string): Promise<boolean>;
|
|
329
|
+
signedUrl?(key: string, options: SignedUrlOptions): Promise<string>;
|
|
330
|
+
list(prefix: string): Promise<{
|
|
331
|
+
key: string;
|
|
332
|
+
size: number;
|
|
333
|
+
}[]>;
|
|
334
|
+
};
|
|
335
|
+
/** Injected cipher for `encrypt: true` scopes. The package never ships one. */
|
|
336
|
+
type CryptoHooks = {
|
|
337
|
+
encrypt: (data: Uint8Array) => Uint8Array | Promise<Uint8Array>;
|
|
338
|
+
decrypt: (data: Uint8Array) => Uint8Array | Promise<Uint8Array>;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export { type AudioExtension as A, type CertificateExtension as C, DEFAULT_LABELS as D, EN_LABELS as E, type FileCategory as F, type ImageExtension as I, type KeyExtension as K, type ProviderCapabilities as P, type ReplaceMode as R, type StorageProvider as S, type UploaderLabels as U, type Visibility as V, type FileCategoryConfig as a, type FileExtension as b, type FileLike as c, type ScopeConfig as d, type ScopeRegistry as e, type ValidationResult as f, type ValidationOptions as g, type CompressOptions as h, type CryptoHooks as i, type DataExtension as j, type DocumentExtension as k, ES_LABELS as l, type PutInput as m, type SignedUrlOptions as n, type StoredFile as o, type UploadStatus as p, type ValidationCode as q, type VideoExtension as r, resolveLabels as s };
|