@cedarjs/uploads 8.0.0-canary.3418 → 8.0.0-canary.3421
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 +6 -0
- package/dist/cleanupStaleUploads.d.ts +4 -3
- package/dist/cleanupStaleUploads.d.ts.map +1 -1
- package/dist/cleanupStaleUploads.js +8 -2
- package/dist/deleteFile.d.ts +19 -4
- package/dist/deleteFile.d.ts.map +1 -1
- package/dist/deleteFile.js +49 -8
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/variants.d.ts +207 -0
- package/dist/variants.d.ts.map +1 -0
- package/dist/variants.js +498 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -14,6 +14,12 @@ File uploads and storage for CedarJS apps.
|
|
|
14
14
|
uploads, and confirming them.
|
|
15
15
|
- **Lifecycle utilities** (`storeFile`, `deleteFile`, `cleanupStaleUploads`) for
|
|
16
16
|
server-generated files and explicit cleanup.
|
|
17
|
+
- **Derived files (variants)**, experimental: thumbnails, previews, a video's
|
|
18
|
+
poster frame. `defineUploadVariants()` takes one generator function per
|
|
19
|
+
variant name and keeps the bookkeeping: variants are `Upload` rows linked
|
|
20
|
+
to their source, made once on first read even under concurrency, failures
|
|
21
|
+
remembered with a retry-after, `regenerateVariants()` to remake one name,
|
|
22
|
+
and deletion with the source.
|
|
17
23
|
- **Content detection** (`detectContentType`, `acceptedContentType`) that
|
|
18
24
|
checks what an uploaded file really is from its bytes, not from its name or
|
|
19
25
|
the type the browser claimed: images, PDF, audio/video, and OpenDocument and
|
|
@@ -3,9 +3,10 @@ export interface CleanupStaleUploadsOptions {
|
|
|
3
3
|
db: UploadDatabase;
|
|
4
4
|
targets: StorageTargets;
|
|
5
5
|
/**
|
|
6
|
-
* Rows
|
|
7
|
-
* `failed`.
|
|
8
|
-
*
|
|
6
|
+
* Rows that have been `pending` and unchanged for this many milliseconds
|
|
7
|
+
* are claimed as `failed`. Measured from the row's last update, so a
|
|
8
|
+
* variant whose retry claimed an old row gets the full time. Defaults to
|
|
9
|
+
* one hour, well past the five-minute validity of a presigned upload URL.
|
|
9
10
|
*/
|
|
10
11
|
olderThan?: number;
|
|
11
12
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanupStaleUploads.d.ts","sourceRoot":"","sources":["../src/cleanupStaleUploads.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAK9E,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,cAAc,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB
|
|
1
|
+
{"version":3,"file":"cleanupStaleUploads.d.ts","sourceRoot":"","sources":["../src/cleanupStaleUploads.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAK9E,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,cAAc,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;CACzD;AAED,MAAM,WAAW,yBAAyB;IACxC,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAA;IACf,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,EAAE,EACF,OAAO,EACP,SAAoB,EACpB,WAAqB,EACrB,SAAe,EACf,OAC0E,GAC3E,EAAE,0BAA0B,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAyFjE"}
|
|
@@ -10,6 +10,7 @@ async function cleanupStaleUploads({
|
|
|
10
10
|
onError = (error, upload) => console.error(`[cedar uploads] cleanup failed for ${upload.id}:`, error)
|
|
11
11
|
}) {
|
|
12
12
|
const now = Date.now();
|
|
13
|
+
const staleBefore = new Date(now - olderThan);
|
|
13
14
|
let claimed = 0;
|
|
14
15
|
let deleted = 0;
|
|
15
16
|
let errors = 0;
|
|
@@ -36,14 +37,19 @@ async function cleanupStaleUploads({
|
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
39
|
const stale = await db.upload.findMany({
|
|
39
|
-
where: { status: "pending",
|
|
40
|
+
where: { status: "pending", updatedAt: { lt: staleBefore } },
|
|
40
41
|
omit: { data: true },
|
|
41
42
|
orderBy: { createdAt: "asc" },
|
|
42
43
|
take: batchSize
|
|
43
44
|
});
|
|
44
45
|
for (const upload of stale) {
|
|
45
46
|
const { count } = await db.upload.updateMany({
|
|
46
|
-
|
|
47
|
+
// A row claimed again since it was read is not stale any more
|
|
48
|
+
where: {
|
|
49
|
+
id: upload.id,
|
|
50
|
+
status: "pending",
|
|
51
|
+
updatedAt: { lt: staleBefore }
|
|
52
|
+
},
|
|
47
53
|
data: { status: "failed" }
|
|
48
54
|
});
|
|
49
55
|
if (count !== 1) {
|
package/dist/deleteFile.d.ts
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
-
import type { StorageProvider, UploadDatabase, UploadRecord } from './types.js';
|
|
2
|
-
export
|
|
1
|
+
import type { StorageProvider, StorageTargets, UploadDatabase, UploadRecord, UploadVariantDatabase } from './types.js';
|
|
2
|
+
export type DeleteFileOptions = {
|
|
3
3
|
db: UploadDatabase;
|
|
4
4
|
upload: Pick<UploadRecord, 'id' | 'storageKey'>;
|
|
5
|
-
|
|
5
|
+
targets?: undefined;
|
|
6
|
+
} | {
|
|
7
|
+
db: UploadDatabase & UploadVariantDatabase;
|
|
8
|
+
upload: Pick<UploadRecord, 'id' | 'storageKey'>;
|
|
9
|
+
/**
|
|
10
|
+
* Every configured storage target. When given, the upload's derived
|
|
11
|
+
* files (variants: thumbnails, previews) are deleted first, from
|
|
12
|
+
* whichever targets hold them. Required for an upload that has
|
|
13
|
+
* variants.
|
|
14
|
+
*/
|
|
15
|
+
targets: StorageTargets;
|
|
16
|
+
};
|
|
6
17
|
/**
|
|
7
18
|
* Deletes a stored file: bytes first, then the `Upload` row. Bytes-first
|
|
8
19
|
* ordering means a crash between the two steps leaves a row whose object is
|
|
9
20
|
* gone, which the app can see and fix by calling again, whereas the reverse
|
|
10
21
|
* order would leave an unreachable object nothing references. A missing
|
|
11
22
|
* object or row is tolerated, so the call is idempotent.
|
|
23
|
+
*
|
|
24
|
+
* Pass `targets` to delete the upload's derived files (variants) with it.
|
|
25
|
+
* Without it, deleting an upload that has variants throws an `UploadError`
|
|
26
|
+
* with code `HAS_VARIANTS`, because the variant rows reference their source.
|
|
12
27
|
*/
|
|
13
|
-
export declare function deleteFile(target: StorageProvider,
|
|
28
|
+
export declare function deleteFile(target: StorageProvider, options: DeleteFileOptions): Promise<void>;
|
|
14
29
|
//# sourceMappingURL=deleteFile.d.ts.map
|
package/dist/deleteFile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deleteFile.d.ts","sourceRoot":"","sources":["../src/deleteFile.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deleteFile.d.ts","sourceRoot":"","sources":["../src/deleteFile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,cAAc,EACd,YAAY,EACZ,qBAAqB,EACtB,MAAM,YAAY,CAAA;AAGnB,MAAM,MAAM,iBAAiB,GACzB;IACE,EAAE,EAAE,cAAc,CAAA;IAClB,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,YAAY,CAAC,CAAA;IAC/C,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,GACD;IACE,EAAE,EAAE,cAAc,GAAG,qBAAqB,CAAA;IAC1C,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,YAAY,CAAC,CAAA;IAC/C;;;;;OAKG;IACH,OAAO,EAAE,cAAc,CAAA;CACxB,CAAA;AA6BL;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA+Cf"}
|
package/dist/deleteFile.js
CHANGED
|
@@ -1,15 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { UploadError } from "./errors.js";
|
|
2
|
+
import { deleteVariants } from "./variants.js";
|
|
3
|
+
const MAX_VARIANT_ROUNDS = 3;
|
|
4
|
+
function isPrismaError(e, code) {
|
|
5
|
+
return typeof e === "object" && e !== null && "code" in e && e.code === code;
|
|
3
6
|
}
|
|
4
|
-
|
|
7
|
+
function hasVariantRows(db, id) {
|
|
8
|
+
return db.upload.count({ where: { sourceId: id } }).then(
|
|
9
|
+
(count) => count > 0,
|
|
10
|
+
() => false
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function hasVariantsError(id, cause) {
|
|
14
|
+
return new UploadError(
|
|
15
|
+
"HAS_VARIANTS",
|
|
16
|
+
`Upload '${id}' has derived files (variants), so its row cannot be deleted on its own. Pass \`targets\` to deleteFile() to delete the variants with it, or call deleteVariants() first.`,
|
|
17
|
+
cause
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
async function deleteFile(target, options) {
|
|
21
|
+
const { upload } = options;
|
|
22
|
+
if (!options.targets && await hasVariantRows(options.db, upload.id)) {
|
|
23
|
+
throw hasVariantsError(upload.id);
|
|
24
|
+
}
|
|
5
25
|
if (upload.storageKey) {
|
|
6
26
|
await target.delete(upload.storageKey);
|
|
7
27
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
for (let round = 1; ; round++) {
|
|
29
|
+
if (options.targets) {
|
|
30
|
+
await deleteVariants(upload, {
|
|
31
|
+
db: options.db,
|
|
32
|
+
targets: options.targets
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await options.db.upload.delete({ where: { id: upload.id } });
|
|
37
|
+
return;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (isPrismaError(e, "P2025")) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!isPrismaError(e, "P2003")) {
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
if (!options.targets) {
|
|
46
|
+
if (!await hasVariantRows(options.db, upload.id)) {
|
|
47
|
+
throw e;
|
|
48
|
+
}
|
|
49
|
+
throw hasVariantsError(upload.id, e);
|
|
50
|
+
}
|
|
51
|
+
if (round >= MAX_VARIANT_ROUNDS) {
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
13
54
|
}
|
|
14
55
|
}
|
|
15
56
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type UploadErrorCode = 'CONFIGURATION' | 'UNKNOWN_TARGET' | 'UNKNOWN_PROFILE' | 'UNAUTHENTICATED' | 'INVALID_TOKEN' | 'TOKEN_USER_MISMATCH' | 'TOKEN_ORGANIZATION_MISMATCH' | 'MIME_TYPE_NOT_ALLOWED' | 'FILE_TOO_LARGE' | 'TOO_MANY_FILES' | 'PRESIGN_NOT_SUPPORTED' | 'NOT_SUPPORTED' | 'NOT_FOUND' | 'FORBIDDEN' | 'NOT_PENDING' | 'NOT_IN_STORAGE' | 'SIZE_MISMATCH' | 'INVALID_KEY';
|
|
1
|
+
export type UploadErrorCode = 'CONFIGURATION' | 'UNKNOWN_TARGET' | 'UNKNOWN_PROFILE' | 'UNAUTHENTICATED' | 'INVALID_TOKEN' | 'TOKEN_USER_MISMATCH' | 'TOKEN_ORGANIZATION_MISMATCH' | 'MIME_TYPE_NOT_ALLOWED' | 'FILE_TOO_LARGE' | 'TOO_MANY_FILES' | 'PRESIGN_NOT_SUPPORTED' | 'NOT_SUPPORTED' | 'NOT_FOUND' | 'FORBIDDEN' | 'NOT_PENDING' | 'NOT_IN_STORAGE' | 'SIZE_MISMATCH' | 'INVALID_KEY' | 'HAS_VARIANTS';
|
|
2
2
|
/**
|
|
3
3
|
* Thrown by every part of the uploads package when a request cannot be
|
|
4
4
|
* honored. `code` is stable for programmatic handling and `statusCode` is the
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,6BAA6B,GAC7B,uBAAuB,GACvB,gBAAgB,GAChB,gBAAgB,GAChB,uBAAuB,GACvB,eAAe,GACf,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,eAAe,GACf,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,6BAA6B,GAC7B,uBAAuB,GACvB,gBAAgB,GAChB,gBAAgB,GAChB,uBAAuB,GACvB,eAAe,GACf,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,eAAe,GACf,aAAa,GACb,cAAc,CAAA;AAwBlB;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,IAAI,EAAE,eAAe,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;gBAEN,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAMpE"}
|
package/dist/errors.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export { storeFile } from './storeFile.js';
|
|
|
15
15
|
export type { StoreFileOptions } from './storeFile.js';
|
|
16
16
|
export { deleteFile } from './deleteFile.js';
|
|
17
17
|
export type { DeleteFileOptions } from './deleteFile.js';
|
|
18
|
+
export { defineUploadVariants, deleteVariants } from './variants.js';
|
|
19
|
+
export type { DefineUploadVariantsOptions, RegenerateVariantsResult, UploadVariantContext, UploadVariantDefinition, UploadVariantFile, UploadVariantGenerator, UploadVariants, UploadVariantSource, } from './variants.js';
|
|
18
20
|
export { cleanupStaleUploads } from './cleanupStaleUploads.js';
|
|
19
21
|
export type { CleanupStaleUploadsOptions, CleanupStaleUploadsResult, } from './cleanupStaleUploads.js';
|
|
20
22
|
export { createUploadToken, UPLOAD_TOKEN_HEADER, UPLOAD_TOKEN_PURPOSE, verifyUploadToken, } from './uploadToken.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import { createDbProvider } from "./providers/db.js";
|
|
|
31
31
|
import { generateStorageKey } from "./keys.js";
|
|
32
32
|
import { storeFile } from "./storeFile.js";
|
|
33
33
|
import { deleteFile } from "./deleteFile.js";
|
|
34
|
+
import { defineUploadVariants, deleteVariants } from "./variants.js";
|
|
34
35
|
import { cleanupStaleUploads } from "./cleanupStaleUploads.js";
|
|
35
36
|
import {
|
|
36
37
|
createUploadToken,
|
|
@@ -94,7 +95,9 @@ export {
|
|
|
94
95
|
createWithSignedUrlDirective,
|
|
95
96
|
defineStorageTargets,
|
|
96
97
|
defineUploadProfiles,
|
|
98
|
+
defineUploadVariants,
|
|
97
99
|
deleteFile,
|
|
100
|
+
deleteVariants,
|
|
98
101
|
detectContentType,
|
|
99
102
|
findZipCentralDirectory,
|
|
100
103
|
generateStorageKey,
|
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ export interface UploadWhere {
|
|
|
43
43
|
id?: string | {
|
|
44
44
|
in: string[];
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Only exists when the `Upload` model has the variant columns. A query
|
|
48
|
+
* that uses it must handle the error Prisma raises without them.
|
|
49
|
+
*/
|
|
50
|
+
sourceId?: string;
|
|
46
51
|
target?: string;
|
|
47
52
|
status?: string | {
|
|
48
53
|
in: string[];
|
|
@@ -219,4 +224,105 @@ export interface SerializedUpload {
|
|
|
219
224
|
createdAt: string;
|
|
220
225
|
updatedAt: string;
|
|
221
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* An `Upload` row in a schema that has the two variant columns, `sourceId`
|
|
229
|
+
* and `variant`. A derived file (a thumbnail, a preview, a video's poster
|
|
230
|
+
* frame) is an `Upload` row of its own whose `sourceId` points at the file
|
|
231
|
+
* it was made from and whose `variant` names what it is (`'thumb-164'`,
|
|
232
|
+
* `'preview'`). Original files have both set to `null`.
|
|
233
|
+
*/
|
|
234
|
+
export interface UploadVariantRecord extends UploadRecord {
|
|
235
|
+
sourceId: string | null;
|
|
236
|
+
variant: string | null;
|
|
237
|
+
}
|
|
238
|
+
export interface UploadVariantWhere {
|
|
239
|
+
id?: string | {
|
|
240
|
+
in?: string[];
|
|
241
|
+
notIn?: string[];
|
|
242
|
+
};
|
|
243
|
+
sourceId?: string | null | {
|
|
244
|
+
in: string[];
|
|
245
|
+
};
|
|
246
|
+
variant?: string | null;
|
|
247
|
+
status?: string;
|
|
248
|
+
tokenId?: string | null;
|
|
249
|
+
storageKey?: string | null;
|
|
250
|
+
createdAt?: {
|
|
251
|
+
lt?: Date;
|
|
252
|
+
lte?: Date;
|
|
253
|
+
};
|
|
254
|
+
updatedAt?: {
|
|
255
|
+
lt?: Date;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export interface UploadVariantCreateData extends UploadCreateData {
|
|
259
|
+
sourceId: string;
|
|
260
|
+
variant: string;
|
|
261
|
+
}
|
|
262
|
+
export interface UploadVariantUpdateData {
|
|
263
|
+
status?: UploadStatus;
|
|
264
|
+
target?: string;
|
|
265
|
+
size?: bigint;
|
|
266
|
+
storageKey?: string | null;
|
|
267
|
+
filename?: string;
|
|
268
|
+
mimeType?: string;
|
|
269
|
+
data?: Uint8Array<ArrayBuffer> | null;
|
|
270
|
+
tokenId?: string | null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The subset of Prisma's generated `Upload` delegate that derived files
|
|
274
|
+
* (variants) need. A generated client satisfies it once the `Upload` model
|
|
275
|
+
* has the `sourceId` and `variant` columns described in the uploads docs.
|
|
276
|
+
*/
|
|
277
|
+
export interface UploadVariantDelegate {
|
|
278
|
+
create(args: {
|
|
279
|
+
data: UploadVariantCreateData;
|
|
280
|
+
}): Promise<UploadVariantRecord>;
|
|
281
|
+
findUnique(args: {
|
|
282
|
+
where: {
|
|
283
|
+
id: string;
|
|
284
|
+
};
|
|
285
|
+
omit?: {
|
|
286
|
+
data?: boolean;
|
|
287
|
+
};
|
|
288
|
+
}): Promise<UploadVariantRecord | null>;
|
|
289
|
+
findFirst(args: {
|
|
290
|
+
where: UploadVariantWhere;
|
|
291
|
+
omit?: {
|
|
292
|
+
data?: boolean;
|
|
293
|
+
};
|
|
294
|
+
orderBy?: {
|
|
295
|
+
createdAt: 'asc' | 'desc';
|
|
296
|
+
};
|
|
297
|
+
}): Promise<UploadVariantRecord | null>;
|
|
298
|
+
findMany(args: {
|
|
299
|
+
where: UploadVariantWhere;
|
|
300
|
+
omit?: {
|
|
301
|
+
data?: boolean;
|
|
302
|
+
};
|
|
303
|
+
orderBy?: {
|
|
304
|
+
createdAt: 'asc' | 'desc';
|
|
305
|
+
};
|
|
306
|
+
take?: number;
|
|
307
|
+
}): Promise<UploadVariantRecord[]>;
|
|
308
|
+
updateMany(args: {
|
|
309
|
+
where: UploadVariantWhere;
|
|
310
|
+
data: UploadVariantUpdateData;
|
|
311
|
+
}): Promise<{
|
|
312
|
+
count: number;
|
|
313
|
+
}>;
|
|
314
|
+
deleteMany(args: {
|
|
315
|
+
where: UploadVariantWhere;
|
|
316
|
+
}): Promise<{
|
|
317
|
+
count: number;
|
|
318
|
+
}>;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The subset of a Prisma client that derived files (variants) need. Any
|
|
322
|
+
* generated client whose `Upload` model has the `sourceId` and `variant`
|
|
323
|
+
* columns satisfies it.
|
|
324
|
+
*/
|
|
325
|
+
export interface UploadVariantDatabase {
|
|
326
|
+
upload: UploadVariantDelegate;
|
|
327
|
+
}
|
|
222
328
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { ContentDisposition, StorageProvider, StorageTargets, UploadVariantDatabase, UploadVariantRecord } from './types.js';
|
|
2
|
+
/** The file a variant generator returns. */
|
|
3
|
+
export interface UploadVariantFile {
|
|
4
|
+
/** The derived file's bytes. */
|
|
5
|
+
data: Uint8Array;
|
|
6
|
+
/** MIME type of `data`, for example `image/webp`. */
|
|
7
|
+
mimeType: string;
|
|
8
|
+
/**
|
|
9
|
+
* Filename for the derived file's `Upload` row. Defaults to the source's
|
|
10
|
+
* filename without its extension, plus the variant name and the extension
|
|
11
|
+
* of `mimeType`: `photo.jpg` becomes `photo-thumb-164.webp`.
|
|
12
|
+
*/
|
|
13
|
+
filename?: string;
|
|
14
|
+
}
|
|
15
|
+
/** What a variant generator is called with. */
|
|
16
|
+
export interface UploadVariantContext {
|
|
17
|
+
/** The source file's `Upload` row, without its inline `data`. */
|
|
18
|
+
source: UploadVariantRecord;
|
|
19
|
+
/** Name of the variant being made, for example `'thumb-164'`. */
|
|
20
|
+
variant: string;
|
|
21
|
+
/** Reads the source file's bytes from whichever target holds them. */
|
|
22
|
+
readSource(): Promise<Buffer>;
|
|
23
|
+
/**
|
|
24
|
+
* Reads another variant of the same source, making it first if it does
|
|
25
|
+
* not exist yet. Returns `null` when that variant could not be made. This
|
|
26
|
+
* is how one derived file is made from another, for example a PDF's
|
|
27
|
+
* thumbnail cut from its drawn first page (`readVariant('preview')`).
|
|
28
|
+
*/
|
|
29
|
+
readVariant(name: string): Promise<Buffer | null>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Makes one derived file (variant) from its source: a thumbnail from a
|
|
33
|
+
* photo, a preview image from a PDF, a poster frame from a video. This is
|
|
34
|
+
* the only part the app writes; the uploads package stores the result,
|
|
35
|
+
* links it to its source, and keeps track of it.
|
|
36
|
+
*
|
|
37
|
+
* Return `null` when this source cannot have this variant (a file type the
|
|
38
|
+
* generator does not handle). Both `null` and a thrown error are remembered
|
|
39
|
+
* as a failure and not retried until `retryFailedAfter` has passed.
|
|
40
|
+
*/
|
|
41
|
+
export type UploadVariantGenerator = (context: UploadVariantContext) => Promise<UploadVariantFile | null> | UploadVariantFile | null;
|
|
42
|
+
/** A variant generator plus where its files are stored. */
|
|
43
|
+
export interface UploadVariantDefinition {
|
|
44
|
+
generate: UploadVariantGenerator;
|
|
45
|
+
/** Storage target for this variant's files. Defaults to the `target` option. */
|
|
46
|
+
target?: StorageProvider;
|
|
47
|
+
}
|
|
48
|
+
export interface DefineUploadVariantsOptions<Name extends string> {
|
|
49
|
+
db: UploadVariantDatabase;
|
|
50
|
+
/**
|
|
51
|
+
* Every configured storage target. Used to read sources and to read and
|
|
52
|
+
* delete variants wherever they are stored.
|
|
53
|
+
*/
|
|
54
|
+
targets: StorageTargets;
|
|
55
|
+
/** Storage target new variant files are written to, unless a variant names its own. */
|
|
56
|
+
target: StorageProvider;
|
|
57
|
+
/**
|
|
58
|
+
* The variants, by name, each with the function that makes it:
|
|
59
|
+
* `{ 'thumb-164': makeThumb, preview: makePdfPreview }`. Names may use
|
|
60
|
+
* letters, digits, `.`, `_` and `-`.
|
|
61
|
+
*/
|
|
62
|
+
variants: Record<Name, UploadVariantGenerator | UploadVariantDefinition>;
|
|
63
|
+
/**
|
|
64
|
+
* How long a variant that could not be made is left alone before the
|
|
65
|
+
* next request tries again, in milliseconds. Defaults to one day.
|
|
66
|
+
*/
|
|
67
|
+
retryFailedAfter?: number;
|
|
68
|
+
/**
|
|
69
|
+
* How long a claim to make a variant is honored, in milliseconds. A
|
|
70
|
+
* request that finds a claim older than this assumes its maker died and
|
|
71
|
+
* makes the variant itself. Defaults to ten minutes; set it above the
|
|
72
|
+
* slowest generator's running time.
|
|
73
|
+
*/
|
|
74
|
+
claimTimeout?: number;
|
|
75
|
+
/**
|
|
76
|
+
* How often a request waiting for another process to finish making the
|
|
77
|
+
* same variant checks again, in milliseconds. Defaults to 250.
|
|
78
|
+
*/
|
|
79
|
+
pollInterval?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Called when a generator throws or its result cannot be stored. Defaults
|
|
82
|
+
* to `console.error`. The failure is remembered either way.
|
|
83
|
+
*/
|
|
84
|
+
onError?: (error: unknown, info: {
|
|
85
|
+
sourceId: string;
|
|
86
|
+
variant: string;
|
|
87
|
+
}) => void;
|
|
88
|
+
}
|
|
89
|
+
/** What `regenerateVariants()` did. */
|
|
90
|
+
export interface RegenerateVariantsResult {
|
|
91
|
+
/** Variant files deleted, made or failed alike. */
|
|
92
|
+
deleted: number;
|
|
93
|
+
/** Variants made again. */
|
|
94
|
+
made: number;
|
|
95
|
+
/** Variants that could not be made again (remembered as failed). */
|
|
96
|
+
failed: number;
|
|
97
|
+
/** Variants that could not be deleted; reported through `onError`. */
|
|
98
|
+
errors: number;
|
|
99
|
+
}
|
|
100
|
+
/** A source upload, by id or by its row. */
|
|
101
|
+
export type UploadVariantSource = string | {
|
|
102
|
+
id: string;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Derived files (variants) of uploads: thumbnails, previews, poster frames.
|
|
106
|
+
* Returned by `defineUploadVariants()`.
|
|
107
|
+
*/
|
|
108
|
+
export interface UploadVariants<Name extends string> {
|
|
109
|
+
/** The configured variant names. */
|
|
110
|
+
names: readonly Name[];
|
|
111
|
+
/**
|
|
112
|
+
* The variant's `Upload` row, made now if it does not exist yet. Returns
|
|
113
|
+
* `null` when it could not be made and `retryFailedAfter` has not passed.
|
|
114
|
+
*
|
|
115
|
+
* Making a variant happens once: concurrent calls in this process share
|
|
116
|
+
* one attempt, and across processes a claim row decides who makes it
|
|
117
|
+
* while the others wait for the result.
|
|
118
|
+
*/
|
|
119
|
+
getVariant(source: UploadVariantSource, name: Name): Promise<UploadVariantRecord | null>;
|
|
120
|
+
/**
|
|
121
|
+
* The variant's `Upload` row if it has been made, or `null`. Never makes
|
|
122
|
+
* anything; use it where making on demand would be too slow, such as a
|
|
123
|
+
* list page.
|
|
124
|
+
*/
|
|
125
|
+
findVariant(source: UploadVariantSource, name: Name): Promise<UploadVariantRecord | null>;
|
|
126
|
+
/**
|
|
127
|
+
* The made variants of many sources in one query, keyed by source id.
|
|
128
|
+
* Sources without that variant are left out. Never makes anything.
|
|
129
|
+
*/
|
|
130
|
+
findVariants(sourceIds: string[], name: Name): Promise<Map<string, UploadVariantRecord>>;
|
|
131
|
+
/**
|
|
132
|
+
* The variant's row and bytes, made now if it does not exist yet, or
|
|
133
|
+
* `null` when it could not be made.
|
|
134
|
+
*/
|
|
135
|
+
readVariant(source: UploadVariantSource, name: Name): Promise<{
|
|
136
|
+
upload: UploadVariantRecord;
|
|
137
|
+
data: Buffer;
|
|
138
|
+
} | null>;
|
|
139
|
+
/**
|
|
140
|
+
* A URL a browser can load the variant from, made now if it does not
|
|
141
|
+
* exist yet: a signed URL for object storage, a `data:` URI for database
|
|
142
|
+
* targets. `null` when the variant could not be made.
|
|
143
|
+
*/
|
|
144
|
+
getVariantUrl(source: UploadVariantSource, name: Name, options?: {
|
|
145
|
+
expiresIn?: number;
|
|
146
|
+
disposition?: ContentDisposition;
|
|
147
|
+
}): Promise<string | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Remakes every variant called `name`, for every source that has one
|
|
150
|
+
* (made or failed), and leaves other variants alone. Use it after fixing
|
|
151
|
+
* a generator. Each old file is deleted, then the variant is made again
|
|
152
|
+
* unless `remake` is `false`, in which case it is made on its next read.
|
|
153
|
+
*/
|
|
154
|
+
regenerateVariants(name: Name, options?: {
|
|
155
|
+
remake?: boolean;
|
|
156
|
+
batchSize?: number;
|
|
157
|
+
}): Promise<RegenerateVariantsResult>;
|
|
158
|
+
/**
|
|
159
|
+
* Deletes all variants of a source, bytes first, then rows. `deleteFile()`
|
|
160
|
+
* does this for you when given `targets`.
|
|
161
|
+
*/
|
|
162
|
+
deleteVariants(source: UploadVariantSource): Promise<number>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Deletes every derived file (variant) of `source`: bytes first, then rows,
|
|
166
|
+
* like `deleteFile()`. Returns how many were deleted. `deleteFile()` calls
|
|
167
|
+
* this when given `targets`, so a source never goes without its variants.
|
|
168
|
+
*/
|
|
169
|
+
export declare function deleteVariants(source: UploadVariantSource, { db, targets }: {
|
|
170
|
+
db: UploadVariantDatabase;
|
|
171
|
+
targets: StorageTargets;
|
|
172
|
+
}): Promise<number>;
|
|
173
|
+
/**
|
|
174
|
+
* Declares the derived files (variants) uploads can have, such as
|
|
175
|
+
* thumbnails, previews or a video's poster frame, each with the function
|
|
176
|
+
* that makes it. **Experimental.**
|
|
177
|
+
*
|
|
178
|
+
* The app supplies only the generators. The uploads package keeps the
|
|
179
|
+
* bookkeeping: each variant is an `Upload` row linked to its source by
|
|
180
|
+
* `sourceId` and named by `variant`; it is made on first read, once even
|
|
181
|
+
* when several requests or processes ask at the same moment; a variant
|
|
182
|
+
* that cannot be made is remembered and tried again after
|
|
183
|
+
* `retryFailedAfter`; `regenerateVariants()` remakes every variant of one
|
|
184
|
+
* name; and `deleteFile()` with `targets` deletes a source's variants with
|
|
185
|
+
* it.
|
|
186
|
+
*
|
|
187
|
+
* The package does not make images or read videos itself. A generator is
|
|
188
|
+
* any function from source bytes to new bytes, using sharp, pdf.js, ffmpeg
|
|
189
|
+
* or a third-party service as the app sees fit.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* export const uploadVariants = defineUploadVariants({
|
|
193
|
+
* db,
|
|
194
|
+
* targets,
|
|
195
|
+
* target: targets.thumbnails,
|
|
196
|
+
* variants: {
|
|
197
|
+
* 'thumb-164': async ({ readSource }) => ({
|
|
198
|
+
* data: await sharp(await readSource()).resize(164, 164).webp().toBuffer(),
|
|
199
|
+
* mimeType: 'image/webp',
|
|
200
|
+
* }),
|
|
201
|
+
* },
|
|
202
|
+
* })
|
|
203
|
+
*
|
|
204
|
+
* const thumb = await uploadVariants.getVariant(photo.uploadId, 'thumb-164')
|
|
205
|
+
*/
|
|
206
|
+
export declare function defineUploadVariants<Name extends string>(options: DefineUploadVariantsOptions<Name>): UploadVariants<Name>;
|
|
207
|
+
//# sourceMappingURL=variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variants.d.ts","sourceRoot":"","sources":["../src/variants.ts"],"names":[],"mappings":"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"}
|
package/dist/variants.js
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
|
+
import mime from "mime-types";
|
|
4
|
+
import { UploadError } from "./errors.js";
|
|
5
|
+
import { generateStorageKey } from "./keys.js";
|
|
6
|
+
import { DB_MAX_FILE_SIZE } from "./profiles.js";
|
|
7
|
+
import { toDataUri } from "./serialize.js";
|
|
8
|
+
import { resolveTarget } from "./targets.js";
|
|
9
|
+
const ONE_DAY = 24 * 60 * 60 * 1e3;
|
|
10
|
+
const TEN_MINUTES = 10 * 60 * 1e3;
|
|
11
|
+
const MIME_TYPE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i;
|
|
12
|
+
const VARIANT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
13
|
+
function idOf(source) {
|
|
14
|
+
return typeof source === "string" ? source : source.id;
|
|
15
|
+
}
|
|
16
|
+
function isPrismaError(e, code) {
|
|
17
|
+
return typeof e === "object" && e !== null && "code" in e && e.code === code;
|
|
18
|
+
}
|
|
19
|
+
function defaultFilename(sourceFilename, name, mimeType) {
|
|
20
|
+
const base = sourceFilename.replace(/\.[^./\\]*$/, "") || "file";
|
|
21
|
+
const ext = mimeType ? mime.extension(mimeType.split(";")[0].trim()) : false;
|
|
22
|
+
return ext ? `${base}-${name}.${ext}` : `${base}-${name}`;
|
|
23
|
+
}
|
|
24
|
+
async function readBytes(db, targets, upload) {
|
|
25
|
+
const target = resolveTarget(targets, upload.target);
|
|
26
|
+
if (target.providerType === "db") {
|
|
27
|
+
const row = await db.upload.findUnique({ where: { id: upload.id } });
|
|
28
|
+
if (!row?.data) {
|
|
29
|
+
throw new UploadError(
|
|
30
|
+
"NOT_FOUND",
|
|
31
|
+
`Upload '${upload.id}' has no inline data.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return Buffer.from(row.data);
|
|
35
|
+
}
|
|
36
|
+
if (!upload.storageKey) {
|
|
37
|
+
throw new UploadError(
|
|
38
|
+
"NOT_FOUND",
|
|
39
|
+
`Upload '${upload.id}' has no stored file.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return Buffer.from(await target.read(upload.storageKey));
|
|
43
|
+
}
|
|
44
|
+
async function deleteVariantRow(db, targets, row) {
|
|
45
|
+
let current = row;
|
|
46
|
+
while (current) {
|
|
47
|
+
if (current.storageKey) {
|
|
48
|
+
await resolveTarget(targets, current.target).delete(current.storageKey);
|
|
49
|
+
}
|
|
50
|
+
const { count } = await db.upload.deleteMany({
|
|
51
|
+
where: {
|
|
52
|
+
id: current.id,
|
|
53
|
+
status: current.status,
|
|
54
|
+
tokenId: current.tokenId,
|
|
55
|
+
storageKey: current.storageKey
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (count === 1) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
current = await db.upload.findUnique({
|
|
62
|
+
where: { id: current.id },
|
|
63
|
+
omit: { data: true }
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function deleteVariants(source, { db, targets }) {
|
|
68
|
+
const rows = await db.upload.findMany({
|
|
69
|
+
where: { sourceId: idOf(source) },
|
|
70
|
+
omit: { data: true }
|
|
71
|
+
});
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
await deleteVariantRow(db, targets, row);
|
|
74
|
+
}
|
|
75
|
+
return rows.length;
|
|
76
|
+
}
|
|
77
|
+
function defineUploadVariants(options) {
|
|
78
|
+
const {
|
|
79
|
+
db,
|
|
80
|
+
targets,
|
|
81
|
+
target: defaultTarget,
|
|
82
|
+
retryFailedAfter = ONE_DAY,
|
|
83
|
+
claimTimeout = TEN_MINUTES,
|
|
84
|
+
pollInterval = 250,
|
|
85
|
+
onError = (error, { sourceId, variant }) => console.error(
|
|
86
|
+
`[cedar uploads] could not make variant '${variant}' of ${sourceId}:`,
|
|
87
|
+
error
|
|
88
|
+
)
|
|
89
|
+
} = options;
|
|
90
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const [name, value] of Object.entries(options.variants)) {
|
|
92
|
+
if (!VARIANT_NAME.test(name)) {
|
|
93
|
+
throw new UploadError(
|
|
94
|
+
"CONFIGURATION",
|
|
95
|
+
`Invalid variant name '${name}'. Use letters, digits, '.', '_' and '-', starting with a letter or digit.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const definition = typeof value === "function" ? { generate: value } : value;
|
|
99
|
+
const target = definition.target ?? defaultTarget;
|
|
100
|
+
if (!Object.hasOwn(targets, target.name) || targets[target.name] !== target) {
|
|
101
|
+
throw new UploadError(
|
|
102
|
+
"CONFIGURATION",
|
|
103
|
+
`Variant '${name}' uses a storage target that isn't in \`targets\`.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
definitions.set(name, { generate: definition.generate, target });
|
|
107
|
+
}
|
|
108
|
+
const names = [...definitions.keys()];
|
|
109
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
110
|
+
const waitingFor = /* @__PURE__ */ new Map();
|
|
111
|
+
const keyOf = (sourceId, name) => `${sourceId}\0${name}`;
|
|
112
|
+
const isWaitingFor = (from, to) => {
|
|
113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
114
|
+
const stack = [from];
|
|
115
|
+
for (let key = stack.pop(); key !== void 0; key = stack.pop()) {
|
|
116
|
+
if (key === to) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (!seen.has(key)) {
|
|
120
|
+
seen.add(key);
|
|
121
|
+
stack.push(...waitingFor.get(key) ?? []);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
};
|
|
126
|
+
const obtainFor = async (waiter, sourceId, name, chain) => {
|
|
127
|
+
const key = keyOf(sourceId, name);
|
|
128
|
+
if (!chain.includes(name) && inFlight.has(key) && isWaitingFor(key, waiter)) {
|
|
129
|
+
throw new UploadError(
|
|
130
|
+
"CONFIGURATION",
|
|
131
|
+
`Variant '${name}' and the variant reading it depend on each other.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const waits = waitingFor.get(waiter) ?? [];
|
|
135
|
+
waitingFor.set(waiter, [...waits, key]);
|
|
136
|
+
try {
|
|
137
|
+
return await obtain(sourceId, name, chain);
|
|
138
|
+
} finally {
|
|
139
|
+
const rest = waitingFor.get(waiter) ?? [];
|
|
140
|
+
rest.splice(rest.indexOf(key), 1);
|
|
141
|
+
if (rest.length === 0) {
|
|
142
|
+
waitingFor.delete(waiter);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const definitionOf = (name) => {
|
|
147
|
+
const definition = definitions.get(name);
|
|
148
|
+
if (!definition) {
|
|
149
|
+
throw new UploadError(
|
|
150
|
+
"CONFIGURATION",
|
|
151
|
+
`Unknown upload variant '${name}'. Configured variants: ${names.join(", ") || "(none)"}.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return definition;
|
|
155
|
+
};
|
|
156
|
+
const loadSource = async (sourceId) => {
|
|
157
|
+
const source = await db.upload.findUnique({
|
|
158
|
+
where: { id: sourceId },
|
|
159
|
+
omit: { data: true }
|
|
160
|
+
});
|
|
161
|
+
if (source?.status !== "completed") {
|
|
162
|
+
throw new UploadError(
|
|
163
|
+
"NOT_FOUND",
|
|
164
|
+
`Upload '${sourceId}' does not exist or is not completed, so it cannot have variants.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (source.sourceId) {
|
|
168
|
+
throw new UploadError(
|
|
169
|
+
"NOT_SUPPORTED",
|
|
170
|
+
`Upload '${sourceId}' is itself a variant. Variants are made from original uploads only; use \`readVariant()\` in a generator to make one variant from another.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return source;
|
|
174
|
+
};
|
|
175
|
+
const findRow = (sourceId, name) => db.upload.findFirst({
|
|
176
|
+
where: { sourceId, variant: name },
|
|
177
|
+
omit: { data: true }
|
|
178
|
+
});
|
|
179
|
+
const make = async (source, rowId, claim, name, chain) => {
|
|
180
|
+
const definition = definitionOf(name);
|
|
181
|
+
const target = definition.target;
|
|
182
|
+
const ours = { id: rowId, status: "pending", tokenId: claim };
|
|
183
|
+
const fail = async (error) => {
|
|
184
|
+
if (error !== void 0) {
|
|
185
|
+
onError(error, { sourceId: source.id, variant: name });
|
|
186
|
+
}
|
|
187
|
+
await db.upload.updateMany({ where: ours, data: { status: "failed" } });
|
|
188
|
+
return null;
|
|
189
|
+
};
|
|
190
|
+
let file;
|
|
191
|
+
try {
|
|
192
|
+
file = await definition.generate({
|
|
193
|
+
source,
|
|
194
|
+
variant: name,
|
|
195
|
+
readSource: () => readBytes(db, targets, source),
|
|
196
|
+
readVariant: async (other) => {
|
|
197
|
+
const made = await obtainFor(
|
|
198
|
+
keyOf(source.id, name),
|
|
199
|
+
source.id,
|
|
200
|
+
other,
|
|
201
|
+
[...chain, name]
|
|
202
|
+
);
|
|
203
|
+
return made ? readBytes(db, targets, made) : null;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return fail(e);
|
|
208
|
+
}
|
|
209
|
+
if (!file) {
|
|
210
|
+
return fail();
|
|
211
|
+
}
|
|
212
|
+
if (!MIME_TYPE.test(file.mimeType)) {
|
|
213
|
+
return fail(
|
|
214
|
+
new UploadError(
|
|
215
|
+
"MIME_TYPE_NOT_ALLOWED",
|
|
216
|
+
`Variant '${name}' returned '${file.mimeType}', which is not a valid MIME type.`
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const isDbTarget = target.providerType === "db";
|
|
221
|
+
if (isDbTarget && file.data.byteLength > DB_MAX_FILE_SIZE) {
|
|
222
|
+
return fail(
|
|
223
|
+
new UploadError(
|
|
224
|
+
"FILE_TOO_LARGE",
|
|
225
|
+
`Variant '${name}' is ${file.data.byteLength} bytes, which exceeds the ${DB_MAX_FILE_SIZE} byte limit for target '${target.name}'.`
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const storageKey = isDbTarget ? null : generateStorageKey(file.mimeType);
|
|
230
|
+
if (storageKey) {
|
|
231
|
+
try {
|
|
232
|
+
await target.write(storageKey, file.data, {
|
|
233
|
+
contentType: file.mimeType
|
|
234
|
+
});
|
|
235
|
+
} catch (e) {
|
|
236
|
+
await target.delete(storageKey).catch(() => void 0);
|
|
237
|
+
await db.upload.deleteMany({ where: ours });
|
|
238
|
+
throw e;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const { count } = await db.upload.updateMany({
|
|
242
|
+
where: ours,
|
|
243
|
+
data: {
|
|
244
|
+
status: "completed",
|
|
245
|
+
// A retry or a takeover writes to the variant's current target,
|
|
246
|
+
// which is not necessarily the one the row was created for
|
|
247
|
+
target: target.name,
|
|
248
|
+
storageKey,
|
|
249
|
+
size: BigInt(file.data.byteLength),
|
|
250
|
+
mimeType: file.mimeType,
|
|
251
|
+
filename: file.filename ?? defaultFilename(source.filename, name, file.mimeType),
|
|
252
|
+
// Copying gives Prisma the plain-ArrayBuffer-backed array its
|
|
253
|
+
// `Bytes` input requires
|
|
254
|
+
data: isDbTarget ? new Uint8Array(file.data) : null,
|
|
255
|
+
tokenId: null
|
|
256
|
+
}
|
|
257
|
+
}).catch(async (e) => {
|
|
258
|
+
if (storageKey) {
|
|
259
|
+
await target.delete(storageKey).catch(() => void 0);
|
|
260
|
+
}
|
|
261
|
+
await db.upload.deleteMany({ where: ours }).catch(() => void 0);
|
|
262
|
+
throw e;
|
|
263
|
+
});
|
|
264
|
+
if (count !== 1) {
|
|
265
|
+
if (storageKey) {
|
|
266
|
+
await target.delete(storageKey).catch(() => void 0);
|
|
267
|
+
}
|
|
268
|
+
const current = await findRow(source.id, name);
|
|
269
|
+
return current?.status === "completed" ? current : null;
|
|
270
|
+
}
|
|
271
|
+
return db.upload.findUnique({ where: { id: rowId }, omit: { data: true } });
|
|
272
|
+
};
|
|
273
|
+
const obtainUncached = async (sourceId, name, chain) => {
|
|
274
|
+
const definition = definitionOf(name);
|
|
275
|
+
const source = await loadSource(sourceId);
|
|
276
|
+
for (; ; ) {
|
|
277
|
+
const row = await findRow(sourceId, name);
|
|
278
|
+
const now = Date.now();
|
|
279
|
+
const claim = randomUUID();
|
|
280
|
+
if (!row) {
|
|
281
|
+
let created;
|
|
282
|
+
try {
|
|
283
|
+
created = await db.upload.create({
|
|
284
|
+
data: {
|
|
285
|
+
sourceId,
|
|
286
|
+
variant: name,
|
|
287
|
+
target: definition.target.name,
|
|
288
|
+
status: "pending",
|
|
289
|
+
filename: defaultFilename(source.filename, name),
|
|
290
|
+
mimeType: "application/octet-stream",
|
|
291
|
+
size: 0n,
|
|
292
|
+
tokenId: claim,
|
|
293
|
+
userId: source.userId,
|
|
294
|
+
organizationId: source.organizationId
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
} catch (e) {
|
|
298
|
+
if (isPrismaError(e, "P2002")) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (isPrismaError(e, "P2003")) {
|
|
302
|
+
throw new UploadError(
|
|
303
|
+
"NOT_FOUND",
|
|
304
|
+
`Upload '${sourceId}' was deleted while its variant '${name}' was being made.`,
|
|
305
|
+
e
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
throw e;
|
|
309
|
+
}
|
|
310
|
+
return make(source, created.id, claim, name, chain);
|
|
311
|
+
}
|
|
312
|
+
if (row.status === "completed") {
|
|
313
|
+
return row;
|
|
314
|
+
}
|
|
315
|
+
if (row.status === "failed") {
|
|
316
|
+
const cutoff2 = new Date(now - retryFailedAfter);
|
|
317
|
+
if (row.updatedAt >= cutoff2) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const { count } = await db.upload.updateMany({
|
|
321
|
+
where: { id: row.id, status: "failed", updatedAt: { lt: cutoff2 } },
|
|
322
|
+
data: { status: "pending", tokenId: claim }
|
|
323
|
+
});
|
|
324
|
+
if (count === 1) {
|
|
325
|
+
return make(source, row.id, claim, name, chain);
|
|
326
|
+
}
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const cutoff = new Date(now - claimTimeout);
|
|
330
|
+
if (row.updatedAt < cutoff) {
|
|
331
|
+
const { count } = await db.upload.updateMany({
|
|
332
|
+
where: {
|
|
333
|
+
id: row.id,
|
|
334
|
+
status: "pending",
|
|
335
|
+
tokenId: row.tokenId,
|
|
336
|
+
updatedAt: { lt: cutoff }
|
|
337
|
+
},
|
|
338
|
+
data: { tokenId: claim }
|
|
339
|
+
});
|
|
340
|
+
if (count === 1) {
|
|
341
|
+
return make(source, row.id, claim, name, chain);
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
await sleep(pollInterval);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const obtain = (sourceId, name, chain) => {
|
|
349
|
+
if (chain.includes(name)) {
|
|
350
|
+
return Promise.reject(
|
|
351
|
+
new UploadError(
|
|
352
|
+
"CONFIGURATION",
|
|
353
|
+
`Variant '${name}' depends on itself: ${[...chain, name].join(" -> ")}.`
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const key = keyOf(sourceId, name);
|
|
358
|
+
const running = inFlight.get(key);
|
|
359
|
+
if (running) {
|
|
360
|
+
return running;
|
|
361
|
+
}
|
|
362
|
+
const attempt = obtainUncached(sourceId, name, chain);
|
|
363
|
+
const forget = () => {
|
|
364
|
+
if (inFlight.get(key) === attempt) {
|
|
365
|
+
inFlight.delete(key);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
inFlight.set(key, attempt);
|
|
369
|
+
attempt.then(forget, forget);
|
|
370
|
+
return attempt;
|
|
371
|
+
};
|
|
372
|
+
const getVariant = (source, name) => obtain(idOf(source), name, []);
|
|
373
|
+
const findVariant = async (source, name) => {
|
|
374
|
+
definitionOf(name);
|
|
375
|
+
return db.upload.findFirst({
|
|
376
|
+
where: { sourceId: idOf(source), variant: name, status: "completed" },
|
|
377
|
+
omit: { data: true }
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
const findVariants = async (sourceIds, name) => {
|
|
381
|
+
definitionOf(name);
|
|
382
|
+
const rows = sourceIds.length ? await db.upload.findMany({
|
|
383
|
+
where: {
|
|
384
|
+
sourceId: { in: sourceIds },
|
|
385
|
+
variant: name,
|
|
386
|
+
status: "completed"
|
|
387
|
+
},
|
|
388
|
+
omit: { data: true }
|
|
389
|
+
}) : [];
|
|
390
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
391
|
+
for (const row of rows) {
|
|
392
|
+
if (row.sourceId) {
|
|
393
|
+
bySource.set(row.sourceId, row);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return bySource;
|
|
397
|
+
};
|
|
398
|
+
const readVariant = async (source, name) => {
|
|
399
|
+
const upload = await getVariant(source, name);
|
|
400
|
+
if (!upload) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
return { upload, data: await readBytes(db, targets, upload) };
|
|
404
|
+
};
|
|
405
|
+
const getVariantUrl = async (source, name, urlOptions = {}) => {
|
|
406
|
+
const upload = await getVariant(source, name);
|
|
407
|
+
if (!upload) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
const target = resolveTarget(targets, upload.target);
|
|
411
|
+
if (target.providerType === "db") {
|
|
412
|
+
return toDataUri(upload.mimeType, await readBytes(db, targets, upload));
|
|
413
|
+
}
|
|
414
|
+
if (!upload.storageKey) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
return target.getSignedReadUrl(upload.storageKey, urlOptions);
|
|
418
|
+
};
|
|
419
|
+
const regenerateVariants = async (name, {
|
|
420
|
+
remake = true,
|
|
421
|
+
batchSize = 100
|
|
422
|
+
} = {}) => {
|
|
423
|
+
definitionOf(name);
|
|
424
|
+
const result = {
|
|
425
|
+
deleted: 0,
|
|
426
|
+
made: 0,
|
|
427
|
+
failed: 0,
|
|
428
|
+
errors: 0
|
|
429
|
+
};
|
|
430
|
+
const newest = await db.upload.findFirst({
|
|
431
|
+
where: { variant: name },
|
|
432
|
+
omit: { data: true },
|
|
433
|
+
orderBy: { createdAt: "desc" }
|
|
434
|
+
});
|
|
435
|
+
if (!newest) {
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
const cutoff = newest.createdAt;
|
|
439
|
+
const exclude = [];
|
|
440
|
+
for (; ; ) {
|
|
441
|
+
const rows = await db.upload.findMany({
|
|
442
|
+
where: {
|
|
443
|
+
variant: name,
|
|
444
|
+
createdAt: { lte: cutoff },
|
|
445
|
+
...exclude.length > 0 && { id: { notIn: exclude } }
|
|
446
|
+
},
|
|
447
|
+
omit: { data: true },
|
|
448
|
+
orderBy: { createdAt: "asc" },
|
|
449
|
+
take: batchSize
|
|
450
|
+
});
|
|
451
|
+
if (rows.length === 0) {
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
for (const row of rows) {
|
|
455
|
+
const sourceId = row.sourceId ?? "";
|
|
456
|
+
try {
|
|
457
|
+
await deleteVariantRow(db, targets, row);
|
|
458
|
+
result.deleted += 1;
|
|
459
|
+
} catch (e) {
|
|
460
|
+
result.errors += 1;
|
|
461
|
+
exclude.push(row.id);
|
|
462
|
+
onError(e, { sourceId, variant: name });
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (remake) {
|
|
466
|
+
await inFlight.get(keyOf(sourceId, name))?.catch(() => null);
|
|
467
|
+
const made = await getVariant(sourceId, name).catch((e) => {
|
|
468
|
+
onError(e, { sourceId, variant: name });
|
|
469
|
+
return null;
|
|
470
|
+
});
|
|
471
|
+
if (made) {
|
|
472
|
+
result.made += 1;
|
|
473
|
+
} else {
|
|
474
|
+
result.failed += 1;
|
|
475
|
+
}
|
|
476
|
+
const current = await findRow(sourceId, name);
|
|
477
|
+
if (current && current.createdAt <= cutoff) {
|
|
478
|
+
exclude.push(current.id);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
return {
|
|
485
|
+
names,
|
|
486
|
+
getVariant,
|
|
487
|
+
findVariant,
|
|
488
|
+
findVariants,
|
|
489
|
+
readVariant,
|
|
490
|
+
getVariantUrl,
|
|
491
|
+
regenerateVariants,
|
|
492
|
+
deleteVariants: (source) => deleteVariants(source, { db, targets })
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
export {
|
|
496
|
+
defineUploadVariants,
|
|
497
|
+
deleteVariants
|
|
498
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/uploads",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3421",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"test:watch": "vitest watch"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@cedarjs/api": "8.0.0-canary.
|
|
47
|
-
"@cedarjs/context": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/graphql-server": "8.0.0-canary.
|
|
46
|
+
"@cedarjs/api": "8.0.0-canary.3421",
|
|
47
|
+
"@cedarjs/context": "8.0.0-canary.3421",
|
|
48
|
+
"@cedarjs/graphql-server": "8.0.0-canary.3421",
|
|
49
49
|
"@fastify/multipart": "9.4.0",
|
|
50
50
|
"graphql": "16.14.2",
|
|
51
51
|
"mime-types": "2.1.35"
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"@arethetypeswrong/cli": "0.18.5",
|
|
56
56
|
"@aws-sdk/client-s3": "3.1149.0",
|
|
57
57
|
"@aws-sdk/s3-request-presigner": "3.1149.0",
|
|
58
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
59
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
58
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3421",
|
|
59
|
+
"@cedarjs/web": "8.0.0-canary.3421",
|
|
60
60
|
"@prisma/adapter-better-sqlite3": "7.10.0",
|
|
61
61
|
"@prisma/client": "7.10.0",
|
|
62
62
|
"@testing-library/dom": "10.4.2",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"@apollo/client": "4.3.3",
|
|
91
91
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
92
92
|
"@aws-sdk/s3-request-presigner": "^3.0.0",
|
|
93
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
93
|
+
"@cedarjs/web": "8.0.0-canary.3421",
|
|
94
94
|
"@uppy/aws-s3": "^6.0.0",
|
|
95
95
|
"@uppy/core": "^6.0.0",
|
|
96
96
|
"@uppy/dashboard": "^6.0.0",
|