@maas/payload-plugin-media-cloud 0.0.39 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/staticHandler.mjs +5 -2
- package/dist/adapter/staticHandler.mjs.map +1 -1
- package/dist/components/gridContext/gridContext.d.mts +1 -1
- package/dist/components/gridView/gridView.css +11 -1
- package/dist/components/gridView/gridView.d.mts +1 -1
- package/dist/components/gridView/gridView.mjs +3 -2
- package/dist/components/gridView/gridView.mjs.map +1 -1
- package/dist/components/muxPreview/muxPreview.d.mts +1 -1
- package/dist/components/selectMany/selectMany.d.mts +10 -0
- package/dist/components/selectMany/selectMany.mjs +49 -0
- package/dist/components/selectMany/selectMany.mjs.map +1 -0
- package/dist/components/uploadHandler/uploadHandler.d.mts +1 -1
- package/dist/endpoints/muxAssetHandler.d.mts +1 -1
- package/dist/endpoints/muxCreateUploadHandler.d.mts +1 -1
- package/dist/endpoints/muxWebhookHandler.d.mts +1 -1
- package/dist/endpoints/tusPostProcessorHandler.mjs +13 -3
- package/dist/endpoints/tusPostProcessorHandler.mjs.map +1 -1
- package/dist/tus/stores/s3/partsManager.mjs +4 -10
- package/dist/tus/stores/s3/partsManager.mjs.map +1 -1
- package/dist/tus/stores/s3/s3Store.mjs +4 -1
- package/dist/tus/stores/s3/s3Store.mjs.map +1 -1
- package/dist/utils/file.d.mts +2 -2
- package/dist/utils/mux.d.mts +1 -1
- package/dist/utils/tus.d.mts +4 -4
- package/package.json +1 -1
|
@@ -50,13 +50,16 @@ function createEmptyResponse(args) {
|
|
|
50
50
|
* @returns A Promise that resolves to a Response object containing the file stream or empty response on error
|
|
51
51
|
*/
|
|
52
52
|
async function serveS3File({ getS3Store, doc }) {
|
|
53
|
+
let stream$1 = void 0;
|
|
53
54
|
try {
|
|
54
|
-
|
|
55
|
+
stream$1 = await getS3Store().read(doc.path ?? doc.filename);
|
|
55
56
|
if (stream$1) {
|
|
56
57
|
const webStream = Readable.toWeb(stream$1);
|
|
57
58
|
return new Response(webStream);
|
|
58
59
|
}
|
|
59
|
-
} catch (_error) {
|
|
60
|
+
} catch (_error) {
|
|
61
|
+
stream$1?.destroy();
|
|
62
|
+
}
|
|
60
63
|
return createEmptyResponse({});
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staticHandler.mjs","names":["stream"],"sources":["../../src/adapter/staticHandler.ts"],"sourcesContent":["import { Readable } from 'node:stream'\nimport type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'\nimport type { Document } from 'payload'\nimport type { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface StaticHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\ninterface ServeS3FileArgs {\n getS3Store: () => S3Store\n doc: Document\n}\n\ninterface CreateEmptyResponseArgs {\n mimeType?: string\n}\n\n/**\n * Creates a static handler that serves files from S3 or returns empty responses for Mux assets\n * @param args - The arguments for creating the static handler\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.collection - The name of the media collection in Payload\n * @returns A StaticHandler function that serves files or empty responses based on storage type\n */\nexport function getStaticHandler(args: StaticHandlerArgs): StaticHandler {\n const { getS3Store, collection } = args\n return async (req, { params }) => {\n const { payload } = req\n const filename = params.filename\n\n const { docs } = await payload.find({\n collection,\n where: { filename: { equals: filename } },\n })\n\n const doc = docs[0] as Document\n\n if (!doc) {\n return createEmptyResponse()\n }\n\n if (doc.storage === 'mux') {\n return createEmptyResponse({ mimeType: doc.mimeType })\n }\n\n return await serveS3File({ getS3Store, doc })\n }\n}\n\n/**\n * Creates an empty response with appropriate headers\n * @param args - Optional arguments for the empty response\n * @param args.mimeType - The MIME type to set in the Content-Type header\n * @returns A Response object with empty content and appropriate headers\n */\nfunction createEmptyResponse(args?: CreateEmptyResponseArgs): Response {\n const { mimeType = 'application/octet-stream' } = args ?? {}\n return new Response(new Uint8Array(0), {\n status: 200,\n headers: {\n 'Content-Type': mimeType,\n 'Content-Length': '0',\n },\n })\n}\n\n/**\n * Serves a file from S3 storage\n * @param args - The arguments for serving the S3 file\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.doc - The document containing file metadata\n * @returns A Promise that resolves to a Response object containing the file stream or empty response on error\n */\nasync function serveS3File({\n getS3Store,\n doc,\n}: ServeS3FileArgs): Promise<Response> {\n try {\n const s3Store = getS3Store()\n
|
|
1
|
+
{"version":3,"file":"staticHandler.mjs","names":["stream: Readable | undefined","stream"],"sources":["../../src/adapter/staticHandler.ts"],"sourcesContent":["import { Readable } from 'node:stream'\nimport type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'\nimport type { Document } from 'payload'\nimport type { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface StaticHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\ninterface ServeS3FileArgs {\n getS3Store: () => S3Store\n doc: Document\n}\n\ninterface CreateEmptyResponseArgs {\n mimeType?: string\n}\n\n/**\n * Creates a static handler that serves files from S3 or returns empty responses for Mux assets\n * @param args - The arguments for creating the static handler\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.collection - The name of the media collection in Payload\n * @returns A StaticHandler function that serves files or empty responses based on storage type\n */\nexport function getStaticHandler(args: StaticHandlerArgs): StaticHandler {\n const { getS3Store, collection } = args\n return async (req, { params }) => {\n const { payload } = req\n const filename = params.filename\n\n const { docs } = await payload.find({\n collection,\n where: { filename: { equals: filename } },\n })\n\n const doc = docs[0] as Document\n\n if (!doc) {\n return createEmptyResponse()\n }\n\n if (doc.storage === 'mux') {\n return createEmptyResponse({ mimeType: doc.mimeType })\n }\n\n return await serveS3File({ getS3Store, doc })\n }\n}\n\n/**\n * Creates an empty response with appropriate headers\n * @param args - Optional arguments for the empty response\n * @param args.mimeType - The MIME type to set in the Content-Type header\n * @returns A Response object with empty content and appropriate headers\n */\nfunction createEmptyResponse(args?: CreateEmptyResponseArgs): Response {\n const { mimeType = 'application/octet-stream' } = args ?? {}\n return new Response(new Uint8Array(0), {\n status: 200,\n headers: {\n 'Content-Type': mimeType,\n 'Content-Length': '0',\n },\n })\n}\n\n/**\n * Serves a file from S3 storage\n * @param args - The arguments for serving the S3 file\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.doc - The document containing file metadata\n * @returns A Promise that resolves to a Response object containing the file stream or empty response on error\n */\nasync function serveS3File({\n getS3Store,\n doc,\n}: ServeS3FileArgs): Promise<Response> {\n let stream: Readable | undefined = undefined\n\n try {\n const s3Store = getS3Store()\n stream = await s3Store.read(doc.path ?? doc.filename)\n\n if (stream) {\n const webStream = Readable.toWeb(stream) as ReadableStream<Uint8Array>\n return new Response(webStream)\n }\n } catch (_error) {\n stream?.destroy()\n // File not found or other error, fall back to empty response\n }\n\n return createEmptyResponse({})\n}\n"],"mappings":";;;;;;;;;;AA0BA,SAAgB,iBAAiB,MAAwC;CACvE,MAAM,EAAE,YAAY,eAAe;AACnC,QAAO,OAAO,KAAK,EAAE,aAAa;EAChC,MAAM,EAAE,YAAY;EACpB,MAAM,WAAW,OAAO;EAExB,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;GAClC;GACA,OAAO,EAAE,UAAU,EAAE,QAAQ,UAAU,EAAE;GAC1C,CAAC;EAEF,MAAM,MAAM,KAAK;AAEjB,MAAI,CAAC,IACH,QAAO,qBAAqB;AAG9B,MAAI,IAAI,YAAY,MAClB,QAAO,oBAAoB,EAAE,UAAU,IAAI,UAAU,CAAC;AAGxD,SAAO,MAAM,YAAY;GAAE;GAAY;GAAK,CAAC;;;;;;;;;AAUjD,SAAS,oBAAoB,MAA0C;CACrE,MAAM,EAAE,WAAW,+BAA+B,QAAQ,EAAE;AAC5D,QAAO,IAAI,SAAS,IAAI,WAAW,EAAE,EAAE;EACrC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,kBAAkB;GACnB;EACF,CAAC;;;;;;;;;AAUJ,eAAe,YAAY,EACzB,YACA,OACqC;CACrC,IAAIA,WAA+B;AAEnC,KAAI;AAEF,aAAS,MADO,YAAY,CACL,KAAK,IAAI,QAAQ,IAAI,SAAS;AAErD,MAAIC,UAAQ;GACV,MAAM,YAAY,SAAS,MAAMA,SAAO;AACxC,UAAO,IAAI,SAAS,UAAU;;UAEzB,QAAQ;AACf,YAAQ,SAAS;;AAInB,QAAO,oBAAoB,EAAE,CAAC"}
|
|
@@ -45,6 +45,16 @@
|
|
|
45
45
|
.grid-view__list-selection {
|
|
46
46
|
display: flex;
|
|
47
47
|
align-items: center;
|
|
48
|
+
justify-content: space-between;
|
|
49
|
+
flex-wrap: wrap;
|
|
48
50
|
gap: 1rem;
|
|
49
|
-
margin
|
|
51
|
+
margin: 1rem 10px 0 10px;
|
|
52
|
+
width: 100%;
|
|
53
|
+
& .list-selection {
|
|
54
|
+
margin-bottom: 0;
|
|
55
|
+
margin-left: 0;
|
|
56
|
+
& span {
|
|
57
|
+
white-space: nowrap;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
50
60
|
}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { GridProvider } from "../gridContext/gridContext.mjs";
|
|
4
4
|
import { ItemCardGrid } from "../itemCardGrid/itemCardGrid.mjs";
|
|
5
|
+
import { SelectMany } from "../selectMany/selectMany.mjs";
|
|
5
6
|
import { c } from "react/compiler-runtime";
|
|
6
7
|
import { Fragment, useEffect } from "react";
|
|
7
8
|
import { useRouter } from "next/navigation";
|
|
8
9
|
import { getTranslation } from "@payloadcms/translations";
|
|
9
|
-
import { Gutter, ListControls, ListHeader, ListSelection, PageControls,
|
|
10
|
+
import { Gutter, ListControls, ListHeader, ListSelection, PageControls, SelectionProvider, useBulkUpload, useConfig, useListDrawerContext, useListQuery, useModal, useStepNav, useTranslation, useWindowInfo } from "@payloadcms/ui";
|
|
10
11
|
import { DefaultListViewTabs } from "@payloadcms/ui/elements/DefaultListViewTabs";
|
|
11
12
|
import { formatAdminURL } from "payload/shared";
|
|
12
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -282,7 +283,7 @@ function GridView(props) {
|
|
|
282
283
|
label: getTranslation(collectionConfig.labels.plural, i18n)
|
|
283
284
|
}), /* @__PURE__ */ jsx("div", {
|
|
284
285
|
className: "grid-view__list-selection-actions",
|
|
285
|
-
children: typeof onBulkSelect === "function" ?
|
|
286
|
+
children: typeof onBulkSelect === "function" ? [...beforeActions ?? [], /* @__PURE__ */ jsx(SelectMany, { onClick: onBulkSelect }, "select-many")] : beforeActions
|
|
286
287
|
})]
|
|
287
288
|
}) : null,
|
|
288
289
|
collectionConfig
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gridView.mjs","names":["c","_c","React","Fragment","useMemo","useEffect","useRouter","getTranslation","Gutter","useListQuery","useConfig","SelectionProvider","useTranslation","useWindowInfo","useBulkUpload","useModal","useStepNav","ListHeader","ListSelection","ListControls","PageControls","SelectMany","useListDrawerContext","DefaultListViewTabs","formatAdminURL","ItemCardGrid","GridProvider","jsx","_jsx","jsxs","_jsxs","GridView","props","$","collectionSlug","AfterList","AfterListTable","BeforeList","BeforeListTable","Description","newDocumentURL","hasCreatePermission","hasDeletePermission","disableBulkDelete","disableBulkEdit","enableRowSelections","beforeActions","viewType","config","getEntityConfig","routes","t0","admin","adminRoute","router","data","t1","docs","t2","t3","undefined","i18n","setStepNav","openModal","drawerSlug","bulkUploadDrawerSlug","setCollectionSlug","setBulkUploadCollectionSlug","setOnSuccess","breakpoints","t4","s","smallBreak","t5","collectionConfig","labels","upload","isUploadCollection","Boolean","isBulkUploadEnabled","bulkUpload","isTrashEnabled","trash","onBulkSelect","isInDrawer","getThumbnailURL","_temp","t6","t7","doc_0","title","doc","filename","alt","String","id","storage","thumbnailURL","relationTo","map","mappedDocs","refresh","openBulkUpload","t8","plural","baseLabel","label","url","path","trashLabel","t","navItems","t9","t10","totalDocs","t11","t12","t13","t14","filter","t15","t16","Actions","onBulkUploadSuccess","t17","t18","enableColumns","t19","length","singular","className","children","type","onClick","push","t20","AfterPageControls","t21","t22","t23","t24","t25","mux","playbackId"],"sources":["../../../src/components/gridView/gridView.tsx"],"sourcesContent":["'use client'\n\nimport React, { Fragment, useMemo, useEffect } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { getTranslation } from '@payloadcms/translations'\nimport {\n Gutter,\n useListQuery,\n useConfig,\n SelectionProvider,\n useTranslation,\n useWindowInfo,\n useBulkUpload,\n useModal,\n useStepNav,\n ListHeader,\n ListSelection,\n ListControls,\n PageControls,\n SelectMany,\n useListDrawerContext,\n} from '@payloadcms/ui'\nimport { DefaultListViewTabs } from '@payloadcms/ui/elements/DefaultListViewTabs'\nimport { formatAdminURL } from 'payload/shared'\nimport { ItemCardGrid } from '../itemCardGrid/itemCardGrid'\nimport { GridProvider } from '../gridContext/gridContext'\n\nimport type { ListViewClientProps } from 'payload'\n\nimport './gridView.css'\n\n// Type for media document with common properties\ninterface MediaDocument {\n id: string | number\n storage: 'mux' | 's3'\n filename?: string\n alt?: string\n mimeType?: string\n url?: string\n thumbnailURL?: string\n mux?: {\n playbackId?: string\n }\n}\n\nexport interface MappedDocument {\n id: string | number\n storage: 'mux' | 's3'\n title: string\n thumbnailURL?: string\n relationTo: string\n}\n\nexport function GridView(props: ListViewClientProps): React.JSX.Element {\n const baseClass = 'grid-view'\n\n const {\n collectionSlug,\n AfterList,\n AfterListTable,\n BeforeList,\n BeforeListTable,\n Description,\n newDocumentURL,\n hasCreatePermission,\n hasDeletePermission,\n disableBulkDelete,\n disableBulkEdit,\n enableRowSelections,\n beforeActions,\n viewType,\n } = props\n\n const { config, getEntityConfig } = useConfig()\n\n const {\n routes: { admin: adminRoute },\n } = config\n\n const router = useRouter()\n\n const { data } = useListQuery() ?? {}\n const { docs = [] } = data ?? {}\n\n const { i18n } = useTranslation()\n const { setStepNav } = useStepNav()\n const { openModal } = useModal()\n\n const {\n drawerSlug: bulkUploadDrawerSlug,\n setCollectionSlug: setBulkUploadCollectionSlug,\n setOnSuccess,\n } = useBulkUpload()\n\n const {\n breakpoints: { s: smallBreak },\n } = useWindowInfo()\n\n const collectionConfig = getEntityConfig({ collectionSlug })\n const { labels, upload } = collectionConfig\n\n const isUploadCollection = Boolean(upload)\n\n const isBulkUploadEnabled =\n (isUploadCollection && collectionConfig.upload?.bulkUpload) ?? true\n\n const isTrashEnabled = Boolean(collectionConfig.trash)\n\n const { onBulkSelect, isInDrawer } = useListDrawerContext()\n\n const getThumbnailURL = (doc: MediaDocument): string | undefined => {\n if (doc?.thumbnailURL) {\n return doc.thumbnailURL\n }\n\n if (doc?.storage === 'mux' && doc?.mux?.playbackId) {\n return `https://image.mux.com/${doc.mux.playbackId}/thumbnail.jpg?width=400&height=400&fit_mode=pad`\n }\n\n return undefined\n }\n\n const mappedDocs: MappedDocument[] = useMemo(() => {\n return docs.map((doc: MediaDocument) => {\n const title = doc.filename ?? doc.alt ?? String(doc.id)\n return {\n id: doc.id,\n storage: doc.storage,\n title: title,\n thumbnailURL: getThumbnailURL(doc),\n relationTo: collectionSlug,\n }\n })\n }, [docs, collectionSlug])\n\n const openBulkUpload = React.useCallback(() => {\n setBulkUploadCollectionSlug(collectionSlug)\n openModal(bulkUploadDrawerSlug)\n setOnSuccess(() => router.refresh())\n }, [\n router,\n collectionSlug,\n bulkUploadDrawerSlug,\n openModal,\n setBulkUploadCollectionSlug,\n setOnSuccess,\n ])\n\n // Set breadcrumb navigation\n useEffect(() => {\n const baseLabel = {\n label: getTranslation(labels?.plural, i18n),\n url:\n isTrashEnabled && viewType === 'trash'\n ? formatAdminURL({\n adminRoute,\n path: `/collections/${collectionSlug}`,\n })\n : undefined,\n }\n const trashLabel = {\n label: i18n.t('general:trash'),\n }\n const navItems =\n isTrashEnabled && viewType === 'trash'\n ? [baseLabel, trashLabel]\n : [baseLabel]\n setStepNav(navItems)\n }, [\n adminRoute,\n setStepNav,\n labels,\n isTrashEnabled,\n viewType,\n i18n,\n collectionSlug,\n ])\n\n return (\n <SelectionProvider docs={docs} totalDocs={data?.totalDocs ?? 0}>\n <GridProvider allowMultiSelection={enableRowSelections} docs={mappedDocs}>\n <div className={`grid-view grid-view--${collectionSlug}`}>\n <Fragment>\n {BeforeList}\n <Gutter className={`${baseClass}__wrapper collection-list__wrap`}>\n <ListHeader\n Actions={[\n beforeActions,\n !smallBreak && (\n <ListSelection\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={\n collectionConfig.disableBulkEdit ?? disableBulkEdit\n }\n key=\"list-selection\"\n label={getTranslation(labels?.plural, i18n)}\n />\n ),\n <DefaultListViewTabs\n collectionConfig={collectionConfig}\n config={config}\n key=\"default-list-actions\"\n viewType=\"list\"\n />,\n ].filter(Boolean)}\n collectionConfig={collectionConfig}\n Description={Description}\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={disableBulkEdit}\n hasCreatePermission={hasCreatePermission}\n hasDeletePermission={hasDeletePermission}\n i18n={i18n}\n isBulkUploadEnabled={isBulkUploadEnabled}\n isTrashEnabled={isTrashEnabled}\n newDocumentURL={newDocumentURL}\n onBulkUploadSuccess={() => router.refresh()}\n openBulkUpload={openBulkUpload}\n smallBreak={smallBreak}\n viewType={viewType}\n />\n <ListControls\n beforeActions={\n isInDrawer && typeof onBulkSelect === 'function'\n ? [\n ...(beforeActions ?? []),\n <SelectMany key=\"select-many\" onClick={onBulkSelect} />,\n ]\n : beforeActions\n }\n collectionConfig={collectionConfig}\n collectionSlug={collectionSlug}\n enableColumns={false}\n />\n\n {BeforeListTable}\n\n {docs.length === 0 ? (\n <div className={`${baseClass}__no-results`}>\n <p>\n {i18n.t(\n viewType === 'trash'\n ? 'general:noTrashResults'\n : 'general:noResults',\n {\n label: getTranslation(labels?.plural, i18n),\n }\n )}\n </p>\n {hasCreatePermission && newDocumentURL && (\n <div className={`${baseClass}__no-results__actions`}>\n <button\n type=\"button\"\n onClick={() => router.push(newDocumentURL)}\n className={`${baseClass}__create-link btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup`}\n >\n {i18n.t('general:createNewLabel', {\n label: getTranslation(labels?.singular, i18n),\n })}\n </button>\n </div>\n )}\n </div>\n ) : (\n <ItemCardGrid\n items={mappedDocs}\n adminRoute={adminRoute}\n collectionSlug={collectionSlug}\n />\n )}\n\n {docs?.length > 0 && (\n <PageControls\n AfterPageControls={\n smallBreak ? (\n <div className={`${baseClass}__list-selection`}>\n <ListSelection\n collectionConfig={collectionConfig}\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={disableBulkEdit}\n label={getTranslation(\n collectionConfig.labels.plural,\n i18n\n )}\n />\n <div className={`${baseClass}__list-selection-actions`}>\n {typeof onBulkSelect === 'function'\n ? beforeActions\n ? [\n ...beforeActions,\n <SelectMany\n key=\"select-many\"\n onClick={onBulkSelect}\n />,\n ]\n : [\n <SelectMany\n key=\"select-many\"\n onClick={onBulkSelect}\n />,\n ]\n : beforeActions}\n </div>\n </div>\n ) : null\n }\n collectionConfig={collectionConfig}\n />\n )}\n\n {AfterListTable}\n </Gutter>\n {AfterList}\n </Fragment>\n </div>\n </GridProvider>\n </SelectionProvider>\n )\n}\n\nexport default GridView\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gridView.mjs","names":["c","_c","React","Fragment","useMemo","useEffect","useRouter","getTranslation","Gutter","useListQuery","useConfig","SelectionProvider","useTranslation","useWindowInfo","useBulkUpload","useModal","useStepNav","ListSelection","ListControls","ListHeader","PageControls","useListDrawerContext","DefaultListViewTabs","formatAdminURL","ItemCardGrid","GridProvider","SelectMany","jsx","_jsx","jsxs","_jsxs","GridView","props","$","collectionSlug","AfterList","AfterListTable","BeforeList","BeforeListTable","Description","newDocumentURL","hasCreatePermission","hasDeletePermission","disableBulkDelete","disableBulkEdit","enableRowSelections","beforeActions","viewType","config","getEntityConfig","routes","t0","admin","adminRoute","router","data","t1","docs","t2","t3","undefined","i18n","setStepNav","openModal","drawerSlug","bulkUploadDrawerSlug","setCollectionSlug","setBulkUploadCollectionSlug","setOnSuccess","breakpoints","t4","s","smallBreak","t5","collectionConfig","labels","upload","isUploadCollection","Boolean","isBulkUploadEnabled","bulkUpload","isTrashEnabled","trash","onBulkSelect","isInDrawer","getThumbnailURL","_temp","t6","t7","doc_0","title","doc","filename","alt","String","id","storage","thumbnailURL","relationTo","map","mappedDocs","refresh","openBulkUpload","t8","plural","baseLabel","label","url","path","trashLabel","t","navItems","t9","t10","totalDocs","t11","t12","t13","t14","filter","t15","t16","Actions","onBulkUploadSuccess","t17","t18","enableColumns","t19","length","singular","className","children","type","onClick","push","t20","AfterPageControls","t21","t22","t23","t24","t25","mux","playbackId"],"sources":["../../../src/components/gridView/gridView.tsx"],"sourcesContent":["'use client'\n\nimport React, { Fragment, useMemo, useEffect } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { getTranslation } from '@payloadcms/translations'\nimport {\n Gutter,\n useListQuery,\n useConfig,\n SelectionProvider,\n useTranslation,\n useWindowInfo,\n useBulkUpload,\n useModal,\n useStepNav,\n ListSelection,\n ListControls,\n ListHeader,\n PageControls,\n useListDrawerContext,\n} from '@payloadcms/ui'\nimport { DefaultListViewTabs } from '@payloadcms/ui/elements/DefaultListViewTabs'\nimport { formatAdminURL } from 'payload/shared'\nimport { ItemCardGrid } from '../itemCardGrid/itemCardGrid'\nimport { GridProvider } from '../gridContext/gridContext'\nimport { SelectMany } from '../selectMany/selectMany'\n\nimport type { ListViewClientProps } from 'payload'\n\nimport './gridView.css'\n\n// Type for media document with common properties\ninterface MediaDocument {\n id: string | number\n storage: 'mux' | 's3'\n filename?: string\n alt?: string\n mimeType?: string\n url?: string\n thumbnailURL?: string\n mux?: {\n playbackId?: string\n }\n}\n\nexport interface MappedDocument {\n id: string | number\n storage: 'mux' | 's3'\n title: string\n thumbnailURL?: string\n relationTo: string\n}\n\nexport function GridView(props: ListViewClientProps): React.JSX.Element {\n const baseClass = 'grid-view'\n\n const {\n collectionSlug,\n AfterList,\n AfterListTable,\n BeforeList,\n BeforeListTable,\n Description,\n newDocumentURL,\n hasCreatePermission,\n hasDeletePermission,\n disableBulkDelete,\n disableBulkEdit,\n enableRowSelections,\n beforeActions,\n viewType,\n } = props\n\n const { config, getEntityConfig } = useConfig()\n\n const {\n routes: { admin: adminRoute },\n } = config\n\n const router = useRouter()\n\n const { data } = useListQuery() ?? {}\n const { docs = [] } = data ?? {}\n\n const { i18n } = useTranslation()\n const { setStepNav } = useStepNav()\n const { openModal } = useModal()\n\n const {\n drawerSlug: bulkUploadDrawerSlug,\n setCollectionSlug: setBulkUploadCollectionSlug,\n setOnSuccess,\n } = useBulkUpload()\n\n const {\n breakpoints: { s: smallBreak },\n } = useWindowInfo()\n\n const collectionConfig = getEntityConfig({ collectionSlug })\n const { labels, upload } = collectionConfig\n\n const isUploadCollection = Boolean(upload)\n\n const isBulkUploadEnabled =\n (isUploadCollection && collectionConfig.upload?.bulkUpload) ?? true\n\n const isTrashEnabled = Boolean(collectionConfig.trash)\n\n const { onBulkSelect, isInDrawer } = useListDrawerContext()\n\n const getThumbnailURL = (doc: MediaDocument): string | undefined => {\n if (doc?.thumbnailURL) {\n return doc.thumbnailURL\n }\n\n if (doc?.storage === 'mux' && doc?.mux?.playbackId) {\n return `https://image.mux.com/${doc.mux.playbackId}/thumbnail.jpg?width=400&height=400&fit_mode=pad`\n }\n\n return undefined\n }\n\n const mappedDocs: MappedDocument[] = useMemo(() => {\n return docs.map((doc: MediaDocument) => {\n const title = doc.filename ?? doc.alt ?? String(doc.id)\n return {\n id: doc.id,\n storage: doc.storage,\n title: title,\n thumbnailURL: getThumbnailURL(doc),\n relationTo: collectionSlug,\n }\n })\n }, [docs, collectionSlug])\n\n const openBulkUpload = React.useCallback(() => {\n setBulkUploadCollectionSlug(collectionSlug)\n openModal(bulkUploadDrawerSlug)\n setOnSuccess(() => router.refresh())\n }, [\n router,\n collectionSlug,\n bulkUploadDrawerSlug,\n openModal,\n setBulkUploadCollectionSlug,\n setOnSuccess,\n ])\n\n // Set breadcrumb navigation\n useEffect(() => {\n const baseLabel = {\n label: getTranslation(labels?.plural, i18n),\n url:\n isTrashEnabled && viewType === 'trash'\n ? formatAdminURL({\n adminRoute,\n path: `/collections/${collectionSlug}`,\n })\n : undefined,\n }\n const trashLabel = {\n label: i18n.t('general:trash'),\n }\n const navItems =\n isTrashEnabled && viewType === 'trash'\n ? [baseLabel, trashLabel]\n : [baseLabel]\n setStepNav(navItems)\n }, [\n adminRoute,\n setStepNav,\n labels,\n isTrashEnabled,\n viewType,\n i18n,\n collectionSlug,\n ])\n\n return (\n <SelectionProvider docs={docs} totalDocs={data?.totalDocs ?? 0}>\n <GridProvider allowMultiSelection={enableRowSelections} docs={mappedDocs}>\n <div className={`grid-view grid-view--${collectionSlug}`}>\n <Fragment>\n {BeforeList}\n <Gutter className={`${baseClass}__wrapper collection-list__wrap`}>\n <ListHeader\n Actions={[\n beforeActions,\n !smallBreak && (\n <ListSelection\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={\n collectionConfig.disableBulkEdit ?? disableBulkEdit\n }\n key=\"list-selection\"\n label={getTranslation(labels?.plural, i18n)}\n />\n ),\n <DefaultListViewTabs\n collectionConfig={collectionConfig}\n config={config}\n key=\"default-list-actions\"\n viewType=\"list\"\n />,\n ].filter(Boolean)}\n collectionConfig={collectionConfig}\n Description={Description}\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={disableBulkEdit}\n hasCreatePermission={hasCreatePermission}\n hasDeletePermission={hasDeletePermission}\n i18n={i18n}\n isBulkUploadEnabled={isBulkUploadEnabled}\n isTrashEnabled={isTrashEnabled}\n newDocumentURL={newDocumentURL}\n onBulkUploadSuccess={() => router.refresh()}\n openBulkUpload={openBulkUpload}\n smallBreak={smallBreak}\n viewType={viewType}\n />\n <ListControls\n beforeActions={\n isInDrawer && typeof onBulkSelect === 'function'\n ? [\n ...(beforeActions ?? []),\n <SelectMany key=\"select-many\" onClick={onBulkSelect} />,\n ]\n : beforeActions\n }\n collectionConfig={collectionConfig}\n collectionSlug={collectionSlug}\n enableColumns={false}\n />\n {BeforeListTable}\n {docs.length === 0 ? (\n <div className={`${baseClass}__no-results`}>\n <p>\n {i18n.t(\n viewType === 'trash'\n ? 'general:noTrashResults'\n : 'general:noResults',\n {\n label: getTranslation(labels?.plural, i18n),\n }\n )}\n </p>\n {hasCreatePermission && newDocumentURL && (\n <div className={`${baseClass}__no-results__actions`}>\n <button\n type=\"button\"\n onClick={() => router.push(newDocumentURL)}\n className={`${baseClass}__create-link btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup`}\n >\n {i18n.t('general:createNewLabel', {\n label: getTranslation(labels?.singular, i18n),\n })}\n </button>\n </div>\n )}\n </div>\n ) : (\n <ItemCardGrid\n items={mappedDocs}\n adminRoute={adminRoute}\n collectionSlug={collectionSlug}\n />\n )}\n {docs?.length > 0 && (\n <PageControls\n AfterPageControls={\n smallBreak ? (\n <div className={`${baseClass}__list-selection`}>\n <ListSelection\n collectionConfig={collectionConfig}\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={disableBulkEdit}\n label={getTranslation(\n collectionConfig.labels.plural,\n i18n\n )}\n />\n <div className={`${baseClass}__list-selection-actions`}>\n {typeof onBulkSelect === 'function'\n ? [\n ...(beforeActions ?? []),\n <SelectMany\n key=\"select-many\"\n onClick={onBulkSelect}\n />,\n ]\n : beforeActions}\n </div>\n </div>\n ) : null\n }\n collectionConfig={collectionConfig}\n />\n )}\n {AfterListTable}\n </Gutter>\n {AfterList}\n </Fragment>\n </div>\n </GridProvider>\n </SelectionProvider>\n )\n}\n\nexport default GridView\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useSelection } from "@payloadcms/ui";
|
|
3
|
+
|
|
4
|
+
//#region src/components/selectMany/selectMany.d.ts
|
|
5
|
+
declare const SelectMany: React.FC<{
|
|
6
|
+
onClick?: (ids: ReturnType<typeof useSelection>['selected']) => void;
|
|
7
|
+
}>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { SelectMany };
|
|
10
|
+
//# sourceMappingURL=selectMany.d.mts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { c } from "react/compiler-runtime";
|
|
2
|
+
import { Pill, useSelection, useTranslation } from "@payloadcms/ui";
|
|
3
|
+
import { jsxs } from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/components/selectMany/selectMany.tsx
|
|
6
|
+
const SelectMany = (props) => {
|
|
7
|
+
const $ = c(9);
|
|
8
|
+
const { onClick } = props;
|
|
9
|
+
const { count, selected } = useSelection();
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
if (!selected || !count) return null;
|
|
12
|
+
let t0;
|
|
13
|
+
if ($[0] !== onClick || $[1] !== selected) {
|
|
14
|
+
t0 = () => {
|
|
15
|
+
if (typeof onClick === "function") onClick(selected);
|
|
16
|
+
};
|
|
17
|
+
$[0] = onClick;
|
|
18
|
+
$[1] = selected;
|
|
19
|
+
$[2] = t0;
|
|
20
|
+
} else t0 = $[2];
|
|
21
|
+
let t1;
|
|
22
|
+
if ($[3] !== t) {
|
|
23
|
+
t1 = t("general:select");
|
|
24
|
+
$[3] = t;
|
|
25
|
+
$[4] = t1;
|
|
26
|
+
} else t1 = $[4];
|
|
27
|
+
let t2;
|
|
28
|
+
if ($[5] !== count || $[6] !== t0 || $[7] !== t1) {
|
|
29
|
+
t2 = /* @__PURE__ */ jsxs(Pill, {
|
|
30
|
+
onClick: t0,
|
|
31
|
+
pillStyle: "light",
|
|
32
|
+
size: "small",
|
|
33
|
+
children: [
|
|
34
|
+
t1,
|
|
35
|
+
" ",
|
|
36
|
+
count
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
$[5] = count;
|
|
40
|
+
$[6] = t0;
|
|
41
|
+
$[7] = t1;
|
|
42
|
+
$[8] = t2;
|
|
43
|
+
} else t2 = $[8];
|
|
44
|
+
return t2;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { SelectMany };
|
|
49
|
+
//# sourceMappingURL=selectMany.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectMany.mjs","names":["React","useSelection","useTranslation","Pill","jsxs","_jsxs","SelectMany","props","$","_c","onClick","count","selected","t","t0","t1","t2","pillStyle","size","children"],"sources":["../../../src/components/selectMany/selectMany.tsx"],"sourcesContent":["import React from 'react'\n\nimport { useSelection, useTranslation, Pill } from '@payloadcms/ui'\n\nexport const SelectMany: React.FC<{\n onClick?: (ids: ReturnType<typeof useSelection>['selected']) => void\n}> = (props) => {\n const { onClick } = props\n\n const { count, selected } = useSelection()\n const { t } = useTranslation()\n\n if (!selected || !count) {\n return null\n }\n\n return (\n <Pill\n onClick={() => {\n if (typeof onClick === 'function') {\n onClick(selected)\n }\n }}\n pillStyle=\"light\"\n size=\"small\"\n >\n {t('general:select')} {count}\n </Pill>\n )\n}\n"],"mappings":";;;;;AAIA,MAAaM,cAERC,UAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CACH,MAAA,EAAAC,YAAoBH;CAEpB,MAAA,EAAAI,OAAAC,aAA4BX,cAAc;CAC1C,MAAA,EAAAY,MAAcX,gBAAgB;AAE9B,KAAI,CAACU,YAAD,CAAcD,MAAK,QACd;CACR,IAAAG;AAAA,KAAAN,EAAA,OAAAE,WAAAF,EAAA,OAAAI,UAAA;AAIYE,aAAA;AACP,OAAI,OAAOJ,YAAY,WACrBA,SAAQE,SAAS;;AAEpBJ,IAAA,KAAAE;AAAAF,IAAA,KAAAI;AAAAJ,IAAA,KAAAM;OAAAA,MAAAN,EAAA;CAAA,IAAAO;AAAA,KAAAP,EAAA,OAAAK,GAAA;AAIAE,OAAAF,EAAE,iBAAiB;AAAAL,IAAA,KAAAK;AAAAL,IAAA,KAAAO;OAAAA,MAAAP,EAAA;CAAA,IAAAQ;AAAA,KAAAR,EAAA,OAAAG,SAAAH,EAAA,OAAAM,MAAAN,EAAA,OAAAO,IAAA;AATtBC,OAAAX,qBAACF,MAAI;GACMO,SAAAI;GAKCG,WAAA;GACLC,MAAA;GAAOC,UAAA;IAEXJ;IAAoB;IAAEJ;IAAK;GACxB,CAAC;AAAAH,IAAA,KAAAG;AAAAH,IAAA,KAAAM;AAAAN,IAAA,KAAAO;AAAAP,IAAA,KAAAQ;OAAAA,MAAAR,EAAA;AAAA,QAVPQ"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../../types/index.mjs";
|
|
2
|
+
import { UploadCollectionSlug } from "payload";
|
|
2
3
|
import * as react0 from "react";
|
|
3
4
|
import { ReactNode } from "react";
|
|
4
|
-
import { UploadCollectionSlug } from "payload";
|
|
5
5
|
|
|
6
6
|
//#region src/components/uploadHandler/uploadHandler.d.ts
|
|
7
7
|
type ClientUploadHandlerProps<T extends Record<string, unknown>> = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
|
-
import { Mux as Mux$1 } from "@mux/mux-node";
|
|
3
2
|
import { PayloadHandler } from "payload";
|
|
3
|
+
import { Mux as Mux$1 } from "@mux/mux-node";
|
|
4
4
|
|
|
5
5
|
//#region src/endpoints/muxCreateUploadHandler.d.ts
|
|
6
6
|
interface GetMuxCreateUploadHandlerArgs {
|
|
@@ -24,9 +24,19 @@ function getTusPostProcessorHandler(args) {
|
|
|
24
24
|
if (media.storage !== "s3") return Response.json({ message: "Asset not stored on S3, skipping …" }, { status: 200 });
|
|
25
25
|
const s3Store = getS3Store();
|
|
26
26
|
const matchedId = media?.id;
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const {
|
|
27
|
+
const Key = media.path ?? media.filename ?? "";
|
|
28
|
+
const Bucket = s3Store.bucket;
|
|
29
|
+
const { Body } = await s3Store.client.getObject({
|
|
30
|
+
Key,
|
|
31
|
+
Bucket,
|
|
32
|
+
Range: "bytes=0-524288"
|
|
33
|
+
});
|
|
34
|
+
if (!Body) {
|
|
35
|
+
logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message);
|
|
36
|
+
return Response.json({ message: "Failed to process asset" }, { status: 500 });
|
|
37
|
+
}
|
|
38
|
+
const byteArray = await Body.transformToByteArray();
|
|
39
|
+
const { width, height } = imageSize(Buffer.from(byteArray));
|
|
30
40
|
if (width && height) await payload.update({
|
|
31
41
|
collection,
|
|
32
42
|
id: matchedId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tusPostProcessorHandler.mjs","names":[],"sources":["../../src/endpoints/tusPostProcessorHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\nimport { imageSize } from 'image-size'\nimport { sanitizeFilename } from '../utils/file'\n\ninterface GetTusPostProcessorHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getTusPostProcessorHandler(\n args: GetTusPostProcessorHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n const sanitizedFilename = sanitizeFilename(filename)\n\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: sanitizedFilename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n const media = docs?.[0]\n\n if (!media) {\n throwError(MediaCloudErrors.FILE_NOT_FOUND)\n }\n\n if (media.storage !== 's3') {\n return Response.json(\n { message: 'Asset not stored on S3, skipping …' },\n { status: 200 }\n )\n }\n\n const s3Store = getS3Store()\n const matchedId = media?.id\n\n const
|
|
1
|
+
{"version":3,"file":"tusPostProcessorHandler.mjs","names":[],"sources":["../../src/endpoints/tusPostProcessorHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\nimport { imageSize } from 'image-size'\nimport { sanitizeFilename } from '../utils/file'\n\ninterface GetTusPostProcessorHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getTusPostProcessorHandler(\n args: GetTusPostProcessorHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n const sanitizedFilename = sanitizeFilename(filename)\n\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: sanitizedFilename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n const media = docs?.[0]\n\n if (!media) {\n throwError(MediaCloudErrors.FILE_NOT_FOUND)\n }\n\n if (media.storage !== 's3') {\n return Response.json(\n { message: 'Asset not stored on S3, skipping …' },\n { status: 200 }\n )\n }\n\n const s3Store = getS3Store()\n const matchedId = media?.id\n\n const Key = media.path ?? media.filename ?? ''\n const Bucket = s3Store.bucket\n const Range = 'bytes=0-524288' // ONLY fetch the first 512KB\n\n const { Body } = await s3Store.client.getObject({\n Key,\n Bucket,\n Range,\n })\n\n if (!Body) {\n logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message)\n return Response.json(\n { message: 'Failed to process asset' },\n { status: 500 }\n )\n }\n\n const byteArray = await Body.transformToByteArray()\n const buffer = Buffer.from(byteArray)\n\n const dimensions = imageSize(buffer)\n const { width, height } = dimensions\n\n if (width && height) {\n await payload.update({\n collection,\n id: matchedId,\n data: {\n width,\n height,\n },\n })\n }\n\n return Response.json({ message: 'Asset processed' }, { status: 200 })\n } catch (_error) {\n logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message)\n return Response.json(\n { message: 'Failed to process asset' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";;;;;;AAaA,SAAgB,2BACd,MACgB;CAChB,MAAM,EAAE,YAAY,eAAe;CACnC,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,QAAO,eAAgB,KAAK;AAC1B,MAAI;GACF,MAAM,EAAE,aAAa,YAAY;GACjC,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACH,YAAW,iBAAiB,kBAAkB;GAGhD,MAAM,oBAAoB,iBAAiB,SAAS;GAEpD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;IAClC;IACA,OAAO,EACL,UAAU,EACR,QAAQ,mBACT,EACF;IACD,OAAO;IACP,YAAY;IACb,CAAC;GAEF,MAAM,QAAQ,OAAO;AAErB,OAAI,CAAC,MACH,YAAW,iBAAiB,eAAe;AAG7C,OAAI,MAAM,YAAY,KACpB,QAAO,SAAS,KACd,EAAE,SAAS,sCAAsC,EACjD,EAAE,QAAQ,KAAK,CAChB;GAGH,MAAM,UAAU,YAAY;GAC5B,MAAM,YAAY,OAAO;GAEzB,MAAM,MAAM,MAAM,QAAQ,MAAM,YAAY;GAC5C,MAAM,SAAS,QAAQ;GAGvB,MAAM,EAAE,SAAS,MAAM,QAAQ,OAAO,UAAU;IAC9C;IACA;IACA,OALY;IAMb,CAAC;AAEF,OAAI,CAAC,MAAM;AACT,aAAS,iBAAiB,sBAAsB,QAAQ;AACxD,WAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB;;GAGH,MAAM,YAAY,MAAM,KAAK,sBAAsB;GAInD,MAAM,EAAE,OAAO,WADI,UAFJ,OAAO,KAAK,UAAU,CAED;AAGpC,OAAI,SAAS,OACX,OAAM,QAAQ,OAAO;IACnB;IACA,IAAI;IACJ,MAAM;KACJ;KACA;KACD;IACF,CAAC;AAGJ,UAAO,SAAS,KAAK,EAAE,SAAS,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;WAC9D,QAAQ;AACf,YAAS,iBAAiB,sBAAsB,QAAQ;AACxD,UAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -183,8 +183,8 @@ var S3PartsManager = class {
|
|
|
183
183
|
fileReader.on("end", () => {
|
|
184
184
|
fs.unlink(filePath, () => {});
|
|
185
185
|
});
|
|
186
|
-
fileReader.on("error", (
|
|
187
|
-
fileReader.destroy(
|
|
186
|
+
fileReader.on("error", (error) => {
|
|
187
|
+
fileReader.destroy(error);
|
|
188
188
|
fs.unlink(filePath, () => {});
|
|
189
189
|
});
|
|
190
190
|
}
|
|
@@ -288,17 +288,13 @@ var S3PartsManager = class {
|
|
|
288
288
|
const promises = [];
|
|
289
289
|
let pendingChunkFilepath = null;
|
|
290
290
|
let bytesUploaded = 0;
|
|
291
|
-
let permit = void 0;
|
|
292
291
|
const splitterStream = new StreamSplitter({
|
|
293
292
|
chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),
|
|
294
293
|
directory: os.tmpdir()
|
|
295
|
-
}).on("beforeChunkStarted", async () => {
|
|
296
|
-
permit = await this.partUploadSemaphore.acquire();
|
|
297
294
|
}).on("chunkStarted", (filepath) => {
|
|
298
295
|
pendingChunkFilepath = filepath;
|
|
299
296
|
}).on("chunkFinished", ({ path, size: partSize }) => {
|
|
300
297
|
pendingChunkFilepath = null;
|
|
301
|
-
const acquiredPermit = permit;
|
|
302
298
|
const partNumber = currentPartNumber++;
|
|
303
299
|
offset += partSize;
|
|
304
300
|
const isFinalPart = size === offset;
|
|
@@ -306,7 +302,8 @@ var S3PartsManager = class {
|
|
|
306
302
|
try {
|
|
307
303
|
const readable = fs.createReadStream(path);
|
|
308
304
|
readable.on("error", function(error) {
|
|
309
|
-
|
|
305
|
+
readable.destroy(error);
|
|
306
|
+
fs.unlink(path, () => {});
|
|
310
307
|
});
|
|
311
308
|
switch (true) {
|
|
312
309
|
case partSize >= this.minPartSize || isFinalPart:
|
|
@@ -330,13 +327,10 @@ var S3PartsManager = class {
|
|
|
330
327
|
throw mappedError;
|
|
331
328
|
} finally {
|
|
332
329
|
fs.promises.rm(path).catch(function() {});
|
|
333
|
-
acquiredPermit?.();
|
|
334
330
|
}
|
|
335
331
|
};
|
|
336
332
|
const deferred = uploadChunk();
|
|
337
333
|
promises.push(deferred);
|
|
338
|
-
}).on("chunkError", () => {
|
|
339
|
-
permit?.();
|
|
340
334
|
});
|
|
341
335
|
try {
|
|
342
336
|
await stream.promises.pipeline(readStream, splitterStream);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"partsManager.mjs","names":["client: S3","bucket: string","minPartSize: number","partUploadSemaphore: Semaphore","metadataManager: S3MetadataManager","fileOperations: S3FileOperations","generateCompleteTag: (value: 'false' | 'true') => string | undefined","params: AWS.ListPartsCommandInput","params: AWS.UploadPartCommandInput","promises: Promise<void>[]","pendingChunkFilepath: null | string","permit: SemaphorePermit | undefined"],"sources":["../../../../src/tus/stores/s3/partsManager.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport stream from 'node:stream'\n\nimport { NoSuchKey, NotFound, type S3 } from '@aws-sdk/client-s3'\nimport { StreamSplitter } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { Readable } from 'node:stream'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './fileOperations'\nimport type { S3MetadataManager } from './metadataManager'\nimport type { Semaphore, SemaphorePermit } from './semaphore'\n\ntype RetrievePartsArgs = {\n id: string\n partNumberMarker?: string\n}\n\ntype FinishMultipartUploadArgs = {\n metadata: TusUploadMetadata\n parts: Array<AWS.Part>\n}\n\ntype GetIncompletePartArgs = {\n id: string\n}\n\ntype GetIncompletePartSizeArgs = {\n id: string\n}\n\ntype DeleteIncompletePartArgs = {\n id: string\n}\n\ntype DownloadIncompletePartArgs = {\n id: string\n}\n\ntype UploadIncompletePartArgs = {\n id: string\n readStream: fs.ReadStream | Readable\n}\n\ntype UploadPartArgs = {\n metadata: TusUploadMetadata\n readStream: fs.ReadStream | Readable\n partNumber: number\n}\n\ntype UploadPartsArgs = {\n metadata: TusUploadMetadata\n readStream: stream.Readable\n currentPartNumber: number\n offset: number\n}\n\nconst { log, throwError } = useErrorHandler()\n\nexport class S3PartsManager {\n constructor(\n private client: S3,\n private bucket: string,\n private minPartSize: number,\n private partUploadSemaphore: Semaphore,\n private metadataManager: S3MetadataManager,\n private fileOperations: S3FileOperations,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n\n /**\n * Gets the number of complete parts/chunks already uploaded to S3.\n * Retrieves only consecutive parts.\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.partNumberMarker - Marker for pagination (optional)\n * @returns Promise that resolves to array of uploaded parts\n */\n async retrieveParts(args: RetrievePartsArgs): Promise<Array<AWS.Part>> {\n const { id, partNumberMarker } = args\n const metadata = await this.metadataManager.getMetadata({ id })\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.ListPartsCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumberMarker: partNumberMarker,\n UploadId: metadata['upload-id'],\n }\n\n const data = await this.client.listParts(params)\n\n let parts = data.Parts ?? []\n\n if (data.IsTruncated) {\n const rest = await this.retrieveParts({\n id,\n partNumberMarker: data.NextPartNumberMarker,\n })\n parts = [...parts, ...rest]\n }\n\n if (!partNumberMarker) {\n parts.sort((a, b) => (a.PartNumber || 0) - (b.PartNumber || 0))\n }\n\n return parts\n }\n\n /**\n * Completes a multipart upload on S3.\n * This is where S3 concatenates all the uploaded parts.\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.parts - Array of uploaded parts to complete\n * @returns Promise that resolves to the location URL (optional)\n */\n async finishMultipartUpload(\n args: FinishMultipartUploadArgs\n ): Promise<string | undefined> {\n const { metadata, parts } = args\n\n const params = {\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n Bucket: this.bucket,\n MultipartUpload: {\n Parts: parts.map((part) => {\n return {\n ETag: part.ETag,\n PartNumber: part.PartNumber,\n }\n }),\n },\n UploadId: metadata['upload-id'],\n }\n\n try {\n const result = await this.client.completeMultipartUpload(params)\n return result.Location\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw new Error() // This will never execute but satisfies TypeScript\n }\n }\n\n /**\n * Gets incomplete part from S3\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to readable stream or undefined if not found\n */\n async getIncompletePart(\n args: GetIncompletePartArgs\n ): Promise<Readable | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n })\n return data.Body as Readable\n } catch (error) {\n if (error instanceof NoSuchKey) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Gets the size of an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to part size or undefined if not found\n */\n async getIncompletePartSize(\n args: GetIncompletePartSizeArgs\n ): Promise<number | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.headObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n return data.ContentLength\n } catch (error) {\n if (error instanceof NotFound) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Deletes an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves when deletion is complete\n */\n async deleteIncompletePart(args: DeleteIncompletePartArgs): Promise<void> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n } catch (error) {\n throwError({\n ...MediaCloudErrors.S3_DELETE_ERROR,\n cause: error,\n })\n }\n }\n\n /**\n * Downloads incomplete part to temporary file\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to incomplete part info or undefined if not found\n */\n async downloadIncompletePart(\n args: DownloadIncompletePartArgs\n ): Promise<IncompletePartInfo | undefined> {\n const { id } = args\n const incompletePart = await this.getIncompletePart({ id })\n\n if (!incompletePart) {\n return\n }\n const filePath = await this.fileOperations.generateUniqueTmpFileName({\n template: 'tus-s3-incomplete-part-',\n })\n\n try {\n let incompletePartSize = 0\n\n const byteCounterTransform = new stream.Transform({\n transform(chunk, _, callback) {\n incompletePartSize += chunk.length\n callback(null, chunk)\n },\n })\n\n // Write to temporary file\n await stream.promises.pipeline(\n incompletePart,\n byteCounterTransform,\n fs.createWriteStream(filePath)\n )\n\n const createReadStream = (options: { cleanUpOnEnd: boolean }) => {\n const fileReader = fs.createReadStream(filePath)\n\n if (options.cleanUpOnEnd) {\n fileReader.on('end', () => {\n fs.unlink(filePath, () => {})\n })\n\n fileReader.on('error', (err) => {\n fileReader.destroy(err)\n fs.unlink(filePath, () => {})\n })\n }\n\n return fileReader\n }\n\n return {\n createReader: createReadStream,\n path: filePath,\n size: incompletePartSize,\n }\n } catch (err) {\n fs.promises.rm(filePath).catch(() => {})\n throw err\n }\n }\n\n /**\n * Uploads an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.readStream - The stream to read data from\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadIncompletePart(args: UploadIncompletePartArgs): Promise<string> {\n const { id, readStream } = args\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.putObject({\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n return data.ETag as string\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n }\n }\n\n /**\n * Uploads a single part\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.partNumber - The part number to upload\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadPart(args: UploadPartArgs): Promise<AWS.Part> {\n const { metadata, readStream, partNumber } = args\n const permit = await this.partUploadSemaphore.acquire()\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.UploadPartCommandInput = {\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumber: partNumber,\n UploadId: metadata['upload-id'],\n }\n\n try {\n const data = await this.client.uploadPart(params)\n return { ETag: data.ETag, PartNumber: partNumber }\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n } finally {\n permit()\n }\n }\n\n /**\n * Uploads a stream to s3 using multiple parts\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.currentPartNumber - The current part number to start from\n * @param args.offset - The byte offset to start from\n * @returns Promise that resolves to the number of bytes uploaded\n */\n async uploadParts(args: UploadPartsArgs): Promise<number> {\n const { metadata, readStream, offset: initialOffset } = args\n let { currentPartNumber } = args\n let offset = initialOffset\n const size = metadata.file.size\n const promises: Promise<void>[] = []\n let pendingChunkFilepath: null | string = null\n let bytesUploaded = 0\n let permit: SemaphorePermit | undefined = undefined\n\n const splitterStream = new StreamSplitter({\n chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),\n directory: os.tmpdir(),\n })\n .on('beforeChunkStarted', async () => {\n permit = await this.partUploadSemaphore.acquire()\n })\n .on('chunkStarted', (filepath) => {\n pendingChunkFilepath = filepath\n })\n .on('chunkFinished', ({ path, size: partSize }) => {\n pendingChunkFilepath = null\n\n const acquiredPermit = permit\n const partNumber = currentPartNumber++\n\n offset += partSize\n\n const isFinalPart = size === offset\n\n const uploadChunk = async () => {\n try {\n // Only the first chunk of each PATCH request can prepend\n // an incomplete part (last chunk) from the previous request.\n const readable = fs.createReadStream(path)\n readable.on('error', function (error) {\n throw error\n })\n\n switch (true) {\n case partSize >= this.minPartSize || isFinalPart:\n await this.uploadPart({\n metadata,\n readStream: readable,\n partNumber,\n })\n break\n default:\n await this.uploadIncompletePart({\n id: metadata.file.id,\n readStream: readable,\n })\n break\n }\n\n bytesUploaded += partSize\n } catch (error) {\n // Destroy the splitter to stop processing more chunks\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n splitterStream.destroy(mappedError)\n throw mappedError\n } finally {\n fs.promises.rm(path).catch(function () {})\n acquiredPermit?.()\n }\n }\n\n const deferred = uploadChunk()\n\n promises.push(deferred)\n })\n .on('chunkError', () => {\n permit?.()\n })\n\n try {\n await stream.promises.pipeline(readStream, splitterStream)\n } catch (error) {\n if (pendingChunkFilepath !== null) {\n try {\n await fs.promises.rm(pendingChunkFilepath)\n } catch {\n log(MediaCloudLogs.S3_STORE_CHUNK_REMOVAL_FAILED)\n }\n }\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n promises.push(Promise.reject(mappedError))\n } finally {\n // Wait for all promises\n await Promise.allSettled(promises)\n // Reject the promise if any of the promises reject\n await Promise.all(promises)\n }\n\n return bytesUploaded\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"partsManager.mjs","names":["client: S3","bucket: string","minPartSize: number","partUploadSemaphore: Semaphore","metadataManager: S3MetadataManager","fileOperations: S3FileOperations","generateCompleteTag: (value: 'false' | 'true') => string | undefined","params: AWS.ListPartsCommandInput","params: AWS.UploadPartCommandInput","promises: Promise<void>[]","pendingChunkFilepath: null | string"],"sources":["../../../../src/tus/stores/s3/partsManager.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport stream from 'node:stream'\n\nimport { NoSuchKey, NotFound, type S3 } from '@aws-sdk/client-s3'\nimport { StreamSplitter } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { Readable } from 'node:stream'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './fileOperations'\nimport type { S3MetadataManager } from './metadataManager'\nimport type { Semaphore, SemaphorePermit } from './semaphore'\n\ntype RetrievePartsArgs = {\n id: string\n partNumberMarker?: string\n}\n\ntype FinishMultipartUploadArgs = {\n metadata: TusUploadMetadata\n parts: Array<AWS.Part>\n}\n\ntype GetIncompletePartArgs = {\n id: string\n}\n\ntype GetIncompletePartSizeArgs = {\n id: string\n}\n\ntype DeleteIncompletePartArgs = {\n id: string\n}\n\ntype DownloadIncompletePartArgs = {\n id: string\n}\n\ntype UploadIncompletePartArgs = {\n id: string\n readStream: fs.ReadStream | Readable\n}\n\ntype UploadPartArgs = {\n metadata: TusUploadMetadata\n readStream: fs.ReadStream | Readable\n partNumber: number\n}\n\ntype UploadPartsArgs = {\n metadata: TusUploadMetadata\n readStream: stream.Readable\n currentPartNumber: number\n offset: number\n}\n\nconst { log, throwError } = useErrorHandler()\n\nexport class S3PartsManager {\n constructor(\n private client: S3,\n private bucket: string,\n private minPartSize: number,\n private partUploadSemaphore: Semaphore,\n private metadataManager: S3MetadataManager,\n private fileOperations: S3FileOperations,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n\n /**\n * Gets the number of complete parts/chunks already uploaded to S3.\n * Retrieves only consecutive parts.\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.partNumberMarker - Marker for pagination (optional)\n * @returns Promise that resolves to array of uploaded parts\n */\n async retrieveParts(args: RetrievePartsArgs): Promise<Array<AWS.Part>> {\n const { id, partNumberMarker } = args\n const metadata = await this.metadataManager.getMetadata({ id })\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.ListPartsCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumberMarker: partNumberMarker,\n UploadId: metadata['upload-id'],\n }\n\n const data = await this.client.listParts(params)\n\n let parts = data.Parts ?? []\n\n if (data.IsTruncated) {\n const rest = await this.retrieveParts({\n id,\n partNumberMarker: data.NextPartNumberMarker,\n })\n parts = [...parts, ...rest]\n }\n\n if (!partNumberMarker) {\n parts.sort((a, b) => (a.PartNumber || 0) - (b.PartNumber || 0))\n }\n\n return parts\n }\n\n /**\n * Completes a multipart upload on S3.\n * This is where S3 concatenates all the uploaded parts.\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.parts - Array of uploaded parts to complete\n * @returns Promise that resolves to the location URL (optional)\n */\n async finishMultipartUpload(\n args: FinishMultipartUploadArgs\n ): Promise<string | undefined> {\n const { metadata, parts } = args\n\n const params = {\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n Bucket: this.bucket,\n MultipartUpload: {\n Parts: parts.map((part) => {\n return {\n ETag: part.ETag,\n PartNumber: part.PartNumber,\n }\n }),\n },\n UploadId: metadata['upload-id'],\n }\n\n try {\n const result = await this.client.completeMultipartUpload(params)\n return result.Location\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw new Error() // This will never execute but satisfies TypeScript\n }\n }\n\n /**\n * Gets incomplete part from S3\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to readable stream or undefined if not found\n */\n async getIncompletePart(\n args: GetIncompletePartArgs\n ): Promise<Readable | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n })\n return data.Body as Readable\n } catch (error) {\n if (error instanceof NoSuchKey) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Gets the size of an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to part size or undefined if not found\n */\n async getIncompletePartSize(\n args: GetIncompletePartSizeArgs\n ): Promise<number | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.headObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n return data.ContentLength\n } catch (error) {\n if (error instanceof NotFound) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Deletes an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves when deletion is complete\n */\n async deleteIncompletePart(args: DeleteIncompletePartArgs): Promise<void> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n } catch (error) {\n throwError({\n ...MediaCloudErrors.S3_DELETE_ERROR,\n cause: error,\n })\n }\n }\n\n /**\n * Downloads incomplete part to temporary file\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to incomplete part info or undefined if not found\n */\n async downloadIncompletePart(\n args: DownloadIncompletePartArgs\n ): Promise<IncompletePartInfo | undefined> {\n const { id } = args\n const incompletePart = await this.getIncompletePart({ id })\n\n if (!incompletePart) {\n return\n }\n const filePath = await this.fileOperations.generateUniqueTmpFileName({\n template: 'tus-s3-incomplete-part-',\n })\n\n try {\n let incompletePartSize = 0\n\n const byteCounterTransform = new stream.Transform({\n transform(chunk, _, callback) {\n incompletePartSize += chunk.length\n callback(null, chunk)\n },\n })\n\n // Write to temporary file\n await stream.promises.pipeline(\n incompletePart,\n byteCounterTransform,\n fs.createWriteStream(filePath)\n )\n\n const createReadStream = (options: { cleanUpOnEnd: boolean }) => {\n const fileReader = fs.createReadStream(filePath)\n\n if (options.cleanUpOnEnd) {\n fileReader.on('end', () => {\n fs.unlink(filePath, () => {})\n })\n\n fileReader.on('error', (error) => {\n fileReader.destroy(error)\n fs.unlink(filePath, () => {})\n })\n }\n\n return fileReader\n }\n\n return {\n createReader: createReadStream,\n path: filePath,\n size: incompletePartSize,\n }\n } catch (err) {\n fs.promises.rm(filePath).catch(() => {})\n throw err\n }\n }\n\n /**\n * Uploads an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.readStream - The stream to read data from\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadIncompletePart(args: UploadIncompletePartArgs): Promise<string> {\n const { id, readStream } = args\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.putObject({\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n return data.ETag as string\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n }\n }\n\n /**\n * Uploads a single part\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.partNumber - The part number to upload\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadPart(args: UploadPartArgs): Promise<AWS.Part> {\n const { metadata, readStream, partNumber } = args\n const permit = await this.partUploadSemaphore.acquire()\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.UploadPartCommandInput = {\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumber: partNumber,\n UploadId: metadata['upload-id'],\n }\n\n try {\n const data = await this.client.uploadPart(params)\n return { ETag: data.ETag, PartNumber: partNumber }\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n } finally {\n permit()\n }\n }\n\n /**\n * Uploads a stream to s3 using multiple parts\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.currentPartNumber - The current part number to start from\n * @param args.offset - The byte offset to start from\n * @returns Promise that resolves to the number of bytes uploaded\n */\n async uploadParts(args: UploadPartsArgs): Promise<number> {\n const { metadata, readStream, offset: initialOffset } = args\n let { currentPartNumber } = args\n let offset = initialOffset\n const size = metadata.file.size\n const promises: Promise<void>[] = []\n let pendingChunkFilepath: null | string = null\n let bytesUploaded = 0\n\n const splitterStream = new StreamSplitter({\n chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),\n directory: os.tmpdir(),\n })\n .on('chunkStarted', (filepath) => {\n pendingChunkFilepath = filepath\n })\n .on('chunkFinished', ({ path, size: partSize }) => {\n pendingChunkFilepath = null\n\n const partNumber = currentPartNumber++\n\n offset += partSize\n\n const isFinalPart = size === offset\n\n const uploadChunk = async () => {\n try {\n // Only the first chunk of each PATCH request can prepend\n // an incomplete part (last chunk) from the previous request.\n const readable = fs.createReadStream(path)\n readable.on('error', function (error) {\n readable.destroy(error)\n fs.unlink(path, () => {})\n })\n\n switch (true) {\n case partSize >= this.minPartSize || isFinalPart:\n await this.uploadPart({\n metadata,\n readStream: readable,\n partNumber,\n })\n break\n default:\n await this.uploadIncompletePart({\n id: metadata.file.id,\n readStream: readable,\n })\n break\n }\n\n bytesUploaded += partSize\n } catch (error) {\n // Destroy the splitter to stop processing more chunks\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n splitterStream.destroy(mappedError)\n throw mappedError\n } finally {\n fs.promises.rm(path).catch(function () {})\n }\n }\n\n const deferred = uploadChunk()\n\n promises.push(deferred)\n })\n\n try {\n await stream.promises.pipeline(readStream, splitterStream)\n } catch (error) {\n if (pendingChunkFilepath !== null) {\n try {\n await fs.promises.rm(pendingChunkFilepath)\n } catch {\n log(MediaCloudLogs.S3_STORE_CHUNK_REMOVAL_FAILED)\n }\n }\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n promises.push(Promise.reject(mappedError))\n } finally {\n // Wait for all promises\n await Promise.allSettled(promises)\n // Reject the promise if any of the promises reject\n await Promise.all(promises)\n }\n\n return bytesUploaded\n }\n}\n"],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ import { NoSuchKey, NotFound, S3 } from "@aws-sdk/client-s3";
|
|
|
10
10
|
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
|
11
11
|
import { Agent } from "https";
|
|
12
12
|
import { DataStore, ERRORS, TUS_RESUMABLE, Upload } from "@tus/utils";
|
|
13
|
+
import { logError } from "payload";
|
|
13
14
|
|
|
14
15
|
//#region src/tus/stores/s3/s3Store.ts
|
|
15
16
|
const { log } = useErrorHandler();
|
|
@@ -57,7 +58,9 @@ var S3Store = class extends DataStore {
|
|
|
57
58
|
this.fileOperations = new S3FileOperations(this.maxMultipartParts, this.maxUploadSize, this.minPartSize, this.partSize);
|
|
58
59
|
this.partsManager = new S3PartsManager(this.client, this.bucket, this.minPartSize, this.partUploadSemaphore, this.metadataManager, this.fileOperations, this.generateCompleteTag.bind(this));
|
|
59
60
|
this.expirationManager = new S3ExpirationManager(this.client, this.bucket, this.expirationPeriodInMilliseconds, this.metadataManager.generateInfoKey.bind(this.metadataManager), this.metadataManager.generatePartKey.bind(this.metadataManager));
|
|
60
|
-
this.deleteExpired()
|
|
61
|
+
this.deleteExpired().catch((error) => {
|
|
62
|
+
logError(error);
|
|
63
|
+
});
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
63
66
|
* Helper method to check if expiration tags should be used
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3Store.mjs","names":["mappedS3ClientConfig: AWS.S3ClientConfig","HttpsAgent","request: AWS.CreateMultipartUploadCommandInput","metadata: TusUploadMetadata","offset: number","lastError: Error | null","region: string"],"sources":["../../../../src/tus/stores/s3/s3Store.ts"],"sourcesContent":["import { NoSuchKey, NotFound, S3 } from '@aws-sdk/client-s3'\nimport { NodeHttpHandler } from '@smithy/node-http-handler'\nimport { Agent as HttpsAgent } from 'https'\nimport { DataStore, ERRORS, TUS_RESUMABLE, Upload } from '@tus/utils'\nimport stream, { type Readable } from 'node:stream'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { Semaphore } from './semaphore'\nimport { S3ExpirationManager } from './expirationManager'\nimport { S3FileOperations } from './fileOperations'\nimport { S3MetadataManager } from './metadataManager'\nimport { S3PartsManager } from './partsManager'\n\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { AWSError, S3StoreConfig, TusUploadMetadata } from '../../../types'\n\nconst { log } = useErrorHandler()\n\nexport class S3Store extends DataStore {\n public client: S3\n public bucket: string\n public partSize = 8 * 1024 * 1024 // 8MB preferred part size\n public minPartSize = 5 * 1024 * 1024 // 5MB minimum part size\n public maxMultipartParts = 10_000\n public maxUploadSize = 5_497_558_138_880 as const // 5TiB\n public useTags = false\n public expirationPeriodInMilliseconds = 0\n protected acl?: string\n\n protected partUploadSemaphore: Semaphore\n protected metadataManager: S3MetadataManager\n protected fileOperations: S3FileOperations\n protected partsManager: S3PartsManager\n protected expirationManager: S3ExpirationManager\n protected customEndpoint: string\n\n constructor(options: S3StoreConfig) {\n super()\n const {\n maxMultipartParts,\n minPartSize,\n partSize,\n s3ClientConfig,\n maxConcurrentPartUploads,\n useTags,\n expirationPeriodInMilliseconds,\n } = options\n const { acl, bucket, ...restS3ClientConfig } = s3ClientConfig\n\n this.extensions = [\n 'creation',\n 'creation-with-upload',\n 'creation-defer-length',\n 'termination',\n 'expiration',\n ]\n\n const mappedS3ClientConfig: AWS.S3ClientConfig = {\n ...restS3ClientConfig,\n requestHandler: new NodeHttpHandler({\n connectionTimeout: 60000,\n requestTimeout: 300000,\n httpsAgent: new HttpsAgent({\n maxSockets: 1000,\n keepAlive: true,\n keepAliveMsecs: 60000,\n }),\n }),\n }\n\n this.bucket = bucket\n this.acl = acl\n this.client = new S3(mappedS3ClientConfig)\n this.customEndpoint = String(restS3ClientConfig.endpoint)\n\n this.partSize = partSize ?? this.partSize\n this.minPartSize = minPartSize ?? this.minPartSize\n this.maxMultipartParts = maxMultipartParts ?? this.maxMultipartParts\n\n this.useTags = useTags ?? this.useTags\n this.expirationPeriodInMilliseconds =\n expirationPeriodInMilliseconds ?? this.expirationPeriodInMilliseconds\n this.partUploadSemaphore = new Semaphore(maxConcurrentPartUploads ?? 60)\n\n // Initialize component managers\n this.metadataManager = new S3MetadataManager(\n this.client,\n this.bucket,\n this.shouldUseExpirationTags.bind(this),\n this.generateCompleteTag.bind(this)\n )\n\n this.fileOperations = new S3FileOperations(\n this.maxMultipartParts,\n this.maxUploadSize,\n this.minPartSize,\n this.partSize\n )\n\n this.partsManager = new S3PartsManager(\n this.client,\n this.bucket,\n this.minPartSize,\n this.partUploadSemaphore,\n this.metadataManager,\n this.fileOperations,\n this.generateCompleteTag.bind(this)\n )\n\n this.expirationManager = new S3ExpirationManager(\n this.client,\n this.bucket,\n this.expirationPeriodInMilliseconds,\n this.metadataManager.generateInfoKey.bind(this.metadataManager),\n this.metadataManager.generatePartKey.bind(this.metadataManager)\n )\n\n // Cleanup expired uploads when the store is initialized\n this.deleteExpired()\n }\n\n /**\n * Helper method to check if expiration tags should be used\n * @returns True if expiration tags should be used\n */\n protected shouldUseExpirationTags(): boolean {\n return this.expirationPeriodInMilliseconds !== 0 && this.useTags\n }\n\n /**\n * Generates a tag for marking complete/incomplete uploads\n * @param value - Either 'false' or 'true' to mark completion status\n * @returns The tag string or undefined if tags shouldn’t be used\n */\n protected generateCompleteTag(value: 'false' | 'true'): string | undefined {\n if (!this.shouldUseExpirationTags()) {\n return undefined\n }\n return `Tus-Completed=${value}`\n }\n\n /**\n * Creates a multipart upload on S3 attaching any metadata to it.\n * Also, a `${file_id}.info` file is created which holds some information\n * about the upload itself like: `upload-id`, `upload-length`, etc.\n * @param upload - The upload object to create\n * @return Promise that resolves to the created upload object with storage information\n */\n public async create(upload: Upload): Promise<Upload> {\n log(MediaCloudLogs.S3_STORE_MULTIPART_INIT)\n\n const request: AWS.CreateMultipartUploadCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: upload.id,\n prefix: upload.metadata?.prefix ?? undefined,\n }),\n Metadata: { 'tus-version': TUS_RESUMABLE },\n }\n\n if (upload.metadata?.contentType) {\n request.ContentType = upload.metadata.contentType as string\n }\n\n if (upload.metadata?.cacheControl) {\n request.CacheControl = upload.metadata.cacheControl as string\n }\n\n if (this.acl) {\n request.ACL = this.acl as AWS.ObjectCannedACL\n }\n\n upload.creation_date = new Date().toISOString()\n\n const response = await this.client.createMultipartUpload(request)\n\n upload.storage = {\n type: 's3',\n bucket: this.bucket,\n path: response.Key as string,\n }\n await this.metadataManager.saveMetadata({\n upload,\n uploadId: response.UploadId as string,\n })\n log(MediaCloudLogs.S3_STORE_MULTIPART_CREATED)\n\n return upload\n }\n\n /**\n * Writes `buffer` to the file specified by the upload’s `id` at `offset`\n * @param readable - The readable stream to write\n * @param id - The upload ID\n * @param offset - The byte offset to write at\n * @return Promise that resolves to the new offset after writing the data\n */\n public async write(\n readable: stream.Readable,\n id: string,\n offset: number\n ): Promise<number> {\n const metadata = await this.metadataManager.getMetadata({ id })\n\n // TUS sends PATCH requests with an `upload-offset` header.\n // Offset the write by the offset in the PATCH request.\n const calculatedOffset = this.fileOperations.calculateOffsetFromParts({\n parts: await this.partsManager.retrieveParts({ id }),\n })\n const offsetDiff = offset - calculatedOffset\n const requestedOffset = offset\n\n let finalReadable = readable\n\n if (offsetDiff < 0) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n // If the offset given in the PATCH request is higher than\n // the expected offset, we need to prepend an incomplete\n // part to the readable stream, if one exists.\n if (offsetDiff > 0) {\n const incompletePart = await this.partsManager.downloadIncompletePart({\n id,\n })\n\n if (!incompletePart) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n if (incompletePart.size !== offsetDiff) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n // Clear the incomplete part from S3 since it's going to be combined with the current request\n await this.partsManager.deleteIncompletePart({ id })\n\n // Adjust offset to account for the incomplete part\n offset = requestedOffset - incompletePart.size\n\n finalReadable = stream.Readable.from(\n (async function* () {\n yield* incompletePart.createReader({ cleanUpOnEnd: true })\n yield* readable\n })()\n )\n }\n\n const partNumber = this.fileOperations.calculatePartNumber({\n parts: await this.partsManager.retrieveParts({ id }),\n })\n\n const bytesUploaded = await this.partsManager.uploadParts({\n metadata,\n readStream: finalReadable,\n currentPartNumber: partNumber,\n offset,\n })\n\n // The size of the incomplete part should not be counted, because the\n // process of the incomplete part should be fully transparent to the user.\n const newOffset =\n requestedOffset + bytesUploaded - (offsetDiff > 0 ? offsetDiff : 0)\n\n // Check if the upload is complete\n if (metadata.file.size === newOffset) {\n try {\n const parts = await this.partsManager.retrieveParts({ id })\n await this.partsManager.finishMultipartUpload({ metadata, parts })\n\n // Update the metadata with completed state\n const completedUpload = new Upload({\n ...metadata.file,\n offset: newOffset,\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n\n await this.metadataManager.completeMetadata({ upload: completedUpload })\n } catch (error) {\n log(MediaCloudLogs.S3_STORE_UPLOAD_FAILED)\n throw error\n }\n }\n\n return newOffset\n }\n\n /**\n * Returns the current state of the upload, i.e how much data has been\n * uploaded and if the upload is complete.\n * @param id - The upload ID to retrieve\n * @returns Promise that resolves to the upload object with current offset and storage information\n */\n public async getUpload(id: string): Promise<Upload> {\n let metadata: TusUploadMetadata\n\n try {\n metadata = await this.metadataManager.getMetadata({ id })\n } catch (error) {\n if (\n error instanceof NoSuchKey ||\n error instanceof NotFound ||\n (error as AWSError)?.Code === 'NotFound' ||\n (error as AWSError)?.Code === 'NoSuchKey'\n ) {\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n\n let offset: number\n\n try {\n const parts = await this.partsManager.retrieveParts({ id })\n offset = this.fileOperations.calculateOffsetFromParts({ parts })\n } catch (error) {\n // Check if the error is caused by the upload not being found. This happens\n // when the multipart upload has already been completed or aborted.\n if (\n (error as AWSError)?.Code === 'NoSuchUpload' ||\n (error as AWSError)?.Code === 'NoSuchKey'\n ) {\n return new Upload({\n ...metadata.file,\n metadata: metadata.file.metadata,\n offset: metadata.file.size as number,\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n }\n\n log(MediaCloudLogs.S3_STORE_RETRIEVE_PARTS_ERROR)\n throw error\n }\n\n const incompletePartSize = await this.partsManager.getIncompletePartSize({\n id,\n })\n\n return new Upload({\n ...metadata.file,\n offset: offset + (incompletePartSize ?? 0),\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n }\n\n /**\n * Reads the file specified by the upload’s `id` and returns a readable stream\n * @param id - The upload ID to read\n * @returns Promise that resolves to a readable stream of the file's contents\n */\n async read(id: string): Promise<Readable> {\n log(MediaCloudLogs.S3_STORE_READ_ATTEMPT)\n let retries = 3\n let lastError: Error | null = null\n\n while (retries > 0) {\n try {\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: id,\n })\n log(MediaCloudLogs.S3_STORE_READ_SUCCESS)\n return data.Body as Readable\n } catch (error) {\n log(MediaCloudLogs.S3_STORE_READ_RETRY)\n lastError = error as Error\n retries--\n\n if (retries > 0) {\n // Wait a bit before retrying, in case S3 needs time for consistency\n await new Promise((resolve) => setTimeout(resolve, 100))\n }\n }\n }\n\n log(MediaCloudLogs.S3_STORE_READ_FAILED)\n throw lastError ?? new Error(`Failed to read file ${id}`)\n }\n\n /**\n *\n * Moves the file specified by its `oldKey` to `newKey`.\n * @param oldKey - The current S3 key of the file to be moved\n * @param newKey - The new S3 key to move the file to\n * @return Promise that resolves when the file has been successfully moved, or rejects with an error if the move operation fails\n */\n public async copy(oldKey: string, newKey: string): Promise<void> {\n try {\n await this.client.copyObject({\n Bucket: this.bucket,\n CopySource: encodeURI(`${this.bucket}/${oldKey}`),\n Key: newKey,\n })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: oldKey,\n })\n } catch (error) {\n if (\n (error as AWSError)?.code &&\n ['NoSuchKey', 'NoSuchUpload', 'NotFound'].includes(\n (error as AWSError).Code || ''\n )\n ) {\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error as Error\n }\n }\n\n /**\n * Removes files specified by the upload’s `id`\n * @param id - The upload ID to remove\n * @returns Promise that resolves when the file and its metadata have been removed\n */\n public async remove(id: string): Promise<void> {\n try {\n const { 'upload-id': uploadId, file } =\n await this.metadataManager.getMetadata({ id })\n if (uploadId) {\n await this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: id,\n UploadId: uploadId,\n })\n }\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n },\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\n },\n })\n } catch (error) {\n if (\n (error as AWSError)?.code &&\n ['NoSuchKey', 'NoSuchUpload', 'NotFound'].includes(\n (error as AWSError).Code || ''\n )\n ) {\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n }\n\n /**\n * Removes the .info file specified by the upload’s `id`\n * @param id - The upload ID to clean up\n * @returns Promise that resolves when the metadata file has been removed\n */\n public async cleanup(id: string): Promise<void> {\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\n },\n })\n } catch (error) {\n if (\n (error as AWSError)?.code &&\n ['NoSuchKey', 'NoSuchUpload', 'NotFound'].includes(\n (error as AWSError).Code || ''\n )\n ) {\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n }\n\n /**\n * Combine all multipart uploads into a single object\n * @param id - The upload ID to complete\n * @returns Promise that resolves to the completed upload object with storage information\n */\n public async completeMultipartUpload(id: string): Promise<Upload> {\n const metadata = await this.metadataManager.getMetadata({ id })\n const parts = await this.partsManager.retrieveParts({ id })\n\n const incompletePartInfo = await this.partsManager.downloadIncompletePart({\n id,\n })\n\n if (incompletePartInfo) {\n // Upload the incomplete part as a regular part\n await this.partsManager.uploadPart({\n metadata,\n readStream: incompletePartInfo.createReader({ cleanUpOnEnd: true }),\n partNumber: parts.length + 1,\n })\n\n // Remove the incomplete part\n await this.partsManager.deleteIncompletePart({ id })\n\n // Re-fetch parts to include the newly uploaded part\n const updatedParts = await this.partsManager.retrieveParts({ id })\n await this.partsManager.finishMultipartUpload({\n metadata,\n parts: updatedParts,\n })\n } else {\n await this.partsManager.finishMultipartUpload({ metadata, parts })\n }\n\n const completedUpload = new Upload({\n ...metadata.file,\n offset: metadata.file.size ?? 0,\n size: metadata.file.size ?? 0,\n storage: metadata.file.storage,\n })\n\n await this.metadataManager.completeMetadata({ upload: completedUpload })\n\n return completedUpload\n }\n\n /**\n * Get the full S3 URL for an uploaded file\n * @param id - The upload ID to get the URL for\n * @returns The full URL to access the uploaded file on S3\n */\n public getUrl(id: string): string {\n // Use the custom endpoint if available\n if (this.customEndpoint) {\n return `${this.customEndpoint}/${this.bucket}/${id}`\n }\n\n // Fallback to standard AWS S3 URL format\n const regionConfig = this.client.config.region\n let region: string\n\n // If region is a function, we can't resolve it synchronously, so use a fallback\n if (typeof regionConfig === 'function') {\n region = 'us-east-1' // fallback for sync calls\n } else {\n region = regionConfig || 'us-east-1'\n }\n\n // Standard AWS S3 URL format\n if (region === 'us-east-1') {\n return `https://${this.bucket}.s3.amazonaws.com/${id}`\n } else {\n return `https://${this.bucket}.s3.${region}.amazonaws.com/${id}`\n }\n }\n\n /**\n * Deletes expired incomplete uploads.\n * Returns the number of deleted uploads.\n * @returns Promise that resolves to the number of deleted uploads\n */\n async deleteExpired(): Promise<number> {\n return this.expirationManager.deleteExpired()\n }\n\n /**\n * Returns the expiration period in milliseconds\n * @return The expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationManager.getExpiration()\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAM,EAAE,QAAQ,iBAAiB;AAEjC,IAAa,UAAb,cAA6B,UAAU;CAkBrC,YAAY,SAAwB;AAClC,SAAO;kBAhBS,IAAI,OAAO;qBACR,IAAI,OAAO;2BACL;uBACJ;iBACN;wCACuB;EAYtC,MAAM,EACJ,mBACA,aACA,UACA,gBACA,0BACA,SACA,mCACE;EACJ,MAAM,EAAE,KAAK,QAAQ,GAAG,uBAAuB;AAE/C,OAAK,aAAa;GAChB;GACA;GACA;GACA;GACA;GACD;EAED,MAAMA,uBAA2C;GAC/C,GAAG;GACH,gBAAgB,IAAI,gBAAgB;IAClC,mBAAmB;IACnB,gBAAgB;IAChB,YAAY,IAAIC,MAAW;KACzB,YAAY;KACZ,WAAW;KACX,gBAAgB;KACjB,CAAC;IACH,CAAC;GACH;AAED,OAAK,SAAS;AACd,OAAK,MAAM;AACX,OAAK,SAAS,IAAI,GAAG,qBAAqB;AAC1C,OAAK,iBAAiB,OAAO,mBAAmB,SAAS;AAEzD,OAAK,WAAW,YAAY,KAAK;AACjC,OAAK,cAAc,eAAe,KAAK;AACvC,OAAK,oBAAoB,qBAAqB,KAAK;AAEnD,OAAK,UAAU,WAAW,KAAK;AAC/B,OAAK,iCACH,kCAAkC,KAAK;AACzC,OAAK,sBAAsB,IAAI,UAAU,4BAA4B,GAAG;AAGxE,OAAK,kBAAkB,IAAI,kBACzB,KAAK,QACL,KAAK,QACL,KAAK,wBAAwB,KAAK,KAAK,EACvC,KAAK,oBAAoB,KAAK,KAAK,CACpC;AAED,OAAK,iBAAiB,IAAI,iBACxB,KAAK,mBACL,KAAK,eACL,KAAK,aACL,KAAK,SACN;AAED,OAAK,eAAe,IAAI,eACtB,KAAK,QACL,KAAK,QACL,KAAK,aACL,KAAK,qBACL,KAAK,iBACL,KAAK,gBACL,KAAK,oBAAoB,KAAK,KAAK,CACpC;AAED,OAAK,oBAAoB,IAAI,oBAC3B,KAAK,QACL,KAAK,QACL,KAAK,gCACL,KAAK,gBAAgB,gBAAgB,KAAK,KAAK,gBAAgB,EAC/D,KAAK,gBAAgB,gBAAgB,KAAK,KAAK,gBAAgB,CAChE;AAGD,OAAK,eAAe;;;;;;CAOtB,AAAU,0BAAmC;AAC3C,SAAO,KAAK,mCAAmC,KAAK,KAAK;;;;;;;CAQ3D,AAAU,oBAAoB,OAA6C;AACzE,MAAI,CAAC,KAAK,yBAAyB,CACjC;AAEF,SAAO,iBAAiB;;;;;;;;;CAU1B,MAAa,OAAO,QAAiC;AACnD,MAAI,eAAe,wBAAwB;EAE3C,MAAMC,UAAiD;GACrD,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,gBAAgB;IACxC,IAAI,OAAO;IACX,QAAQ,OAAO,UAAU,UAAU;IACpC,CAAC;GACF,UAAU,EAAE,eAAe,eAAe;GAC3C;AAED,MAAI,OAAO,UAAU,YACnB,SAAQ,cAAc,OAAO,SAAS;AAGxC,MAAI,OAAO,UAAU,aACnB,SAAQ,eAAe,OAAO,SAAS;AAGzC,MAAI,KAAK,IACP,SAAQ,MAAM,KAAK;AAGrB,SAAO,iCAAgB,IAAI,MAAM,EAAC,aAAa;EAE/C,MAAM,WAAW,MAAM,KAAK,OAAO,sBAAsB,QAAQ;AAEjE,SAAO,UAAU;GACf,MAAM;GACN,QAAQ,KAAK;GACb,MAAM,SAAS;GAChB;AACD,QAAM,KAAK,gBAAgB,aAAa;GACtC;GACA,UAAU,SAAS;GACpB,CAAC;AACF,MAAI,eAAe,2BAA2B;AAE9C,SAAO;;;;;;;;;CAUT,MAAa,MACX,UACA,IACA,QACiB;EACjB,MAAM,WAAW,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;EAI/D,MAAM,mBAAmB,KAAK,eAAe,yBAAyB,EACpE,OAAO,MAAM,KAAK,aAAa,cAAc,EAAE,IAAI,CAAC,EACrD,CAAC;EACF,MAAM,aAAa,SAAS;EAC5B,MAAM,kBAAkB;EAExB,IAAI,gBAAgB;AAEpB,MAAI,aAAa,EACf,OAAM,OAAO;AAMf,MAAI,aAAa,GAAG;GAClB,MAAM,iBAAiB,MAAM,KAAK,aAAa,uBAAuB,EACpE,IACD,CAAC;AAEF,OAAI,CAAC,eACH,OAAM,OAAO;AAGf,OAAI,eAAe,SAAS,WAC1B,OAAM,OAAO;AAIf,SAAM,KAAK,aAAa,qBAAqB,EAAE,IAAI,CAAC;AAGpD,YAAS,kBAAkB,eAAe;AAE1C,mBAAgB,OAAO,SAAS,MAC7B,mBAAmB;AAClB,WAAO,eAAe,aAAa,EAAE,cAAc,MAAM,CAAC;AAC1D,WAAO;OACL,CACL;;EAGH,MAAM,aAAa,KAAK,eAAe,oBAAoB,EACzD,OAAO,MAAM,KAAK,aAAa,cAAc,EAAE,IAAI,CAAC,EACrD,CAAC;EAWF,MAAM,YACJ,kBAVoB,MAAM,KAAK,aAAa,YAAY;GACxD;GACA,YAAY;GACZ,mBAAmB;GACnB;GACD,CAAC,IAKmC,aAAa,IAAI,aAAa;AAGnE,MAAI,SAAS,KAAK,SAAS,UACzB,KAAI;GACF,MAAM,QAAQ,MAAM,KAAK,aAAa,cAAc,EAAE,IAAI,CAAC;AAC3D,SAAM,KAAK,aAAa,sBAAsB;IAAE;IAAU;IAAO,CAAC;GAGlE,MAAM,kBAAkB,IAAI,OAAO;IACjC,GAAG,SAAS;IACZ,QAAQ;IACR,MAAM,SAAS,KAAK;IACpB,SAAS,SAAS,KAAK;IACxB,CAAC;AAEF,SAAM,KAAK,gBAAgB,iBAAiB,EAAE,QAAQ,iBAAiB,CAAC;WACjE,OAAO;AACd,OAAI,eAAe,uBAAuB;AAC1C,SAAM;;AAIV,SAAO;;;;;;;;CAST,MAAa,UAAU,IAA6B;EAClD,IAAIC;AAEJ,MAAI;AACF,cAAW,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;WAClD,OAAO;AACd,OACE,iBAAiB,aACjB,iBAAiB,YAChB,OAAoB,SAAS,cAC7B,OAAoB,SAAS,YAE9B,OAAM,OAAO;AAEf,SAAM;;EAGR,IAAIC;AAEJ,MAAI;GACF,MAAM,QAAQ,MAAM,KAAK,aAAa,cAAc,EAAE,IAAI,CAAC;AAC3D,YAAS,KAAK,eAAe,yBAAyB,EAAE,OAAO,CAAC;WACzD,OAAO;AAGd,OACG,OAAoB,SAAS,kBAC7B,OAAoB,SAAS,YAE9B,QAAO,IAAI,OAAO;IAChB,GAAG,SAAS;IACZ,UAAU,SAAS,KAAK;IACxB,QAAQ,SAAS,KAAK;IACtB,MAAM,SAAS,KAAK;IACpB,SAAS,SAAS,KAAK;IACxB,CAAC;AAGJ,OAAI,eAAe,8BAA8B;AACjD,SAAM;;EAGR,MAAM,qBAAqB,MAAM,KAAK,aAAa,sBAAsB,EACvE,IACD,CAAC;AAEF,SAAO,IAAI,OAAO;GAChB,GAAG,SAAS;GACZ,QAAQ,UAAU,sBAAsB;GACxC,MAAM,SAAS,KAAK;GACpB,SAAS,SAAS,KAAK;GACxB,CAAC;;;;;;;CAQJ,MAAM,KAAK,IAA+B;AACxC,MAAI,eAAe,sBAAsB;EACzC,IAAI,UAAU;EACd,IAAIC,YAA0B;AAE9B,SAAO,UAAU,EACf,KAAI;GACF,MAAM,OAAO,MAAM,KAAK,OAAO,UAAU;IACvC,QAAQ,KAAK;IACb,KAAK;IACN,CAAC;AACF,OAAI,eAAe,sBAAsB;AACzC,UAAO,KAAK;WACL,OAAO;AACd,OAAI,eAAe,oBAAoB;AACvC,eAAY;AACZ;AAEA,OAAI,UAAU,EAEZ,OAAM,IAAI,SAAS,YAAY,WAAW,SAAS,IAAI,CAAC;;AAK9D,MAAI,eAAe,qBAAqB;AACxC,QAAM,6BAAa,IAAI,MAAM,uBAAuB,KAAK;;;;;;;;;CAU3D,MAAa,KAAK,QAAgB,QAA+B;AAC/D,MAAI;AACF,SAAM,KAAK,OAAO,WAAW;IAC3B,QAAQ,KAAK;IACb,YAAY,UAAU,GAAG,KAAK,OAAO,GAAG,SAAS;IACjD,KAAK;IACN,CAAC;AAEF,SAAM,KAAK,OAAO,aAAa;IAC7B,QAAQ,KAAK;IACb,KAAK;IACN,CAAC;WACK,OAAO;AACd,OACG,OAAoB,QACrB;IAAC;IAAa;IAAgB;IAAW,CAAC,SACvC,MAAmB,QAAQ,GAC7B,EACD;AACA,QAAI,eAAe,wBAAwB;AAC3C,UAAM,OAAO;;AAEf,SAAM;;;;;;;;CASV,MAAa,OAAO,IAA2B;AAC7C,MAAI;GACF,MAAM,EAAE,aAAa,UAAU,SAC7B,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;AAChD,OAAI,SACF,OAAM,KAAK,OAAO,qBAAqB;IACrC,QAAQ,KAAK;IACb,KAAK;IACL,UAAU;IACX,CAAC;AAGJ,SAAM,KAAK,OAAO,cAAc;IAC9B,QAAQ,KAAK;IACb,QAAQ,EACN,SAAS;KACP,EACE,KAAK,KAAK,gBAAgB,gBAAgB;MACxC;MACA,QAAQ,MAAM,UAAU,UAAU;MACnC,CAAC,EACH;KACD,EACE,KAAK,KAAK,gBAAgB,gBAAgB;MACxC;MACA,QAAQ,MAAM,UAAU,UAAU;MAClC,cAAc;MACf,CAAC,EACH;KACD,EACE,KAAK,KAAK,gBAAgB,gBAAgB,EAAE,IAAI,CAAC,EAClD;KACF,EACF;IACF,CAAC;WACK,OAAO;AACd,OACG,OAAoB,QACrB;IAAC;IAAa;IAAgB;IAAW,CAAC,SACvC,MAAmB,QAAQ,GAC7B,EACD;AACA,QAAI,eAAe,wBAAwB;AAC3C,UAAM,OAAO;;AAEf,SAAM;;;;;;;;CASV,MAAa,QAAQ,IAA2B;AAC9C,MAAI;GACF,MAAM,EAAE,SAAS,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;AAE/D,SAAM,KAAK,OAAO,cAAc;IAC9B,QAAQ,KAAK;IACb,QAAQ,EACN,SAAS,CACP,EACE,KAAK,KAAK,gBAAgB,gBAAgB;KACxC;KACA,QAAQ,MAAM,UAAU,UAAU;KAClC,cAAc;KACf,CAAC,EACH,EACD,EACE,KAAK,KAAK,gBAAgB,gBAAgB,EAAE,IAAI,CAAC,EAClD,CACF,EACF;IACF,CAAC;WACK,OAAO;AACd,OACG,OAAoB,QACrB;IAAC;IAAa;IAAgB;IAAW,CAAC,SACvC,MAAmB,QAAQ,GAC7B,EACD;AACA,QAAI,eAAe,wBAAwB;AAC3C,UAAM,OAAO;;AAEf,SAAM;;;;;;;;CASV,MAAa,wBAAwB,IAA6B;EAChE,MAAM,WAAW,MAAM,KAAK,gBAAgB,YAAY,EAAE,IAAI,CAAC;EAC/D,MAAM,QAAQ,MAAM,KAAK,aAAa,cAAc,EAAE,IAAI,CAAC;EAE3D,MAAM,qBAAqB,MAAM,KAAK,aAAa,uBAAuB,EACxE,IACD,CAAC;AAEF,MAAI,oBAAoB;AAEtB,SAAM,KAAK,aAAa,WAAW;IACjC;IACA,YAAY,mBAAmB,aAAa,EAAE,cAAc,MAAM,CAAC;IACnE,YAAY,MAAM,SAAS;IAC5B,CAAC;AAGF,SAAM,KAAK,aAAa,qBAAqB,EAAE,IAAI,CAAC;GAGpD,MAAM,eAAe,MAAM,KAAK,aAAa,cAAc,EAAE,IAAI,CAAC;AAClE,SAAM,KAAK,aAAa,sBAAsB;IAC5C;IACA,OAAO;IACR,CAAC;QAEF,OAAM,KAAK,aAAa,sBAAsB;GAAE;GAAU;GAAO,CAAC;EAGpE,MAAM,kBAAkB,IAAI,OAAO;GACjC,GAAG,SAAS;GACZ,QAAQ,SAAS,KAAK,QAAQ;GAC9B,MAAM,SAAS,KAAK,QAAQ;GAC5B,SAAS,SAAS,KAAK;GACxB,CAAC;AAEF,QAAM,KAAK,gBAAgB,iBAAiB,EAAE,QAAQ,iBAAiB,CAAC;AAExE,SAAO;;;;;;;CAQT,AAAO,OAAO,IAAoB;AAEhC,MAAI,KAAK,eACP,QAAO,GAAG,KAAK,eAAe,GAAG,KAAK,OAAO,GAAG;EAIlD,MAAM,eAAe,KAAK,OAAO,OAAO;EACxC,IAAIC;AAGJ,MAAI,OAAO,iBAAiB,WAC1B,UAAS;MAET,UAAS,gBAAgB;AAI3B,MAAI,WAAW,YACb,QAAO,WAAW,KAAK,OAAO,oBAAoB;MAElD,QAAO,WAAW,KAAK,OAAO,MAAM,OAAO,iBAAiB;;;;;;;CAShE,MAAM,gBAAiC;AACrC,SAAO,KAAK,kBAAkB,eAAe;;;;;;CAO/C,gBAAwB;AACtB,SAAO,KAAK,kBAAkB,eAAe"}
|
|
1
|
+
{"version":3,"file":"s3Store.mjs","names":["mappedS3ClientConfig: AWS.S3ClientConfig","HttpsAgent","request: AWS.CreateMultipartUploadCommandInput","metadata: TusUploadMetadata","offset: number","lastError: Error | null","region: string"],"sources":["../../../../src/tus/stores/s3/s3Store.ts"],"sourcesContent":["import { NoSuchKey, NotFound, S3 } from '@aws-sdk/client-s3'\nimport { NodeHttpHandler } from '@smithy/node-http-handler'\nimport { Agent as HttpsAgent } from 'https'\nimport { DataStore, ERRORS, TUS_RESUMABLE, Upload } from '@tus/utils'\nimport stream, { type Readable } from 'node:stream'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { Semaphore } from './semaphore'\nimport { S3ExpirationManager } from './expirationManager'\nimport { S3FileOperations } from './fileOperations'\nimport { S3MetadataManager } from './metadataManager'\nimport { S3PartsManager } from './partsManager'\n\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { AWSError, S3StoreConfig, TusUploadMetadata } from '../../../types'\nimport { logError } from 'payload'\n\nconst { log } = useErrorHandler()\n\nexport class S3Store extends DataStore {\n public client: S3\n public bucket: string\n public partSize = 8 * 1024 * 1024 // 8MB preferred part size\n public minPartSize = 5 * 1024 * 1024 // 5MB minimum part size\n public maxMultipartParts = 10_000\n public maxUploadSize = 5_497_558_138_880 as const // 5TiB\n public useTags = false\n public expirationPeriodInMilliseconds = 0\n protected acl?: string\n\n protected partUploadSemaphore: Semaphore\n protected metadataManager: S3MetadataManager\n protected fileOperations: S3FileOperations\n protected partsManager: S3PartsManager\n protected expirationManager: S3ExpirationManager\n protected customEndpoint: string\n\n constructor(options: S3StoreConfig) {\n super()\n const {\n maxMultipartParts,\n minPartSize,\n partSize,\n s3ClientConfig,\n maxConcurrentPartUploads,\n useTags,\n expirationPeriodInMilliseconds,\n } = options\n const { acl, bucket, ...restS3ClientConfig } = s3ClientConfig\n\n this.extensions = [\n 'creation',\n 'creation-with-upload',\n 'creation-defer-length',\n 'termination',\n 'expiration',\n ]\n\n const mappedS3ClientConfig: AWS.S3ClientConfig = {\n ...restS3ClientConfig,\n requestHandler: new NodeHttpHandler({\n connectionTimeout: 60000,\n requestTimeout: 300000,\n httpsAgent: new HttpsAgent({\n maxSockets: 1000,\n keepAlive: true,\n keepAliveMsecs: 60000,\n }),\n }),\n }\n\n this.bucket = bucket\n this.acl = acl\n this.client = new S3(mappedS3ClientConfig)\n this.customEndpoint = String(restS3ClientConfig.endpoint)\n\n this.partSize = partSize ?? this.partSize\n this.minPartSize = minPartSize ?? this.minPartSize\n this.maxMultipartParts = maxMultipartParts ?? this.maxMultipartParts\n\n this.useTags = useTags ?? this.useTags\n this.expirationPeriodInMilliseconds =\n expirationPeriodInMilliseconds ?? this.expirationPeriodInMilliseconds\n this.partUploadSemaphore = new Semaphore(maxConcurrentPartUploads ?? 60)\n\n // Initialize component managers\n this.metadataManager = new S3MetadataManager(\n this.client,\n this.bucket,\n this.shouldUseExpirationTags.bind(this),\n this.generateCompleteTag.bind(this)\n )\n\n this.fileOperations = new S3FileOperations(\n this.maxMultipartParts,\n this.maxUploadSize,\n this.minPartSize,\n this.partSize\n )\n\n this.partsManager = new S3PartsManager(\n this.client,\n this.bucket,\n this.minPartSize,\n this.partUploadSemaphore,\n this.metadataManager,\n this.fileOperations,\n this.generateCompleteTag.bind(this)\n )\n\n this.expirationManager = new S3ExpirationManager(\n this.client,\n this.bucket,\n this.expirationPeriodInMilliseconds,\n this.metadataManager.generateInfoKey.bind(this.metadataManager),\n this.metadataManager.generatePartKey.bind(this.metadataManager)\n )\n\n // Cleanup expired uploads when the store is initialized\n this.deleteExpired().catch((error) => {\n logError(error)\n })\n }\n\n /**\n * Helper method to check if expiration tags should be used\n * @returns True if expiration tags should be used\n */\n protected shouldUseExpirationTags(): boolean {\n return this.expirationPeriodInMilliseconds !== 0 && this.useTags\n }\n\n /**\n * Generates a tag for marking complete/incomplete uploads\n * @param value - Either 'false' or 'true' to mark completion status\n * @returns The tag string or undefined if tags shouldn’t be used\n */\n protected generateCompleteTag(value: 'false' | 'true'): string | undefined {\n if (!this.shouldUseExpirationTags()) {\n return undefined\n }\n return `Tus-Completed=${value}`\n }\n\n /**\n * Creates a multipart upload on S3 attaching any metadata to it.\n * Also, a `${file_id}.info` file is created which holds some information\n * about the upload itself like: `upload-id`, `upload-length`, etc.\n * @param upload - The upload object to create\n * @return Promise that resolves to the created upload object with storage information\n */\n public async create(upload: Upload): Promise<Upload> {\n log(MediaCloudLogs.S3_STORE_MULTIPART_INIT)\n\n const request: AWS.CreateMultipartUploadCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: upload.id,\n prefix: upload.metadata?.prefix ?? undefined,\n }),\n Metadata: { 'tus-version': TUS_RESUMABLE },\n }\n\n if (upload.metadata?.contentType) {\n request.ContentType = upload.metadata.contentType as string\n }\n\n if (upload.metadata?.cacheControl) {\n request.CacheControl = upload.metadata.cacheControl as string\n }\n\n if (this.acl) {\n request.ACL = this.acl as AWS.ObjectCannedACL\n }\n\n upload.creation_date = new Date().toISOString()\n\n const response = await this.client.createMultipartUpload(request)\n\n upload.storage = {\n type: 's3',\n bucket: this.bucket,\n path: response.Key as string,\n }\n await this.metadataManager.saveMetadata({\n upload,\n uploadId: response.UploadId as string,\n })\n log(MediaCloudLogs.S3_STORE_MULTIPART_CREATED)\n\n return upload\n }\n\n /**\n * Writes `buffer` to the file specified by the upload’s `id` at `offset`\n * @param readable - The readable stream to write\n * @param id - The upload ID\n * @param offset - The byte offset to write at\n * @return Promise that resolves to the new offset after writing the data\n */\n public async write(\n readable: stream.Readable,\n id: string,\n offset: number\n ): Promise<number> {\n const metadata = await this.metadataManager.getMetadata({ id })\n\n // TUS sends PATCH requests with an `upload-offset` header.\n // Offset the write by the offset in the PATCH request.\n const calculatedOffset = this.fileOperations.calculateOffsetFromParts({\n parts: await this.partsManager.retrieveParts({ id }),\n })\n const offsetDiff = offset - calculatedOffset\n const requestedOffset = offset\n\n let finalReadable = readable\n\n if (offsetDiff < 0) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n // If the offset given in the PATCH request is higher than\n // the expected offset, we need to prepend an incomplete\n // part to the readable stream, if one exists.\n if (offsetDiff > 0) {\n const incompletePart = await this.partsManager.downloadIncompletePart({\n id,\n })\n\n if (!incompletePart) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n if (incompletePart.size !== offsetDiff) {\n throw ERRORS.FILE_WRITE_ERROR\n }\n\n // Clear the incomplete part from S3 since it's going to be combined with the current request\n await this.partsManager.deleteIncompletePart({ id })\n\n // Adjust offset to account for the incomplete part\n offset = requestedOffset - incompletePart.size\n\n finalReadable = stream.Readable.from(\n (async function* () {\n yield* incompletePart.createReader({ cleanUpOnEnd: true })\n yield* readable\n })()\n )\n }\n\n const partNumber = this.fileOperations.calculatePartNumber({\n parts: await this.partsManager.retrieveParts({ id }),\n })\n\n const bytesUploaded = await this.partsManager.uploadParts({\n metadata,\n readStream: finalReadable,\n currentPartNumber: partNumber,\n offset,\n })\n\n // The size of the incomplete part should not be counted, because the\n // process of the incomplete part should be fully transparent to the user.\n const newOffset =\n requestedOffset + bytesUploaded - (offsetDiff > 0 ? offsetDiff : 0)\n\n // Check if the upload is complete\n if (metadata.file.size === newOffset) {\n try {\n const parts = await this.partsManager.retrieveParts({ id })\n await this.partsManager.finishMultipartUpload({ metadata, parts })\n\n // Update the metadata with completed state\n const completedUpload = new Upload({\n ...metadata.file,\n offset: newOffset,\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n\n await this.metadataManager.completeMetadata({ upload: completedUpload })\n } catch (error) {\n log(MediaCloudLogs.S3_STORE_UPLOAD_FAILED)\n throw error\n }\n }\n\n return newOffset\n }\n\n /**\n * Returns the current state of the upload, i.e how much data has been\n * uploaded and if the upload is complete.\n * @param id - The upload ID to retrieve\n * @returns Promise that resolves to the upload object with current offset and storage information\n */\n public async getUpload(id: string): Promise<Upload> {\n let metadata: TusUploadMetadata\n\n try {\n metadata = await this.metadataManager.getMetadata({ id })\n } catch (error) {\n if (\n error instanceof NoSuchKey ||\n error instanceof NotFound ||\n (error as AWSError)?.Code === 'NotFound' ||\n (error as AWSError)?.Code === 'NoSuchKey'\n ) {\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n\n let offset: number\n\n try {\n const parts = await this.partsManager.retrieveParts({ id })\n offset = this.fileOperations.calculateOffsetFromParts({ parts })\n } catch (error) {\n // Check if the error is caused by the upload not being found. This happens\n // when the multipart upload has already been completed or aborted.\n if (\n (error as AWSError)?.Code === 'NoSuchUpload' ||\n (error as AWSError)?.Code === 'NoSuchKey'\n ) {\n return new Upload({\n ...metadata.file,\n metadata: metadata.file.metadata,\n offset: metadata.file.size as number,\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n }\n\n log(MediaCloudLogs.S3_STORE_RETRIEVE_PARTS_ERROR)\n throw error\n }\n\n const incompletePartSize = await this.partsManager.getIncompletePartSize({\n id,\n })\n\n return new Upload({\n ...metadata.file,\n offset: offset + (incompletePartSize ?? 0),\n size: metadata.file.size,\n storage: metadata.file.storage,\n })\n }\n\n /**\n * Reads the file specified by the upload’s `id` and returns a readable stream\n * @param id - The upload ID to read\n * @returns Promise that resolves to a readable stream of the file's contents\n */\n async read(id: string): Promise<Readable> {\n log(MediaCloudLogs.S3_STORE_READ_ATTEMPT)\n let retries = 3\n let lastError: Error | null = null\n\n while (retries > 0) {\n try {\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: id,\n })\n log(MediaCloudLogs.S3_STORE_READ_SUCCESS)\n return data.Body as Readable\n } catch (error) {\n log(MediaCloudLogs.S3_STORE_READ_RETRY)\n lastError = error as Error\n retries--\n\n if (retries > 0) {\n // Wait a bit before retrying, in case S3 needs time for consistency\n await new Promise((resolve) => setTimeout(resolve, 100))\n }\n }\n }\n\n log(MediaCloudLogs.S3_STORE_READ_FAILED)\n throw lastError ?? new Error(`Failed to read file ${id}`)\n }\n\n /**\n *\n * Moves the file specified by its `oldKey` to `newKey`.\n * @param oldKey - The current S3 key of the file to be moved\n * @param newKey - The new S3 key to move the file to\n * @return Promise that resolves when the file has been successfully moved, or rejects with an error if the move operation fails\n */\n public async copy(oldKey: string, newKey: string): Promise<void> {\n try {\n await this.client.copyObject({\n Bucket: this.bucket,\n CopySource: encodeURI(`${this.bucket}/${oldKey}`),\n Key: newKey,\n })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: oldKey,\n })\n } catch (error) {\n if (\n (error as AWSError)?.code &&\n ['NoSuchKey', 'NoSuchUpload', 'NotFound'].includes(\n (error as AWSError).Code || ''\n )\n ) {\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error as Error\n }\n }\n\n /**\n * Removes files specified by the upload’s `id`\n * @param id - The upload ID to remove\n * @returns Promise that resolves when the file and its metadata have been removed\n */\n public async remove(id: string): Promise<void> {\n try {\n const { 'upload-id': uploadId, file } =\n await this.metadataManager.getMetadata({ id })\n if (uploadId) {\n await this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: id,\n UploadId: uploadId,\n })\n }\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n },\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\n },\n })\n } catch (error) {\n if (\n (error as AWSError)?.code &&\n ['NoSuchKey', 'NoSuchUpload', 'NotFound'].includes(\n (error as AWSError).Code || ''\n )\n ) {\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n }\n\n /**\n * Removes the .info file specified by the upload’s `id`\n * @param id - The upload ID to clean up\n * @returns Promise that resolves when the metadata file has been removed\n */\n public async cleanup(id: string): Promise<void> {\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: [\n {\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n },\n {\n Key: this.metadataManager.generateInfoKey({ id }),\n },\n ],\n },\n })\n } catch (error) {\n if (\n (error as AWSError)?.code &&\n ['NoSuchKey', 'NoSuchUpload', 'NotFound'].includes(\n (error as AWSError).Code || ''\n )\n ) {\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n throw ERRORS.FILE_NOT_FOUND\n }\n throw error\n }\n }\n\n /**\n * Combine all multipart uploads into a single object\n * @param id - The upload ID to complete\n * @returns Promise that resolves to the completed upload object with storage information\n */\n public async completeMultipartUpload(id: string): Promise<Upload> {\n const metadata = await this.metadataManager.getMetadata({ id })\n const parts = await this.partsManager.retrieveParts({ id })\n\n const incompletePartInfo = await this.partsManager.downloadIncompletePart({\n id,\n })\n\n if (incompletePartInfo) {\n // Upload the incomplete part as a regular part\n await this.partsManager.uploadPart({\n metadata,\n readStream: incompletePartInfo.createReader({ cleanUpOnEnd: true }),\n partNumber: parts.length + 1,\n })\n\n // Remove the incomplete part\n await this.partsManager.deleteIncompletePart({ id })\n\n // Re-fetch parts to include the newly uploaded part\n const updatedParts = await this.partsManager.retrieveParts({ id })\n await this.partsManager.finishMultipartUpload({\n metadata,\n parts: updatedParts,\n })\n } else {\n await this.partsManager.finishMultipartUpload({ metadata, parts })\n }\n\n const completedUpload = new Upload({\n ...metadata.file,\n offset: metadata.file.size ?? 0,\n size: metadata.file.size ?? 0,\n storage: metadata.file.storage,\n })\n\n await this.metadataManager.completeMetadata({ upload: completedUpload })\n\n return completedUpload\n }\n\n /**\n * Get the full S3 URL for an uploaded file\n * @param id - The upload ID to get the URL for\n * @returns The full URL to access the uploaded file on S3\n */\n public getUrl(id: string): string {\n // Use the custom endpoint if available\n if (this.customEndpoint) {\n return `${this.customEndpoint}/${this.bucket}/${id}`\n }\n\n // Fallback to standard AWS S3 URL format\n const regionConfig = this.client.config.region\n let region: string\n\n // If region is a function, we can't resolve it synchronously, so use a fallback\n if (typeof regionConfig === 'function') {\n region = 'us-east-1' // fallback for sync calls\n } else {\n region = regionConfig || 'us-east-1'\n }\n\n // Standard AWS S3 URL format\n if (region === 'us-east-1') {\n return `https://${this.bucket}.s3.amazonaws.com/${id}`\n } else {\n return `https://${this.bucket}.s3.${region}.amazonaws.com/${id}`\n }\n }\n\n /**\n * Deletes expired incomplete uploads.\n * Returns the number of deleted uploads.\n * @returns Promise that resolves to the number of deleted uploads\n */\n async deleteExpired(): Promise<number> {\n return this.expirationManager.deleteExpired()\n }\n\n /**\n * Returns the expiration period in milliseconds\n * @return The expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationManager.getExpiration()\n }\n}\n"],"mappings":"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"}
|
package/dist/utils/file.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions, MimeType } from "../types/index.mjs";
|
|
2
2
|
import { S3Store } from "../tus/stores/s3/s3Store.mjs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as payload3 from "payload";
|
|
4
4
|
|
|
5
5
|
//#region src/utils/file.d.ts
|
|
6
6
|
|
|
@@ -35,7 +35,7 @@ interface CreateFileEndpointsArgs {
|
|
|
35
35
|
pluginOptions: MediaCloudPluginOptions;
|
|
36
36
|
}
|
|
37
37
|
declare function createFileEndpoints(args: CreateFileEndpointsArgs): {
|
|
38
|
-
handler:
|
|
38
|
+
handler: payload3.PayloadHandler;
|
|
39
39
|
method: "get";
|
|
40
40
|
path: string;
|
|
41
41
|
}[];
|
package/dist/utils/mux.d.mts
CHANGED
package/dist/utils/tus.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
2
|
import { S3Store } from "../tus/stores/s3/s3Store.mjs";
|
|
3
|
-
import
|
|
4
|
-
import * as payload2 from "payload";
|
|
3
|
+
import * as payload1 from "payload";
|
|
5
4
|
import { PayloadRequest } from "payload";
|
|
5
|
+
import { Server } from "@tus/server";
|
|
6
6
|
|
|
7
7
|
//#region src/utils/tus.d.ts
|
|
8
8
|
|
|
@@ -45,11 +45,11 @@ declare function createTusEndpoints(args: CreateTusEndpointsArgs): ({
|
|
|
45
45
|
method: "delete";
|
|
46
46
|
path: string;
|
|
47
47
|
} | {
|
|
48
|
-
handler:
|
|
48
|
+
handler: payload1.PayloadHandler;
|
|
49
49
|
method: "get";
|
|
50
50
|
path: string;
|
|
51
51
|
} | {
|
|
52
|
-
handler:
|
|
52
|
+
handler: payload1.PayloadHandler;
|
|
53
53
|
method: "post";
|
|
54
54
|
path: string;
|
|
55
55
|
})[];
|