@cedarjs/uploads 7.0.0-canary.3114
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/README.md +24 -0
- package/dist/authenticator.d.ts +38 -0
- package/dist/authenticator.d.ts.map +1 -0
- package/dist/authenticator.js +51 -0
- package/dist/cleanupStaleUploads.d.ts +50 -0
- package/dist/cleanupStaleUploads.d.ts.map +1 -0
- package/dist/cleanupStaleUploads.js +80 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/deleteFile.d.ts +14 -0
- package/dist/deleteFile.d.ts.map +1 -0
- package/dist/deleteFile.js +18 -0
- package/dist/directives.d.ts +55 -0
- package/dist/directives.d.ts.map +1 -0
- package/dist/directives.js +174 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/fastify/plugin.d.ts +49 -0
- package/dist/fastify/plugin.d.ts.map +1 -0
- package/dist/fastify/plugin.js +241 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/keys.d.ts +18 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +25 -0
- package/dist/pending.d.ts +39 -0
- package/dist/pending.d.ts.map +1 -0
- package/dist/pending.js +94 -0
- package/dist/profiles.d.ts +49 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +68 -0
- package/dist/providers/db.d.ts +9 -0
- package/dist/providers/db.d.ts.map +1 -0
- package/dist/providers/db.js +41 -0
- package/dist/providers/fs.d.ts +34 -0
- package/dist/providers/fs.d.ts.map +1 -0
- package/dist/providers/fs.js +125 -0
- package/dist/providers/s3.d.ts +25 -0
- package/dist/providers/s3.d.ts.map +1 -0
- package/dist/providers/s3.js +117 -0
- package/dist/s3.d.ts +3 -0
- package/dist/s3.d.ts.map +1 -0
- package/dist/s3.js +4 -0
- package/dist/serialize.d.ts +10 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +22 -0
- package/dist/serveToken.d.ts +36 -0
- package/dist/serveToken.d.ts.map +1 -0
- package/dist/serveToken.js +45 -0
- package/dist/services.d.ts +70 -0
- package/dist/services.d.ts.map +1 -0
- package/dist/services.js +141 -0
- package/dist/storeFile.d.ts +27 -0
- package/dist/storeFile.d.ts.map +1 -0
- package/dist/storeFile.js +53 -0
- package/dist/targets.d.ts +14 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +21 -0
- package/dist/types.d.ts +212 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/dist/uploadLoader.d.ts +11 -0
- package/dist/uploadLoader.d.ts.map +1 -0
- package/dist/uploadLoader.js +47 -0
- package/dist/uploadToken.d.ts +43 -0
- package/dist/uploadToken.d.ts.map +1 -0
- package/dist/uploadToken.js +61 -0
- package/dist/web/components/DbInput.d.ts +18 -0
- package/dist/web/components/DbInput.d.ts.map +1 -0
- package/dist/web/components/DbInput.js +46 -0
- package/dist/web/components/FsUploader.d.ts +6 -0
- package/dist/web/components/FsUploader.d.ts.map +1 -0
- package/dist/web/components/FsUploader.js +21 -0
- package/dist/web/components/S3Uploader.d.ts +6 -0
- package/dist/web/components/S3Uploader.d.ts.map +1 -0
- package/dist/web/components/S3Uploader.js +21 -0
- package/dist/web/components/UppyUploader.d.ts +23 -0
- package/dist/web/components/UppyUploader.d.ts.map +1 -0
- package/dist/web/components/UppyUploader.js +72 -0
- package/dist/web/createUppy.d.ts +62 -0
- package/dist/web/createUppy.d.ts.map +1 -0
- package/dist/web/createUppy.js +82 -0
- package/dist/web/graphql.d.ts +48 -0
- package/dist/web/graphql.d.ts.map +1 -0
- package/dist/web/graphql.js +36 -0
- package/dist/web/hooks/useDbUpload.d.ts +31 -0
- package/dist/web/hooks/useDbUpload.d.ts.map +1 -0
- package/dist/web/hooks/useDbUpload.js +80 -0
- package/dist/web/hooks/useFsUpload.d.ts +20 -0
- package/dist/web/hooks/useFsUpload.d.ts.map +1 -0
- package/dist/web/hooks/useFsUpload.js +58 -0
- package/dist/web/hooks/useS3Upload.d.ts +23 -0
- package/dist/web/hooks/useS3Upload.d.ts.map +1 -0
- package/dist/web/hooks/useS3Upload.js +99 -0
- package/dist/web/hooks/useUploadToken.d.ts +34 -0
- package/dist/web/hooks/useUploadToken.d.ts.map +1 -0
- package/dist/web/hooks/useUploadToken.js +60 -0
- package/dist/web/hooks/useUppyUpload.d.ts +23 -0
- package/dist/web/hooks/useUppyUpload.d.ts.map +1 -0
- package/dist/web/hooks/useUppyUpload.js +76 -0
- package/dist/web/index.d.ts +22 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +36 -0
- package/dist/webhooks/s3.d.ts +52 -0
- package/dist/webhooks/s3.d.ts.map +1 -0
- package/dist/webhooks/s3.js +122 -0
- package/dist/webhooks/sns.d.ts +34 -0
- package/dist/webhooks/sns.d.ts.map +1 -0
- package/dist/webhooks/sns.js +131 -0
- package/package.json +147 -0
package/dist/services.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { UploadError } from "./errors.js";
|
|
2
|
+
import { generateStorageKey } from "./keys.js";
|
|
3
|
+
import {
|
|
4
|
+
assertOwnership,
|
|
5
|
+
assertTokenAllowsFile,
|
|
6
|
+
createPendingUpload
|
|
7
|
+
} from "./pending.js";
|
|
8
|
+
import { resolveProfile } from "./profiles.js";
|
|
9
|
+
import { resolveTarget } from "./targets.js";
|
|
10
|
+
import { createUploadToken } from "./uploadToken.js";
|
|
11
|
+
function issueUploadToken({
|
|
12
|
+
profiles,
|
|
13
|
+
profile: profileName,
|
|
14
|
+
secret,
|
|
15
|
+
currentUser,
|
|
16
|
+
organizationId
|
|
17
|
+
}) {
|
|
18
|
+
if (!currentUser) {
|
|
19
|
+
throw new UploadError(
|
|
20
|
+
"UNAUTHENTICATED",
|
|
21
|
+
"You must be logged in to request an upload token."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const profile = resolveProfile(profiles, profileName);
|
|
25
|
+
const org = organizationId ?? currentUser.organizationId ?? void 0;
|
|
26
|
+
const token = createUploadToken({
|
|
27
|
+
payload: {
|
|
28
|
+
profile: profile.name,
|
|
29
|
+
allowedMimeTypes: profile.allowedMimeTypes,
|
|
30
|
+
maxFileSize: profile.maxFileSize,
|
|
31
|
+
maxFiles: profile.maxFiles,
|
|
32
|
+
target: profile.target,
|
|
33
|
+
sub: String(currentUser.id),
|
|
34
|
+
...org ? { organizationId: org } : {}
|
|
35
|
+
},
|
|
36
|
+
secret,
|
|
37
|
+
expiresIn: profile.expiresIn
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
token,
|
|
41
|
+
allowedMimeTypes: profile.allowedMimeTypes,
|
|
42
|
+
maxFileSize: BigInt(profile.maxFileSize),
|
|
43
|
+
maxFiles: profile.maxFiles
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function toBigInt(value, what) {
|
|
47
|
+
try {
|
|
48
|
+
return BigInt(value);
|
|
49
|
+
} catch {
|
|
50
|
+
throw new UploadError("FILE_TOO_LARGE", `Invalid ${what}: ${value}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function createPresignedUpload({
|
|
54
|
+
db,
|
|
55
|
+
targets,
|
|
56
|
+
tokenPayload,
|
|
57
|
+
input
|
|
58
|
+
}) {
|
|
59
|
+
const size = toBigInt(input.size, "file size");
|
|
60
|
+
assertTokenAllowsFile(tokenPayload, { contentType: input.contentType, size });
|
|
61
|
+
const target = resolveTarget(targets, tokenPayload.target);
|
|
62
|
+
const storageKey = generateStorageKey(input.contentType);
|
|
63
|
+
const upload = await createPendingUpload({
|
|
64
|
+
db,
|
|
65
|
+
payload: tokenPayload,
|
|
66
|
+
filename: input.filename,
|
|
67
|
+
mimeType: input.contentType,
|
|
68
|
+
size,
|
|
69
|
+
storageKey
|
|
70
|
+
});
|
|
71
|
+
let presigned;
|
|
72
|
+
try {
|
|
73
|
+
presigned = await target.getPresignedUploadUrl(storageKey, {
|
|
74
|
+
contentType: input.contentType,
|
|
75
|
+
size
|
|
76
|
+
});
|
|
77
|
+
} catch (e) {
|
|
78
|
+
await db.upload.delete({ where: { id: upload.id } }).catch(() => void 0);
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
return { uploadId: upload.id, ...presigned };
|
|
82
|
+
}
|
|
83
|
+
async function confirmUpload({
|
|
84
|
+
db,
|
|
85
|
+
targets,
|
|
86
|
+
uploadId,
|
|
87
|
+
currentUser
|
|
88
|
+
}) {
|
|
89
|
+
const upload = await db.upload.findFirst({
|
|
90
|
+
where: { id: uploadId },
|
|
91
|
+
omit: { data: true }
|
|
92
|
+
});
|
|
93
|
+
if (!upload) {
|
|
94
|
+
throw new UploadError("NOT_FOUND", "Upload not found.");
|
|
95
|
+
}
|
|
96
|
+
assertOwnership(upload, currentUser, "this upload");
|
|
97
|
+
if (upload.status !== "pending" || !upload.storageKey) {
|
|
98
|
+
throw new UploadError("NOT_PENDING", "Upload is not awaiting confirmation.");
|
|
99
|
+
}
|
|
100
|
+
const target = resolveTarget(targets, upload.target);
|
|
101
|
+
if (!await target.exists(upload.storageKey)) {
|
|
102
|
+
throw new UploadError("NOT_IN_STORAGE", "Upload not found in storage.");
|
|
103
|
+
}
|
|
104
|
+
const actualSize = await target.getObjectSize(upload.storageKey);
|
|
105
|
+
if (actualSize === null || BigInt(actualSize) !== upload.size) {
|
|
106
|
+
const { count: count2 } = await db.upload.updateMany({
|
|
107
|
+
where: { id: uploadId, status: "pending" },
|
|
108
|
+
data: { status: "failed" }
|
|
109
|
+
});
|
|
110
|
+
if (count2 === 1) {
|
|
111
|
+
await target.delete(upload.storageKey);
|
|
112
|
+
}
|
|
113
|
+
throw new UploadError(
|
|
114
|
+
"SIZE_MISMATCH",
|
|
115
|
+
`Uploaded object is ${actualSize ?? "of unknown"} bytes but ${upload.size} bytes were authorized.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const { count } = await db.upload.updateMany({
|
|
119
|
+
where: { id: uploadId, status: "pending" },
|
|
120
|
+
data: { status: "completed" }
|
|
121
|
+
});
|
|
122
|
+
const confirmed = await db.upload.findFirst({
|
|
123
|
+
where: { id: uploadId },
|
|
124
|
+
omit: { data: true }
|
|
125
|
+
});
|
|
126
|
+
if (!confirmed) {
|
|
127
|
+
throw new UploadError("NOT_FOUND", "Upload not found.");
|
|
128
|
+
}
|
|
129
|
+
if (count === 0 && confirmed.status !== "completed") {
|
|
130
|
+
throw new UploadError(
|
|
131
|
+
"NOT_PENDING",
|
|
132
|
+
"Upload was marked failed before it could be confirmed."
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return confirmed;
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
confirmUpload,
|
|
139
|
+
createPresignedUpload,
|
|
140
|
+
issueUploadToken
|
|
141
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { StorageProvider, UploadDatabase, UploadRecord } from './types.js';
|
|
2
|
+
export interface StoreFileOptions {
|
|
3
|
+
db: UploadDatabase;
|
|
4
|
+
filename: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
data: Uint8Array;
|
|
7
|
+
/**
|
|
8
|
+
* Reject `data` larger than this many bytes. Defaults to the 1 MB DB cap
|
|
9
|
+
* for `providerType: 'db'` targets and to unlimited for object storage.
|
|
10
|
+
*/
|
|
11
|
+
maxSize?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Owner of the file, when it belongs to a user. Persisted so the ownership
|
|
14
|
+
* checks that guard user uploads apply to server-stored files too.
|
|
15
|
+
*/
|
|
16
|
+
userId?: string;
|
|
17
|
+
/** Owning organization, for multi-tenant apps. */
|
|
18
|
+
organizationId?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Stores a file directly on a target and creates its `Upload` row. This is
|
|
22
|
+
* the path for server-generated files (PDFs, exports, thumbnails) and for
|
|
23
|
+
* files that arrive as base64 through GraphQL. It bypasses tokens, the
|
|
24
|
+
* upload routes, and Uppy entirely.
|
|
25
|
+
*/
|
|
26
|
+
export declare function storeFile(target: StorageProvider, options: StoreFileOptions): Promise<UploadRecord>;
|
|
27
|
+
//# sourceMappingURL=storeFile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storeFile.d.ts","sourceRoot":"","sources":["../src/storeFile.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAK/E,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,cAAc,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,UAAU,CAAA;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,YAAY,CAAC,CAyDvB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { UploadError } from "./errors.js";
|
|
2
|
+
import { generateStorageKey } from "./keys.js";
|
|
3
|
+
import { DB_MAX_FILE_SIZE } from "./profiles.js";
|
|
4
|
+
const MIME_TYPE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i;
|
|
5
|
+
async function storeFile(target, options) {
|
|
6
|
+
const { db, filename, mimeType, data, maxSize, userId, organizationId } = options;
|
|
7
|
+
if (!MIME_TYPE.test(mimeType)) {
|
|
8
|
+
throw new UploadError(
|
|
9
|
+
"MIME_TYPE_NOT_ALLOWED",
|
|
10
|
+
`'${mimeType}' is not a valid MIME type.`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
const isDbProvider = target.providerType === "db";
|
|
14
|
+
const cap = maxSize ?? (isDbProvider ? DB_MAX_FILE_SIZE : void 0);
|
|
15
|
+
if (cap !== void 0 && data.byteLength > cap) {
|
|
16
|
+
throw new UploadError(
|
|
17
|
+
"FILE_TOO_LARGE",
|
|
18
|
+
`File is ${data.byteLength} bytes, which exceeds the ${cap} byte limit for target '${target.name}'.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const key = isDbProvider ? null : generateStorageKey(mimeType);
|
|
22
|
+
if (key) {
|
|
23
|
+
await target.write(key, data, { contentType: mimeType });
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return await createRow();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (key) {
|
|
29
|
+
await target.delete(key).catch(() => void 0);
|
|
30
|
+
}
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
function createRow() {
|
|
34
|
+
return db.upload.create({
|
|
35
|
+
data: {
|
|
36
|
+
target: target.name,
|
|
37
|
+
status: "completed",
|
|
38
|
+
filename,
|
|
39
|
+
mimeType,
|
|
40
|
+
size: BigInt(data.byteLength),
|
|
41
|
+
storageKey: key,
|
|
42
|
+
// Copying gives Prisma the plain-ArrayBuffer-backed array its `Bytes`
|
|
43
|
+
// input requires, whatever `data` was backed by
|
|
44
|
+
data: isDbProvider ? new Uint8Array(data) : null,
|
|
45
|
+
userId: userId ?? null,
|
|
46
|
+
organizationId: organizationId ?? null
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
storeFile
|
|
53
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StorageProvider, StorageTargets } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Names each provider after its key so that `targets.avatars` works for direct
|
|
4
|
+
* access and `targets[upload.target]` works for lookup from a database row.
|
|
5
|
+
* Returns the same object, typed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function defineStorageTargets<T extends StorageTargets>(targets: T): T;
|
|
8
|
+
/**
|
|
9
|
+
* Looks a target up by name, throwing an `UploadError` with code
|
|
10
|
+
* `UNKNOWN_TARGET` that lists the configured names when it is missing. Use it
|
|
11
|
+
* when the name comes from an `Upload` row's `target` column.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveTarget(targets: StorageTargets, name: string): StorageProvider;
|
|
14
|
+
//# sourceMappingURL=targets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targets.d.ts","sourceRoot":"","sources":["../src/targets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEjE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,cAAc,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAM5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,GACX,eAAe,CAcjB"}
|
package/dist/targets.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { UploadError } from "./errors.js";
|
|
2
|
+
function defineStorageTargets(targets) {
|
|
3
|
+
for (const [name, provider] of Object.entries(targets)) {
|
|
4
|
+
provider.name = name;
|
|
5
|
+
}
|
|
6
|
+
return targets;
|
|
7
|
+
}
|
|
8
|
+
function resolveTarget(targets, name) {
|
|
9
|
+
const target = Object.hasOwn(targets, name) ? targets[name] : void 0;
|
|
10
|
+
if (!target) {
|
|
11
|
+
throw new UploadError(
|
|
12
|
+
"UNKNOWN_TARGET",
|
|
13
|
+
`Unknown storage target '${name}'. Available targets: ${Object.keys(targets).join(", ") || "(none)"}.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return target;
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
defineStorageTargets,
|
|
20
|
+
resolveTarget
|
|
21
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
/** Lifecycle state of an `Upload` row. */
|
|
3
|
+
export type UploadStatus = 'pending' | 'completed' | 'failed';
|
|
4
|
+
/**
|
|
5
|
+
* How a served file asks the browser to handle it. `attachment` downloads;
|
|
6
|
+
* `inline` renders in place. Serving defaults to `attachment` because the
|
|
7
|
+
* stored MIME type is client-asserted.
|
|
8
|
+
*/
|
|
9
|
+
export type ContentDisposition = 'attachment' | 'inline';
|
|
10
|
+
/**
|
|
11
|
+
* One row of the `Upload` table the setup command adds to `schema.prisma`.
|
|
12
|
+
* `data` is optional because most code paths deliberately leave the inline
|
|
13
|
+
* bytes out of the query.
|
|
14
|
+
*/
|
|
15
|
+
export interface UploadRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
target: string;
|
|
18
|
+
status: string;
|
|
19
|
+
filename: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
size: bigint;
|
|
22
|
+
storageKey: string | null;
|
|
23
|
+
data?: Uint8Array | null;
|
|
24
|
+
userId: string | null;
|
|
25
|
+
tokenId: string | null;
|
|
26
|
+
organizationId: string | null;
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
updatedAt: Date;
|
|
29
|
+
}
|
|
30
|
+
export interface UploadCreateData {
|
|
31
|
+
target: string;
|
|
32
|
+
status: UploadStatus;
|
|
33
|
+
filename: string;
|
|
34
|
+
mimeType: string;
|
|
35
|
+
size: bigint;
|
|
36
|
+
storageKey?: string | null;
|
|
37
|
+
data?: Uint8Array<ArrayBuffer> | null;
|
|
38
|
+
userId?: string | null;
|
|
39
|
+
tokenId?: string | null;
|
|
40
|
+
organizationId?: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface UploadWhere {
|
|
43
|
+
id?: string | {
|
|
44
|
+
in: string[];
|
|
45
|
+
};
|
|
46
|
+
target?: string;
|
|
47
|
+
status?: string | {
|
|
48
|
+
in: string[];
|
|
49
|
+
};
|
|
50
|
+
storageKey?: string | null | {
|
|
51
|
+
not: null;
|
|
52
|
+
};
|
|
53
|
+
tokenId?: string;
|
|
54
|
+
createdAt?: {
|
|
55
|
+
lt?: Date;
|
|
56
|
+
gte?: Date;
|
|
57
|
+
};
|
|
58
|
+
updatedAt?: {
|
|
59
|
+
lt?: Date;
|
|
60
|
+
gte?: Date;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export interface UploadUpdateData {
|
|
64
|
+
status?: UploadStatus;
|
|
65
|
+
size?: bigint;
|
|
66
|
+
storageKey?: string | null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The subset of Prisma's generated `Upload` delegate this package calls. The
|
|
70
|
+
* generated client satisfies it structurally, so apps pass their `db` as-is.
|
|
71
|
+
*/
|
|
72
|
+
export interface UploadDelegate {
|
|
73
|
+
create(args: {
|
|
74
|
+
data: UploadCreateData;
|
|
75
|
+
}): Promise<UploadRecord>;
|
|
76
|
+
findUnique(args: {
|
|
77
|
+
where: {
|
|
78
|
+
id: string;
|
|
79
|
+
};
|
|
80
|
+
}): Promise<UploadRecord | null>;
|
|
81
|
+
findUniqueOrThrow(args: {
|
|
82
|
+
where: {
|
|
83
|
+
id: string;
|
|
84
|
+
};
|
|
85
|
+
}): Promise<UploadRecord>;
|
|
86
|
+
findFirst(args: {
|
|
87
|
+
where: UploadWhere;
|
|
88
|
+
omit?: {
|
|
89
|
+
data?: boolean;
|
|
90
|
+
};
|
|
91
|
+
}): Promise<UploadRecord | null>;
|
|
92
|
+
findMany(args: {
|
|
93
|
+
where: UploadWhere;
|
|
94
|
+
omit?: {
|
|
95
|
+
data?: boolean;
|
|
96
|
+
};
|
|
97
|
+
orderBy?: {
|
|
98
|
+
createdAt: 'asc' | 'desc';
|
|
99
|
+
};
|
|
100
|
+
take?: number;
|
|
101
|
+
}): Promise<UploadRecord[]>;
|
|
102
|
+
updateMany(args: {
|
|
103
|
+
where: UploadWhere;
|
|
104
|
+
data: UploadUpdateData;
|
|
105
|
+
}): Promise<{
|
|
106
|
+
count: number;
|
|
107
|
+
}>;
|
|
108
|
+
delete(args: {
|
|
109
|
+
where: {
|
|
110
|
+
id: string;
|
|
111
|
+
};
|
|
112
|
+
}): Promise<UploadRecord>;
|
|
113
|
+
count(args: {
|
|
114
|
+
where: UploadWhere;
|
|
115
|
+
}): Promise<number>;
|
|
116
|
+
}
|
|
117
|
+
/** What a Prisma interactive transaction hands to its callback. */
|
|
118
|
+
export interface UploadTransactionClient {
|
|
119
|
+
upload: UploadDelegate;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The subset of a Prisma client this package needs: the `Upload` delegate and
|
|
123
|
+
* interactive transactions. Any generated client whose schema contains the
|
|
124
|
+
* `Upload` model satisfies it.
|
|
125
|
+
*/
|
|
126
|
+
export interface UploadDatabase extends UploadTransactionClient {
|
|
127
|
+
$transaction<T>(fn: (tx: UploadTransactionClient) => Promise<T>, options?: {
|
|
128
|
+
isolationLevel?: 'Serializable';
|
|
129
|
+
}): Promise<T>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The shape Uppy's S3 plugin (and any other direct-upload client) needs to
|
|
133
|
+
* PUT a file straight into object storage.
|
|
134
|
+
*/
|
|
135
|
+
export interface PresignedUploadUrl {
|
|
136
|
+
url: string;
|
|
137
|
+
method: string;
|
|
138
|
+
headers: Record<string, string>;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The contract every storage provider implements with its native SDK. It is
|
|
142
|
+
* the minimum the framework needs to write, read, serve, and delete files;
|
|
143
|
+
* provider-specific features stay on the provider's own client.
|
|
144
|
+
*/
|
|
145
|
+
export interface StorageProvider {
|
|
146
|
+
/** Name of this provider instance, assigned by `defineStorageTargets()`. */
|
|
147
|
+
name: string;
|
|
148
|
+
/**
|
|
149
|
+
* `'db'` marks the provider that stores bytes inline in the `Upload` row.
|
|
150
|
+
* Every other value (`'fs'`, `'s3'`, or a third-party string) is treated
|
|
151
|
+
* identically as object storage.
|
|
152
|
+
*/
|
|
153
|
+
providerType: 'db' | (string & {});
|
|
154
|
+
/** Write bytes under `key`. */
|
|
155
|
+
write(key: string, data: Uint8Array, opts: {
|
|
156
|
+
contentType: string;
|
|
157
|
+
}): Promise<void>;
|
|
158
|
+
/** Read the whole object into memory. */
|
|
159
|
+
read(key: string): Promise<Buffer>;
|
|
160
|
+
/**
|
|
161
|
+
* Stream the object. Optional; the serve route falls back to `read()` when
|
|
162
|
+
* a provider does not implement it.
|
|
163
|
+
*/
|
|
164
|
+
readStream?(key: string): Promise<Readable>;
|
|
165
|
+
/** Delete the object. A missing object is not an error. */
|
|
166
|
+
delete(key: string): Promise<void>;
|
|
167
|
+
/** Whether the object exists. */
|
|
168
|
+
exists(key: string): Promise<boolean>;
|
|
169
|
+
/**
|
|
170
|
+
* Size of the object in bytes, or `null` when the provider cannot know.
|
|
171
|
+
* Completion-time verification treats `null` as a failed check.
|
|
172
|
+
*/
|
|
173
|
+
getObjectSize(key: string): Promise<number | null>;
|
|
174
|
+
/**
|
|
175
|
+
* A time-limited URL for reading the object. Defaults to attachment
|
|
176
|
+
* disposition wherever the provider controls response headers.
|
|
177
|
+
*/
|
|
178
|
+
getSignedReadUrl(key: string, opts?: {
|
|
179
|
+
expiresIn?: number;
|
|
180
|
+
disposition?: ContentDisposition;
|
|
181
|
+
}): Promise<string>;
|
|
182
|
+
/**
|
|
183
|
+
* A presigned URL for direct client upload. Providers that cannot do this
|
|
184
|
+
* (FS, DB) throw an `UploadError` with code `PRESIGN_NOT_SUPPORTED`.
|
|
185
|
+
*/
|
|
186
|
+
getPresignedUploadUrl(key: string, opts: {
|
|
187
|
+
contentType: string;
|
|
188
|
+
size?: bigint;
|
|
189
|
+
expiresIn?: number;
|
|
190
|
+
}): Promise<PresignedUploadUrl>;
|
|
191
|
+
/**
|
|
192
|
+
* Provider-specific configuration for third-party integrations. S3 returns
|
|
193
|
+
* `{ bucket, region, keyPrefix }`, FS returns `{ uploadDir }`, DB `{}`.
|
|
194
|
+
*/
|
|
195
|
+
getConfig(): Record<string, unknown>;
|
|
196
|
+
}
|
|
197
|
+
export type StorageTargets = Record<string, StorageProvider>;
|
|
198
|
+
/** A serialized `Upload` row, safe to return from a route or resolver. */
|
|
199
|
+
export interface SerializedUpload {
|
|
200
|
+
id: string;
|
|
201
|
+
target: string;
|
|
202
|
+
status: string;
|
|
203
|
+
filename: string;
|
|
204
|
+
mimeType: string;
|
|
205
|
+
size: string;
|
|
206
|
+
storageKey: string | null;
|
|
207
|
+
userId: string | null;
|
|
208
|
+
organizationId: string | null;
|
|
209
|
+
createdAt: string;
|
|
210
|
+
updatedAt: string;
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { UploadDatabase, UploadRecord } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loads an `Upload` row by id, batching every call made during the same
|
|
4
|
+
* tick for the same `scope` into one query. A transformer directive runs
|
|
5
|
+
* once per field per row, so a list of a hundred rows would otherwise be a
|
|
6
|
+
* hundred queries. Scope on the GraphQL context so the batch and its cache
|
|
7
|
+
* live exactly one request. The batch query leaves out `data`, so inline
|
|
8
|
+
* blobs are never dragged into memory for object-storage rows.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadUpload(scope: object, db: UploadDatabase, id: string): Promise<UploadRecord | null>;
|
|
11
|
+
//# sourceMappingURL=uploadLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadLoader.d.ts","sourceRoot":"","sources":["../src/uploadLoader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AA4C9D;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,cAAc,EAClB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA4B9B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const loaders = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
function flush(loader, db) {
|
|
3
|
+
loader.scheduled = false;
|
|
4
|
+
const batch = loader.queue;
|
|
5
|
+
loader.queue = /* @__PURE__ */ new Map();
|
|
6
|
+
const ids = [...batch.keys()];
|
|
7
|
+
db.upload.findMany({ where: { id: { in: ids } }, omit: { data: true } }).then((rows) => {
|
|
8
|
+
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
9
|
+
for (const [id, waiters] of batch) {
|
|
10
|
+
const row = byId.get(id) ?? null;
|
|
11
|
+
for (const waiter of waiters) {
|
|
12
|
+
waiter.resolve(row);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}).catch((e) => {
|
|
16
|
+
for (const waiters of batch.values()) {
|
|
17
|
+
for (const waiter of waiters) {
|
|
18
|
+
waiter.reject(e);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function loadUpload(scope, db, id) {
|
|
24
|
+
let loader = loaders.get(scope);
|
|
25
|
+
if (!loader) {
|
|
26
|
+
loader = { cache: /* @__PURE__ */ new Map(), queue: /* @__PURE__ */ new Map(), scheduled: false };
|
|
27
|
+
loaders.set(scope, loader);
|
|
28
|
+
}
|
|
29
|
+
const cached = loader.cache.get(id);
|
|
30
|
+
if (cached) {
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
const promise = new Promise((resolve, reject) => {
|
|
34
|
+
const waiters = loader.queue.get(id) ?? [];
|
|
35
|
+
waiters.push({ resolve, reject });
|
|
36
|
+
loader.queue.set(id, waiters);
|
|
37
|
+
});
|
|
38
|
+
loader.cache.set(id, promise);
|
|
39
|
+
if (!loader.scheduled) {
|
|
40
|
+
loader.scheduled = true;
|
|
41
|
+
queueMicrotask(() => flush(loader, db));
|
|
42
|
+
}
|
|
43
|
+
return promise;
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
loadUpload
|
|
47
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SignedTokenExpiresIn } from '@cedarjs/api';
|
|
2
|
+
/** Purpose claim that separates upload tokens from every other signed token. */
|
|
3
|
+
export declare const UPLOAD_TOKEN_PURPOSE = "cedar-upload";
|
|
4
|
+
export { UPLOAD_TOKEN_HEADER } from './constants.js';
|
|
5
|
+
/**
|
|
6
|
+
* The claims inside an upload token. Constraints come from the server-side
|
|
7
|
+
* profile; `sub` binds the token to the user it was issued to and `jti`
|
|
8
|
+
* lets the server count how many files one token has created.
|
|
9
|
+
*/
|
|
10
|
+
export interface UploadTokenPayload {
|
|
11
|
+
/** The profile the token was issued for. */
|
|
12
|
+
profile: string;
|
|
13
|
+
allowedMimeTypes: string[];
|
|
14
|
+
maxFileSize: number;
|
|
15
|
+
maxFiles: number;
|
|
16
|
+
/** The storage target files created with this token land on. */
|
|
17
|
+
target: string;
|
|
18
|
+
/** Id of the user the token was issued to. */
|
|
19
|
+
sub: string;
|
|
20
|
+
/** Unique token id, stamped onto each `Upload` row the token creates. */
|
|
21
|
+
jti: string;
|
|
22
|
+
/** The organization the token was issued under, in multi-tenant apps. */
|
|
23
|
+
organizationId?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CreateUploadTokenOptions {
|
|
26
|
+
payload: Omit<UploadTokenPayload, 'jti'> & {
|
|
27
|
+
jti?: string;
|
|
28
|
+
};
|
|
29
|
+
secret: string;
|
|
30
|
+
expiresIn?: SignedTokenExpiresIn;
|
|
31
|
+
}
|
|
32
|
+
/** Signs an upload token. A fresh `jti` is generated unless one is passed. */
|
|
33
|
+
export declare function createUploadToken({ payload, secret, expiresIn, }: CreateUploadTokenOptions): string;
|
|
34
|
+
/**
|
|
35
|
+
* Verifies an upload token and returns its claims. Throws an `UploadError`
|
|
36
|
+
* with code `INVALID_TOKEN` for a missing, expired, tampered, or foreign
|
|
37
|
+
* token. The token argument accepts `null` and `undefined` so a header value
|
|
38
|
+
* can be passed straight in.
|
|
39
|
+
*/
|
|
40
|
+
export declare function verifyUploadToken(token: string | null | undefined, { secret }: {
|
|
41
|
+
secret: string;
|
|
42
|
+
}): UploadTokenPayload;
|
|
43
|
+
//# sourceMappingURL=uploadToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadToken.d.ts","sourceRoot":"","sources":["../src/uploadToken.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAKxD,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,iBAAiB,CAAA;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAEpD;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAA;IACX,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAA;IACX,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,CAAC,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3D,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,MAAM,EACN,SAA2C,GAC5C,EAAE,wBAAwB,GAAG,MAAM,CAOnC;AAyBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B,kBAAkB,CA2BpB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
createSignedToken,
|
|
4
|
+
SignedTokenError,
|
|
5
|
+
verifySignedToken
|
|
6
|
+
} from "@cedarjs/api";
|
|
7
|
+
import { UploadError } from "./errors.js";
|
|
8
|
+
import { DEFAULT_UPLOAD_TOKEN_EXPIRES_IN } from "./profiles.js";
|
|
9
|
+
const UPLOAD_TOKEN_PURPOSE = "cedar-upload";
|
|
10
|
+
import { UPLOAD_TOKEN_HEADER } from "./constants.js";
|
|
11
|
+
function createUploadToken({
|
|
12
|
+
payload,
|
|
13
|
+
secret,
|
|
14
|
+
expiresIn = DEFAULT_UPLOAD_TOKEN_EXPIRES_IN
|
|
15
|
+
}) {
|
|
16
|
+
return createSignedToken({
|
|
17
|
+
payload: { ...payload, jti: payload.jti ?? randomUUID() },
|
|
18
|
+
purpose: UPLOAD_TOKEN_PURPOSE,
|
|
19
|
+
expiresIn,
|
|
20
|
+
secret
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function isStringArray(value) {
|
|
24
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
25
|
+
}
|
|
26
|
+
function isUploadTokenPayload(value) {
|
|
27
|
+
if (typeof value !== "object" || value === null) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const p = value;
|
|
31
|
+
return typeof p.profile === "string" && isStringArray(p.allowedMimeTypes) && typeof p.maxFileSize === "number" && typeof p.maxFiles === "number" && typeof p.target === "string" && typeof p.sub === "string" && typeof p.jti === "string" && (p.organizationId === void 0 || typeof p.organizationId === "string");
|
|
32
|
+
}
|
|
33
|
+
function verifyUploadToken(token, { secret }) {
|
|
34
|
+
let payload;
|
|
35
|
+
try {
|
|
36
|
+
payload = verifySignedToken(token, {
|
|
37
|
+
purpose: UPLOAD_TOKEN_PURPOSE,
|
|
38
|
+
secret
|
|
39
|
+
});
|
|
40
|
+
} catch (e) {
|
|
41
|
+
if (e instanceof SignedTokenError && e.code === "MISSING_SECRET") {
|
|
42
|
+
throw new UploadError("CONFIGURATION", e.message, e);
|
|
43
|
+
}
|
|
44
|
+
const detail = e instanceof SignedTokenError ? e.code : "INVALID";
|
|
45
|
+
throw new UploadError(
|
|
46
|
+
"INVALID_TOKEN",
|
|
47
|
+
`Invalid upload token (${detail}).`,
|
|
48
|
+
e
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (!isUploadTokenPayload(payload)) {
|
|
52
|
+
throw new UploadError("INVALID_TOKEN", "Invalid upload token (MALFORMED).");
|
|
53
|
+
}
|
|
54
|
+
return payload;
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
UPLOAD_TOKEN_HEADER,
|
|
58
|
+
UPLOAD_TOKEN_PURPOSE,
|
|
59
|
+
createUploadToken,
|
|
60
|
+
verifyUploadToken
|
|
61
|
+
};
|