@maas/payload-plugin-media-cloud 0.0.8 → 0.0.10

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.
Files changed (122) hide show
  1. package/dist/adapter/{handleDelete.d.ts → handleDelete.d.mts} +2 -2
  2. package/dist/adapter/{handleDelete.js → handleDelete.mjs} +3 -3
  3. package/dist/adapter/handleDelete.mjs.map +1 -0
  4. package/dist/adapter/{handleUpload.d.ts → handleUpload.d.mts} +1 -1
  5. package/dist/adapter/{handleUpload.js → handleUpload.mjs} +1 -1
  6. package/dist/adapter/handleUpload.mjs.map +1 -0
  7. package/dist/adapter/{staticHandler.d.ts → staticHandler.d.mts} +2 -2
  8. package/dist/adapter/{staticHandler.js → staticHandler.mjs} +1 -1
  9. package/dist/adapter/staticHandler.mjs.map +1 -0
  10. package/dist/adapter/{storageAdapter.d.ts → storageAdapter.d.mts} +3 -3
  11. package/dist/adapter/{storageAdapter.js → storageAdapter.mjs} +4 -4
  12. package/dist/adapter/storageAdapter.mjs.map +1 -0
  13. package/dist/collections/{mediaCollection.d.ts → mediaCollection.d.mts} +2 -2
  14. package/dist/collections/{mediaCollection.js → mediaCollection.mjs} +24 -5
  15. package/dist/collections/mediaCollection.mjs.map +1 -0
  16. package/dist/components/index.d.mts +7 -0
  17. package/dist/components/index.mjs +5 -0
  18. package/dist/components/mux-preview/index.d.mts +2 -0
  19. package/dist/components/mux-preview/index.mjs +3 -0
  20. package/dist/components/mux-preview/{mux-preview.d.ts → mux-preview.d.mts} +1 -1
  21. package/dist/components/mux-preview/{mux-preview.js → mux-preview.mjs} +2 -11
  22. package/dist/components/mux-preview/mux-preview.mjs.map +1 -0
  23. package/dist/components/upload-handler/index.d.mts +2 -0
  24. package/dist/components/upload-handler/index.mjs +3 -0
  25. package/dist/components/upload-handler/{upload-handler.d.ts → upload-handler.d.mts} +1 -1
  26. package/dist/components/upload-handler/{upload-handler.js → upload-handler.mjs} +17 -19
  27. package/dist/components/upload-handler/upload-handler.mjs.map +1 -0
  28. package/dist/components/upload-manager/index.d.mts +2 -0
  29. package/dist/components/upload-manager/index.mjs +3 -0
  30. package/dist/components/upload-manager/upload-manager.css +0 -1
  31. package/dist/components/upload-manager/{upload-manager.d.ts → upload-manager.d.mts} +1 -1
  32. package/dist/components/upload-manager/upload-manager.mjs +274 -0
  33. package/dist/components/upload-manager/upload-manager.mjs.map +1 -0
  34. package/dist/endpoints/{muxAssetHandler.d.ts → muxAssetHandler.d.mts} +1 -1
  35. package/dist/endpoints/{muxAssetHandler.js → muxAssetHandler.mjs} +3 -4
  36. package/dist/endpoints/muxAssetHandler.mjs.map +1 -0
  37. package/dist/endpoints/{muxCreateUploadHandler.d.ts → muxCreateUploadHandler.d.mts} +2 -2
  38. package/dist/endpoints/{muxCreateUploadHandler.js → muxCreateUploadHandler.mjs} +4 -5
  39. package/dist/endpoints/muxCreateUploadHandler.mjs.map +1 -0
  40. package/dist/endpoints/{muxWebhookHandler.d.ts → muxWebhookHandler.d.mts} +1 -1
  41. package/dist/endpoints/{muxWebhookHandler.js → muxWebhookHandler.mjs} +1 -1
  42. package/dist/endpoints/muxWebhookHandler.mjs.map +1 -0
  43. package/dist/endpoints/tusPostProcessorHandler.d.mts +11 -0
  44. package/dist/endpoints/tusPostProcessorHandler.mjs +48 -0
  45. package/dist/endpoints/tusPostProcessorHandler.mjs.map +1 -0
  46. package/dist/error-handler/dist/{index.js → index.mjs} +4 -8
  47. package/dist/error-handler/dist/index.mjs.map +1 -0
  48. package/dist/hooks/{useEmitter.d.ts → useEmitter.d.mts} +1 -1
  49. package/dist/hooks/{useEmitter.js → useEmitter.mjs} +1 -1
  50. package/dist/hooks/useEmitter.mjs.map +1 -0
  51. package/dist/hooks/useErrorHandler.d.mts +177 -0
  52. package/dist/hooks/{useErrorHandler.js → useErrorHandler.mjs} +3 -3
  53. package/dist/hooks/useErrorHandler.mjs.map +1 -0
  54. package/dist/index.d.mts +3 -0
  55. package/dist/index.mjs +3 -0
  56. package/dist/{plugin.d.ts → plugin.d.mts} +2 -2
  57. package/dist/{plugin.js → plugin.mjs} +23 -14
  58. package/dist/plugin.mjs.map +1 -0
  59. package/dist/tus/stores/s3/{expiration-manager.d.ts → expiration-manager.d.mts} +1 -1
  60. package/dist/tus/stores/s3/{expiration-manager.js → expiration-manager.mjs} +1 -1
  61. package/dist/tus/stores/s3/expiration-manager.mjs.map +1 -0
  62. package/dist/tus/stores/s3/{file-operations.d.ts → file-operations.d.mts} +1 -1
  63. package/dist/tus/stores/s3/{file-operations.js → file-operations.mjs} +4 -5
  64. package/dist/tus/stores/s3/file-operations.mjs.map +1 -0
  65. package/dist/tus/stores/s3/{metadata-manager.d.ts → metadata-manager.d.mts} +2 -2
  66. package/dist/tus/stores/s3/{metadata-manager.js → metadata-manager.mjs} +3 -3
  67. package/dist/tus/stores/s3/metadata-manager.mjs.map +1 -0
  68. package/dist/tus/stores/s3/{parts-manager.d.ts → parts-manager.d.mts} +5 -5
  69. package/dist/tus/stores/s3/{parts-manager.js → parts-manager.mjs} +11 -15
  70. package/dist/tus/stores/s3/parts-manager.mjs.map +1 -0
  71. package/dist/tus/stores/s3/{s3-store.d.ts → s3-store.d.mts} +7 -7
  72. package/dist/tus/stores/s3/{s3-store.js → s3-store.mjs} +18 -29
  73. package/dist/tus/stores/s3/s3-store.mjs.map +1 -0
  74. package/dist/tus/stores/s3/{semaphore.d.ts → semaphore.d.mts} +1 -1
  75. package/dist/tus/stores/s3/{semaphore.js → semaphore.mjs} +2 -3
  76. package/dist/tus/stores/s3/semaphore.mjs.map +1 -0
  77. package/dist/types/errors.d.mts +142 -0
  78. package/dist/types/{errors.js → errors.mjs} +13 -1
  79. package/dist/types/errors.mjs.map +1 -0
  80. package/dist/types/{index.d.ts → index.d.mts} +1 -1
  81. package/dist/types/index.mjs +1 -0
  82. package/dist/utils/{file.d.ts → file.d.mts} +1 -1
  83. package/dist/utils/{file.js → file.mjs} +7 -11
  84. package/dist/utils/file.mjs.map +1 -0
  85. package/package.json +20 -16
  86. package/dist/adapter/handleDelete.js.map +0 -1
  87. package/dist/adapter/handleUpload.js.map +0 -1
  88. package/dist/adapter/staticHandler.js.map +0 -1
  89. package/dist/adapter/storageAdapter.js.map +0 -1
  90. package/dist/collections/mediaCollection.js.map +0 -1
  91. package/dist/components/index.d.ts +0 -4
  92. package/dist/components/index.js +0 -5
  93. package/dist/components/mux-preview/index.d.ts +0 -2
  94. package/dist/components/mux-preview/index.js +0 -3
  95. package/dist/components/mux-preview/mux-preview.js.map +0 -1
  96. package/dist/components/upload-handler/index.d.ts +0 -2
  97. package/dist/components/upload-handler/index.js +0 -3
  98. package/dist/components/upload-handler/upload-handler.js.map +0 -1
  99. package/dist/components/upload-manager/index.d.ts +0 -2
  100. package/dist/components/upload-manager/index.js +0 -3
  101. package/dist/components/upload-manager/upload-manager.js +0 -315
  102. package/dist/components/upload-manager/upload-manager.js.map +0 -1
  103. package/dist/endpoints/muxAssetHandler.js.map +0 -1
  104. package/dist/endpoints/muxCreateUploadHandler.js.map +0 -1
  105. package/dist/endpoints/muxWebhookHandler.js.map +0 -1
  106. package/dist/error-handler/dist/index.js.map +0 -1
  107. package/dist/hooks/useEmitter.js.map +0 -1
  108. package/dist/hooks/useErrorHandler.d.ts +0 -12
  109. package/dist/hooks/useErrorHandler.js.map +0 -1
  110. package/dist/index.d.ts +0 -3
  111. package/dist/index.js +0 -3
  112. package/dist/plugin.js.map +0 -1
  113. package/dist/tus/stores/s3/expiration-manager.js.map +0 -1
  114. package/dist/tus/stores/s3/file-operations.js.map +0 -1
  115. package/dist/tus/stores/s3/metadata-manager.js.map +0 -1
  116. package/dist/tus/stores/s3/parts-manager.js.map +0 -1
  117. package/dist/tus/stores/s3/s3-store.js.map +0 -1
  118. package/dist/tus/stores/s3/semaphore.js.map +0 -1
  119. package/dist/types/errors.d.ts +0 -8
  120. package/dist/types/errors.js.map +0 -1
  121. package/dist/types/index.js +0 -0
  122. package/dist/utils/file.js.map +0 -1
@@ -0,0 +1,177 @@
1
+ import { LogLevel } from "@maas/error-handler";
2
+
3
+ //#region src/hooks/useErrorHandler.d.ts
4
+ declare function useErrorHandler(): {
5
+ logError: (errorEntry: {
6
+ readonly message: "Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux";
7
+ readonly errorCode: 400;
8
+ } | {
9
+ readonly message: "Mux configuration is missing. Mux features will not be available";
10
+ readonly errorCode: 400;
11
+ } | {
12
+ readonly message: "No upload-id found for upload";
13
+ readonly errorCode: 400;
14
+ } | {
15
+ readonly message: "Error deleting Mux asset";
16
+ readonly errorCode: 500;
17
+ } | {
18
+ readonly message: "Mux video upload failed";
19
+ readonly errorCode: 500;
20
+ } | {
21
+ readonly message: "Mux direct upload failed";
22
+ readonly errorCode: 500;
23
+ } | {
24
+ readonly message: "Error in Mux create upload handler";
25
+ readonly errorCode: 500;
26
+ } | {
27
+ readonly message: "Request does not support json() method";
28
+ readonly errorCode: 400;
29
+ } | {
30
+ readonly message: "S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions";
31
+ readonly errorCode: 400;
32
+ } | {
33
+ readonly message: "Error deleting file from S3";
34
+ readonly errorCode: 500;
35
+ } | {
36
+ readonly message: "Could not find a unique file name after maximum tries";
37
+ readonly errorCode: 500;
38
+ } | {
39
+ readonly message: "TUS file upload error occurred";
40
+ readonly errorCode: 500;
41
+ } | {
42
+ readonly message: "Unable to determine file type";
43
+ readonly errorCode: 400;
44
+ } | {
45
+ readonly message: "Error determining file type";
46
+ readonly errorCode: 500;
47
+ } | {
48
+ readonly message: "Error sanitizing filename";
49
+ readonly errorCode: 500;
50
+ } | {
51
+ readonly message: "Filename is missing, cannot parse file";
52
+ readonly errorCode: 500;
53
+ } | {
54
+ readonly message: "File not found";
55
+ readonly errorCode: 404;
56
+ } | {
57
+ readonly message: "Error setting media dimensions";
58
+ readonly errorCode: 500;
59
+ } | {
60
+ readonly message: "No upload URL provided, cannot parse upload ID";
61
+ readonly errorCode: 400;
62
+ } | {
63
+ readonly message: "Upload handler error occurred";
64
+ readonly errorCode: 500;
65
+ } | {
66
+ readonly message: "Polling error for upload";
67
+ readonly errorCode: 500;
68
+ } | {
69
+ readonly message: "Payload Media Cloud plugin is not configured";
70
+ readonly errorCode: 500;
71
+ } | {
72
+ readonly message: "Error in namingFunction";
73
+ readonly errorCode: 500;
74
+ }, overrideLevel?: LogLevel) => void;
75
+ throwError: (errorEntry: {
76
+ readonly message: "Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux";
77
+ readonly errorCode: 400;
78
+ } | {
79
+ readonly message: "Mux configuration is missing. Mux features will not be available";
80
+ readonly errorCode: 400;
81
+ } | {
82
+ readonly message: "No upload-id found for upload";
83
+ readonly errorCode: 400;
84
+ } | {
85
+ readonly message: "Error deleting Mux asset";
86
+ readonly errorCode: 500;
87
+ } | {
88
+ readonly message: "Mux video upload failed";
89
+ readonly errorCode: 500;
90
+ } | {
91
+ readonly message: "Mux direct upload failed";
92
+ readonly errorCode: 500;
93
+ } | {
94
+ readonly message: "Error in Mux create upload handler";
95
+ readonly errorCode: 500;
96
+ } | {
97
+ readonly message: "Request does not support json() method";
98
+ readonly errorCode: 400;
99
+ } | {
100
+ readonly message: "S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions";
101
+ readonly errorCode: 400;
102
+ } | {
103
+ readonly message: "Error deleting file from S3";
104
+ readonly errorCode: 500;
105
+ } | {
106
+ readonly message: "Could not find a unique file name after maximum tries";
107
+ readonly errorCode: 500;
108
+ } | {
109
+ readonly message: "TUS file upload error occurred";
110
+ readonly errorCode: 500;
111
+ } | {
112
+ readonly message: "Unable to determine file type";
113
+ readonly errorCode: 400;
114
+ } | {
115
+ readonly message: "Error determining file type";
116
+ readonly errorCode: 500;
117
+ } | {
118
+ readonly message: "Error sanitizing filename";
119
+ readonly errorCode: 500;
120
+ } | {
121
+ readonly message: "Filename is missing, cannot parse file";
122
+ readonly errorCode: 500;
123
+ } | {
124
+ readonly message: "File not found";
125
+ readonly errorCode: 404;
126
+ } | {
127
+ readonly message: "Error setting media dimensions";
128
+ readonly errorCode: 500;
129
+ } | {
130
+ readonly message: "No upload URL provided, cannot parse upload ID";
131
+ readonly errorCode: 400;
132
+ } | {
133
+ readonly message: "Upload handler error occurred";
134
+ readonly errorCode: 500;
135
+ } | {
136
+ readonly message: "Polling error for upload";
137
+ readonly errorCode: 500;
138
+ } | {
139
+ readonly message: "Payload Media Cloud plugin is not configured";
140
+ readonly errorCode: 500;
141
+ } | {
142
+ readonly message: "Error in namingFunction";
143
+ readonly errorCode: 500;
144
+ }) => never;
145
+ log: (logEntry: {
146
+ readonly message: "Initializing multipart upload";
147
+ } | {
148
+ readonly message: "Multipart upload created";
149
+ } | {
150
+ readonly message: "Failed to finish upload";
151
+ } | {
152
+ readonly message: "Attempting to read file from S3";
153
+ } | {
154
+ readonly message: "Successfully read file from S3";
155
+ } | {
156
+ readonly message: "Failed to read file, retries left";
157
+ } | {
158
+ readonly message: "Failed to read file after all retries";
159
+ } | {
160
+ readonly message: "No file found";
161
+ } | {
162
+ readonly message: "Error retrieving parts";
163
+ } | {
164
+ readonly message: "Saving metadata";
165
+ } | {
166
+ readonly message: "Metadata file saved";
167
+ } | {
168
+ readonly message: "Removing cached data";
169
+ } | {
170
+ readonly message: "Finished uploading incomplete part";
171
+ } | {
172
+ readonly message: "Failed to remove chunk";
173
+ }) => void;
174
+ };
175
+ //#endregion
176
+ export { useErrorHandler };
177
+ //# sourceMappingURL=useErrorHandler.d.mts.map
@@ -1,5 +1,5 @@
1
- import { MediaCloudErrors, MediaCloudLogs } from "../types/errors.js";
2
- import { LogLevel, createLogger } from "../error-handler/dist/index.js";
1
+ import { MediaCloudErrors, MediaCloudLogs } from "../types/errors.mjs";
2
+ import { LogLevel, createLogger } from "../error-handler/dist/index.mjs";
3
3
 
4
4
  //#region src/hooks/useErrorHandler.ts
5
5
  function useErrorHandler() {
@@ -18,4 +18,4 @@ function useErrorHandler() {
18
18
 
19
19
  //#endregion
20
20
  export { useErrorHandler };
21
- //# sourceMappingURL=useErrorHandler.js.map
21
+ //# sourceMappingURL=useErrorHandler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useErrorHandler.mjs","names":[],"sources":["../../src/hooks/useErrorHandler.ts"],"sourcesContent":["import { createLogger, LogLevel } from '@maas/error-handler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../types/errors'\n\nexport function useErrorHandler() {\n const { logError, throwError, log } = createLogger({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n level: LogLevel.ERROR,\n errors: MediaCloudErrors,\n logs: MediaCloudLogs,\n })\n\n return {\n logError,\n throwError,\n log,\n }\n}\n"],"mappings":";;;;AAGA,SAAgB,kBAAkB;CAChC,MAAM,EAAE,UAAU,YAAY,QAAQ,aAAa;EACjD,QAAQ;EACR,OAAO,SAAS;EAChB,QAAQ;EACR,MAAM;EACP,CAAC;AAEF,QAAO;EACL;EACA;EACA;EACD"}
@@ -0,0 +1,3 @@
1
+ import { MediaCloudPluginOptions } from "./types/index.mjs";
2
+ import { mediaCloudPlugin } from "./plugin.mjs";
3
+ export { type MediaCloudPluginOptions, mediaCloudPlugin };
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { mediaCloudPlugin } from "./plugin.mjs";
2
+
3
+ export { mediaCloudPlugin };
@@ -1,4 +1,4 @@
1
- import { MediaCloudPluginOptions } from "./types/index.js";
1
+ import { MediaCloudPluginOptions } from "./types/index.mjs";
2
2
  import { Config } from "payload";
3
3
 
4
4
  //#region src/plugin.d.ts
@@ -12,4 +12,4 @@ import { Config } from "payload";
12
12
  declare function mediaCloudPlugin(pluginOptions: MediaCloudPluginOptions): (config: Config) => Config;
13
13
  //#endregion
14
14
  export { mediaCloudPlugin };
15
- //# sourceMappingURL=plugin.d.ts.map
15
+ //# sourceMappingURL=plugin.d.mts.map
@@ -1,12 +1,13 @@
1
- import { MediaCloudErrors } from "./types/errors.js";
2
- import { useErrorHandler } from "./hooks/useErrorHandler.js";
3
- import { getStorageAdapter } from "./adapter/storageAdapter.js";
4
- import { getMediaCollection } from "./collections/mediaCollection.js";
5
- import { getMuxAssetHandler } from "./endpoints/muxAssetHandler.js";
6
- import { getMuxCreateUploadHandler } from "./endpoints/muxCreateUploadHandler.js";
7
- import { getMuxWebhookHandler } from "./endpoints/muxWebhookHandler.js";
8
- import { S3Store } from "./tus/stores/s3/s3-store.js";
9
- import { generateUniqueFilename } from "./utils/file.js";
1
+ import { MediaCloudErrors } from "./types/errors.mjs";
2
+ import { useErrorHandler } from "./hooks/useErrorHandler.mjs";
3
+ import { getStorageAdapter } from "./adapter/storageAdapter.mjs";
4
+ import { getMediaCollection } from "./collections/mediaCollection.mjs";
5
+ import { getMuxAssetHandler } from "./endpoints/muxAssetHandler.mjs";
6
+ import { getMuxCreateUploadHandler } from "./endpoints/muxCreateUploadHandler.mjs";
7
+ import { getMuxWebhookHandler } from "./endpoints/muxWebhookHandler.mjs";
8
+ import { getTusPostProcessorHandler } from "./endpoints/tusPostProcessorHandler.mjs";
9
+ import { S3Store } from "./tus/stores/s3/s3-store.mjs";
10
+ import { generateUniqueFilename } from "./utils/file.mjs";
10
11
  import Mux from "@mux/mux-node";
11
12
  import { cloudStoragePlugin } from "@payloadcms/plugin-cloud-storage";
12
13
  import { initClientUploads } from "@payloadcms/plugin-cloud-storage/utilities";
@@ -67,8 +68,7 @@ function createTusServer(args) {
67
68
  datastore: s3Store,
68
69
  namingFunction: async (req, metadata) => {
69
70
  try {
70
- const payload = await getPayload({ config: sanitizeConfig(config) });
71
- const { docs } = await payload.find({
71
+ const { docs } = await (await getPayload({ config: sanitizeConfig(config) })).find({
72
72
  collection: "media",
73
73
  where: { filename: { equals: metadata?.filename || "" } }
74
74
  });
@@ -88,7 +88,8 @@ function createTusServer(args) {
88
88
  * @param tusServer - The TUS server instance
89
89
  * @returns An array of endpoint configurations
90
90
  */
91
- function createTusEndpoints(tusServer) {
91
+ function createTusEndpoints(args) {
92
+ const { tusServer, s3Store } = args;
92
93
  /**
93
94
  * Handles TUS requests through the server
94
95
  * @param req - The payload request object
@@ -127,6 +128,11 @@ function createTusEndpoints(tusServer) {
127
128
  handler: tusHandler,
128
129
  method: "delete",
129
130
  path: "/uploads/:id"
131
+ },
132
+ {
133
+ handler: getTusPostProcessorHandler({ s3Store }),
134
+ method: "get",
135
+ path: "/uploads/:filename/process"
130
136
  }
131
137
  ];
132
138
  }
@@ -228,7 +234,10 @@ function mediaCloudPlugin(pluginOptions) {
228
234
  collections: [...config.collections ?? [], mediaCollection],
229
235
  endpoints: [
230
236
  ...config.endpoints ?? [],
231
- ...createTusEndpoints(tusServer),
237
+ ...createTusEndpoints({
238
+ tusServer,
239
+ s3Store
240
+ }),
232
241
  ...createMuxEndpoints({
233
242
  getMuxClient,
234
243
  pluginOptions
@@ -241,4 +250,4 @@ function mediaCloudPlugin(pluginOptions) {
241
250
 
242
251
  //#endregion
243
252
  export { mediaCloudPlugin };
244
- //# sourceMappingURL=plugin.js.map
253
+ //# sourceMappingURL=plugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.mjs","names":["muxClient: Mux | null","TusServer","mappedConfig: Config"],"sources":["../src/plugin.ts"],"sourcesContent":["import Mux from '@mux/mux-node'\n\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport { initClientUploads } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getPayload, sanitizeConfig } from 'payload'\nimport { Server as TusServer, type DataStore } from '@tus/server'\n\nimport { MediaCloudErrors } from './types/errors'\nimport { useErrorHandler } from './hooks/useErrorHandler'\nimport { getStorageAdapter } from './adapter/storageAdapter'\nimport { getMediaCollection } from './collections/mediaCollection'\nimport { getMuxAssetHandler } from './endpoints/muxAssetHandler'\nimport { getMuxCreateUploadHandler } from './endpoints/muxCreateUploadHandler'\nimport { getMuxWebhookHandler } from './endpoints/muxWebhookHandler'\nimport { getTusPostProcessorHandler } from './endpoints/tusPostProcessorHandler'\n\nimport { S3Store } from './tus/stores/s3/s3-store'\nimport { generateUniqueFilename } from './utils/file'\n\nimport type { Config, PayloadRequest } from 'payload'\nimport type { MediaCloudPluginOptions } from './types'\n\ninterface CreateTusServerArgs {\n config: Config\n s3Store: DataStore\n pluginOptions: MediaCloudPluginOptions\n}\n\ninterface CreateMuxEndpointsArgs {\n getMuxClient: () => Mux\n pluginOptions: MediaCloudPluginOptions\n}\n\ninterface CreateTusEndpointsArgs {\n tusServer: TusServer\n s3Store: S3Store\n}\n\nconst { logError, throwError } = useErrorHandler()\n\nlet muxClient: Mux | null = null\n\n/**\n * Validates Mux configuration options\n * @param muxConfig - The Mux configuration to validate\n * @throws {Error} When required Mux configuration is missing\n */\nfunction validateMuxConfig(muxConfig: MediaCloudPluginOptions['mux']): void {\n if (!muxConfig?.tokenId || !muxConfig?.tokenSecret) {\n throwError(MediaCloudErrors.MUX_CONFIG_MISSING)\n }\n}\n\n/**\n * Creates a new Mux client instance\n * @param muxConfig - The Mux configuration options\n * @returns A configured Mux client\n */\nfunction createMuxClient(muxConfig: MediaCloudPluginOptions['mux']): Mux {\n // Create and return Mux client instance\n return new Mux({\n tokenId: muxConfig.tokenId,\n tokenSecret: muxConfig.tokenSecret,\n webhookSecret: muxConfig.webhookSecret,\n })\n}\n\n/**\n * Creates and configures an S3Store instance\n * @param s3Config - The S3 configuration options\n * @returns A configured S3Store instance\n * @throws {Error} When required S3 configuration is missing\n */\nfunction createS3Store(s3Config: MediaCloudPluginOptions['s3']): S3Store {\n // Validate S3 configuration\n if (\n !s3Config?.bucket ||\n !s3Config?.region ||\n !s3Config?.accessKeyId ||\n !s3Config?.secretAccessKey\n ) {\n throwError(MediaCloudErrors.S3_CONFIG_MISSING)\n }\n\n // Create and return S3Store instance\n return new S3Store({\n s3ClientConfig: {\n acl: 'public-read',\n bucket: s3Config.bucket,\n credentials: {\n accessKeyId: s3Config.accessKeyId,\n secretAccessKey: s3Config.secretAccessKey,\n },\n endpoint: s3Config.endpoint,\n forcePathStyle: true,\n region: s3Config.region,\n },\n })\n}\n\n/**\n * Creates a TUS server instance with S3 storage\n * @param args - The arguments for creating the TUS server\n * @returns A configured TusServer instance\n */\nfunction createTusServer(args: CreateTusServerArgs): TusServer {\n const { config, s3Store, pluginOptions } = args\n\n return new TusServer({\n datastore: s3Store,\n namingFunction: async (req, metadata) => {\n try {\n // Get Payload instance\n const payload = await getPayload({\n config: sanitizeConfig(config),\n })\n\n // Check if a file with the same name already exists in the media collection\n const { docs } = await payload.find({\n collection: 'media',\n where: {\n filename: {\n equals: metadata?.filename || '',\n },\n },\n })\n\n // If a file with the same name exists, generate a unique filename\n if (docs.length > 0) {\n return generateUniqueFilename(metadata?.filename ?? '')\n } else {\n // If no file with the same name exists, return the original filename\n return metadata?.filename || generateUniqueFilename('')\n }\n } catch (_error) {\n // If an error occurs, log it and return the original filename\n logError(MediaCloudErrors.NAMING_FUNCTION_ERROR)\n return metadata?.filename || generateUniqueFilename('')\n }\n },\n path: '/api/uploads',\n respectForwardedHeaders: pluginOptions.s3.respectForwardedHeaders ?? true,\n })\n}\n\n/**\n * Creates TUS upload endpoints for file handling\n * @param tusServer - The TUS server instance\n * @returns An array of endpoint configurations\n */\nfunction createTusEndpoints(args: CreateTusEndpointsArgs) {\n const { tusServer, s3Store } = args\n\n /**\n * Handles TUS requests through the server\n * @param req - The payload request object\n * @returns The server response\n */\n function tusHandler(req: PayloadRequest) {\n return tusServer.handleWeb(req as Request)\n }\n\n return [\n { handler: tusHandler, method: 'options' as const, path: '/uploads' },\n { handler: tusHandler, method: 'post' as const, path: '/uploads' },\n { handler: tusHandler, method: 'get' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'put' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'patch' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'delete' as const, path: '/uploads/:id' },\n {\n handler: getTusPostProcessorHandler({ s3Store }),\n method: 'get' as const,\n path: '/uploads/:filename/process',\n },\n ]\n}\n\n/**\n * Creates Mux-related endpoints for asset handling\n * @param args - The arguments for creating Mux endpoints\n * @returns An array of Mux endpoint configurations\n */\nfunction createMuxEndpoints(args: CreateMuxEndpointsArgs) {\n const { getMuxClient, pluginOptions } = args\n return [\n {\n handler: getMuxWebhookHandler({ getMuxClient }),\n method: 'get' as const,\n path: '/mux/webhook',\n },\n {\n handler: getMuxCreateUploadHandler({ getMuxClient, pluginOptions }),\n method: 'post' as const,\n path: '/mux/upload',\n },\n {\n handler: getMuxAssetHandler({ getMuxClient }),\n method: 'get' as const,\n path: '/mux/asset',\n },\n ]\n}\n\n/**\n * Media Cloud Plugin for Payload CMS\n * Provides file upload capabilities using S3 storage and Mux video processing\n * @param pluginOptions - Configuration options for the plugin\n * @returns A function that configures the Payload config\n */\nexport function mediaCloudPlugin(pluginOptions: MediaCloudPluginOptions) {\n return function (config: Config): Config {\n if (!pluginOptions) {\n logError(MediaCloudErrors.PLUGIN_NOT_CONFIGURED)\n return config\n }\n\n const isPluginDisabled = pluginOptions.enabled === false\n\n /**\n * Gets or creates a Mux client instance\n * @returns The Mux client instance\n */\n function getMuxClient(): Mux {\n // Return existing Mux client if already created\n if (muxClient) {\n return muxClient\n }\n // Create and return a new Mux client\n return createMuxClient(pluginOptions.mux)\n }\n\n // Validate Mux configuration\n if (pluginOptions.mux) {\n validateMuxConfig(pluginOptions.mux)\n muxClient = getMuxClient()\n } else {\n logError(MediaCloudErrors.MUX_CONFIG_INCOMPLETE)\n muxClient = null\n }\n\n const s3Store = createS3Store(pluginOptions.s3)\n const tusServer = createTusServer({ config, s3Store, pluginOptions })\n const mediaCollection = getMediaCollection({ s3Store })\n\n if (isPluginDisabled) {\n return config\n }\n\n // Initialize client uploads\n initClientUploads({\n clientHandler:\n '@maas/payload-plugin-media-cloud/components#UploadHandler',\n collections: {\n media: {\n clientUploads: true,\n disableLocalStorage: true,\n },\n },\n config,\n enabled: true,\n serverHandler: () => {\n return Response.json(\n { message: 'Server handler is not implemented' },\n { status: 501 }\n )\n },\n serverHandlerPath: '/media-cloud/upload',\n })\n\n const cloudStorageConfig = {\n collections: {\n media: {\n adapter: getStorageAdapter({ getMuxClient, pluginOptions, s3Store }),\n clientUploads: true,\n disableLocalStorage: true,\n },\n },\n }\n\n const mappedConfig: Config = {\n ...config,\n admin: {\n ...(config.admin ?? {}),\n components: {\n ...(config.admin?.components ?? {}),\n providers: [\n '@maas/payload-plugin-media-cloud/components#UploadManagerProvider',\n ...(config.admin?.components?.providers ?? []),\n ],\n },\n },\n collections: [...(config.collections ?? []), mediaCollection],\n endpoints: [\n ...(config.endpoints ?? []),\n ...createTusEndpoints({ tusServer, s3Store }),\n ...createMuxEndpoints({ getMuxClient, pluginOptions }),\n ],\n }\n\n return cloudStoragePlugin(cloudStorageConfig)(mappedConfig)\n }\n}\n"],"mappings":"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"}
@@ -33,4 +33,4 @@ declare class S3ExpirationManager {
33
33
  }
34
34
  //#endregion
35
35
  export { S3ExpirationManager };
36
- //# sourceMappingURL=expiration-manager.d.ts.map
36
+ //# sourceMappingURL=expiration-manager.d.mts.map
@@ -73,4 +73,4 @@ var S3ExpirationManager = class {
73
73
 
74
74
  //#endregion
75
75
  export { S3ExpirationManager };
76
- //# sourceMappingURL=expiration-manager.js.map
76
+ //# sourceMappingURL=expiration-manager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expiration-manager.mjs","names":["client: S3","bucket: string","expirationPeriodInMilliseconds: number","generateInfoKey: (id: string) => string","generatePartKey: (id: string, isIncompletePart: boolean) => string","keyMarker: string | undefined","uploadIdMarker: string | undefined","listResponse: AWS.ListMultipartUploadsCommandOutput","deletions: Promise<AWS.DeleteObjectsCommandOutput>[]"],"sources":["../../../../src/tus/stores/s3/expiration-manager.ts"],"sourcesContent":["import type AWS from '@aws-sdk/client-s3'\nimport type { S3 } from '@aws-sdk/client-s3'\n\ntype GetExpirationDateArgs = {\n createdAt: string\n}\n\nexport class S3ExpirationManager {\n constructor(\n private client: S3,\n private bucket: string,\n private expirationPeriodInMilliseconds: number,\n private generateInfoKey: (id: string) => string,\n private generatePartKey: (id: string, isIncompletePart: boolean) => string\n ) {}\n\n /**\n * Calculates the expiration date for a file based on a creation date\n * @param args - The function arguments\n * @param args.createdAt - The creation date as a string\n * @returns The expiration date\n */\n getExpirationDate(args: GetExpirationDateArgs): Date {\n const { createdAt } = args\n const date = new Date(createdAt)\n return new Date(date.getTime() + this.expirationPeriodInMilliseconds)\n }\n\n /**\n * Gets the expiration period in milliseconds\n * @returns The expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationPeriodInMilliseconds\n }\n\n /**\n * Deletes expired incomplete uploads based on their initialization date.\n * Returns the number of deleted uploads.\n * @param args - The function arguments (empty object)\n * @returns Promise that resolves to the number of deleted uploads\n */\n async deleteExpired(): Promise<number> {\n // No arguments to destructure\n if (this.getExpiration() === 0) {\n return 0\n }\n\n let keyMarker: string | undefined = undefined\n let uploadIdMarker: string | undefined = undefined\n let isTruncated = true\n let deleted = 0\n\n while (isTruncated) {\n const listResponse: AWS.ListMultipartUploadsCommandOutput =\n await this.client.listMultipartUploads({\n Bucket: this.bucket,\n KeyMarker: keyMarker,\n UploadIdMarker: uploadIdMarker,\n })\n\n const expiredUploads =\n listResponse.Uploads?.filter((multiPartUpload: AWS.MultipartUpload) => {\n const initiatedDate = multiPartUpload.Initiated\n return (\n initiatedDate &&\n new Date().getTime() >\n this.getExpirationDate({\n createdAt: initiatedDate.toISOString(),\n }).getTime()\n )\n }) || []\n\n const objectsToDelete = expiredUploads.reduce(\n (all: { Key: string }[], expiredUpload: AWS.MultipartUpload) => {\n all.push(\n {\n Key: this.generateInfoKey(expiredUpload.Key as string),\n },\n {\n Key: this.generatePartKey(expiredUpload.Key as string, true),\n }\n )\n return all\n },\n [] as { Key: string }[]\n )\n\n const deletions: Promise<AWS.DeleteObjectsCommandOutput>[] = []\n\n if (objectsToDelete.length > 0) {\n deletions.push(\n this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: objectsToDelete,\n },\n })\n )\n }\n\n const abortions = expiredUploads.map(\n (expiredUpload: AWS.MultipartUpload) =>\n this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: expiredUpload.Key,\n UploadId: expiredUpload.UploadId,\n })\n )\n\n await Promise.all([...deletions, ...abortions])\n\n deleted += expiredUploads.length\n\n isTruncated = listResponse.IsTruncated || false\n keyMarker = listResponse.NextKeyMarker\n uploadIdMarker = listResponse.NextUploadIdMarker\n }\n\n return deleted\n }\n}\n"],"mappings":";AAOA,IAAa,sBAAb,MAAiC;CAC/B,YACE,AAAQA,QACR,AAAQC,QACR,AAAQC,gCACR,AAAQC,iBACR,AAAQC,iBACR;EALQ;EACA;EACA;EACA;EACA;;;;;;;;CASV,kBAAkB,MAAmC;EACnD,MAAM,EAAE,cAAc;EACtB,MAAM,OAAO,IAAI,KAAK,UAAU;AAChC,SAAO,IAAI,KAAK,KAAK,SAAS,GAAG,KAAK,+BAA+B;;;;;;CAOvE,gBAAwB;AACtB,SAAO,KAAK;;;;;;;;CASd,MAAM,gBAAiC;AAErC,MAAI,KAAK,eAAe,KAAK,EAC3B,QAAO;EAGT,IAAIC,YAAgC;EACpC,IAAIC,iBAAqC;EACzC,IAAI,cAAc;EAClB,IAAI,UAAU;AAEd,SAAO,aAAa;GAClB,MAAMC,eACJ,MAAM,KAAK,OAAO,qBAAqB;IACrC,QAAQ,KAAK;IACb,WAAW;IACX,gBAAgB;IACjB,CAAC;GAEJ,MAAM,iBACJ,aAAa,SAAS,QAAQ,oBAAyC;IACrE,MAAM,gBAAgB,gBAAgB;AACtC,WACE,kCACA,IAAI,MAAM,EAAC,SAAS,GAClB,KAAK,kBAAkB,EACrB,WAAW,cAAc,aAAa,EACvC,CAAC,CAAC,SAAS;KAEhB,IAAI,EAAE;GAEV,MAAM,kBAAkB,eAAe,QACpC,KAAwB,kBAAuC;AAC9D,QAAI,KACF,EACE,KAAK,KAAK,gBAAgB,cAAc,IAAc,EACvD,EACD,EACE,KAAK,KAAK,gBAAgB,cAAc,KAAe,KAAK,EAC7D,CACF;AACD,WAAO;MAET,EAAE,CACH;GAED,MAAMC,YAAuD,EAAE;AAE/D,OAAI,gBAAgB,SAAS,EAC3B,WAAU,KACR,KAAK,OAAO,cAAc;IACxB,QAAQ,KAAK;IACb,QAAQ,EACN,SAAS,iBACV;IACF,CAAC,CACH;GAGH,MAAM,YAAY,eAAe,KAC9B,kBACC,KAAK,OAAO,qBAAqB;IAC/B,QAAQ,KAAK;IACb,KAAK,cAAc;IACnB,UAAU,cAAc;IACzB,CAAC,CACL;AAED,SAAM,QAAQ,IAAI,CAAC,GAAG,WAAW,GAAG,UAAU,CAAC;AAE/C,cAAW,eAAe;AAE1B,iBAAc,aAAa,eAAe;AAC1C,eAAY,aAAa;AACzB,oBAAiB,aAAa;;AAGhC,SAAO"}
@@ -63,4 +63,4 @@ declare class S3FileOperations {
63
63
  declare function calculateOffsetFromParts(args: CalculateOffsetFromPartsExportArgs): number;
64
64
  //#endregion
65
65
  export { S3FileOperations, calculateOffsetFromParts };
66
- //# sourceMappingURL=file-operations.d.ts.map
66
+ //# sourceMappingURL=file-operations.d.mts.map
@@ -1,5 +1,5 @@
1
- import { MediaCloudErrors } from "../../../types/errors.js";
2
- import { useErrorHandler } from "../../../hooks/useErrorHandler.js";
1
+ import { MediaCloudErrors } from "../../../types/errors.mjs";
2
+ import { useErrorHandler } from "../../../hooks/useErrorHandler.mjs";
3
3
  import crypto from "node:crypto";
4
4
  import fs from "node:fs";
5
5
  import os from "node:os";
@@ -66,8 +66,7 @@ var S3FileOperations = class {
66
66
  try {
67
67
  await fs.promises.access(filePath, fs.constants.F_OK);
68
68
  } catch (error) {
69
- const nodeError = error;
70
- if (nodeError.code === "ENOENT") return filePath;
69
+ if (error.code === "ENOENT") return filePath;
71
70
  }
72
71
  }
73
72
  throwError(MediaCloudErrors.S3_UNIQUE_NAME_ERROR);
@@ -87,4 +86,4 @@ function calculateOffsetFromParts(args) {
87
86
 
88
87
  //#endregion
89
88
  export { S3FileOperations, calculateOffsetFromParts };
90
- //# sourceMappingURL=file-operations.js.map
89
+ //# sourceMappingURL=file-operations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-operations.mjs","names":["maxMultipartParts: number","maxUploadSize: number","minPartSize: number","preferredPartSize: number","optimalPartSize: number"],"sources":["../../../../src/tus/stores/s3/file-operations.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../../../types/errors'\n\nimport type { NodeFSError } from '../../../types'\nimport type AWS from '@aws-sdk/client-s3'\n\ntype CalculateOptimalPartSizeArgs = {\n size?: number\n}\n\ntype CalculateOffsetFromPartsArgs = {\n parts?: Array<AWS.Part>\n}\n\ntype CalculatePartNumberArgs = {\n parts: Array<AWS.Part>\n}\n\ntype GenerateUniqueTmpFileNameArgs = {\n template: string\n}\n\ntype CalculateOffsetFromPartsExportArgs = {\n parts?: Array<{ Size?: number }>\n}\n\nconst { throwError } = useErrorHandler()\n\nexport class S3FileOperations {\n constructor(\n private maxMultipartParts: number,\n private maxUploadSize: number,\n private minPartSize: number,\n private preferredPartSize: number\n ) {}\n\n /**\n * Calculates the optimal part size for S3 multipart upload\n * @param args - The function arguments\n * @param args.size - The upload size in bytes (optional)\n * @returns The optimal part size in bytes\n */\n calculateOptimalPartSize(args: CalculateOptimalPartSizeArgs): number {\n const { size } = args\n // When upload size is not known we assume largest possible value (`maxUploadSize`)\n let uploadSize = size\n if (uploadSize === undefined) {\n uploadSize = this.maxUploadSize\n }\n\n let optimalPartSize: number\n\n // When upload is smaller or equal to PreferredPartSize, we upload in just one part.\n if (uploadSize <= this.preferredPartSize) {\n optimalPartSize = uploadSize\n }\n // Does the upload fit in MaxMultipartParts parts or less with PreferredPartSize.\n else if (uploadSize <= this.preferredPartSize * this.maxMultipartParts) {\n optimalPartSize = this.preferredPartSize\n // The upload is too big for the preferred size.\n // We divide the size with the max amount of parts and round it up.\n } else {\n optimalPartSize = Math.ceil(uploadSize / this.maxMultipartParts)\n }\n\n // Always ensure the part size is at least minPartSize\n return Math.max(optimalPartSize, this.minPartSize)\n }\n\n /**\n * Calculates the offset based on uploaded parts\n * @param args - The function arguments\n * @param args.parts - Array of uploaded parts (optional)\n * @returns The total offset in bytes\n */\n calculateOffsetFromParts(args: CalculateOffsetFromPartsArgs): number {\n const { parts } = args\n return parts && parts.length > 0\n ? parts.reduce((a, b) => a + (b.Size ?? 0), 0)\n : 0\n }\n\n /**\n * Calculates the next part number based on existing parts\n * @param args - The function arguments\n * @param args.parts - Array of uploaded parts\n * @returns The next part number to use\n */\n calculatePartNumber(args: CalculatePartNumberArgs): number {\n const { parts } = args\n return parts.length > 0 ? parts[parts.length - 1].PartNumber! + 1 : 1\n }\n\n /**\n * Generates a unique temporary file name\n * @param args - The function arguments\n * @param args.template - The template string for the file name\n * @returns Promise that resolves to the unique file path\n * @throws Error if unable to find unique name after max tries\n */\n async generateUniqueTmpFileName(\n args: GenerateUniqueTmpFileNameArgs\n ): Promise<string> {\n const { template } = args\n const tries = 5\n for (let i = 0; i < tries; i++) {\n const randomId = crypto.randomBytes(16).toString('hex')\n const filePath = path.join(os.tmpdir(), `${template}${randomId}`)\n try {\n await fs.promises.access(filePath, fs.constants.F_OK)\n } catch (error) {\n const nodeError = error as NodeFSError\n if (nodeError.code === 'ENOENT') {\n return filePath\n }\n }\n }\n throwError(MediaCloudErrors.S3_UNIQUE_NAME_ERROR)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n}\n\n/**\n * Calculates the total offset from uploaded parts\n * @param args - The function arguments\n * @param args.parts - Array of parts with size information (optional)\n * @returns The total offset in bytes\n */\nexport function calculateOffsetFromParts(\n args: CalculateOffsetFromPartsExportArgs\n) {\n const { parts } = args\n return parts && parts.length > 0\n ? parts.reduce((a, b) => a + (b.Size ?? 0), 0)\n : 0\n}\n"],"mappings":";;;;;;;;AA+BA,MAAM,EAAE,eAAe,iBAAiB;AAExC,IAAa,mBAAb,MAA8B;CAC5B,YACE,AAAQA,mBACR,AAAQC,eACR,AAAQC,aACR,AAAQC,mBACR;EAJQ;EACA;EACA;EACA;;;;;;;;CASV,yBAAyB,MAA4C;EACnE,MAAM,EAAE,SAAS;EAEjB,IAAI,aAAa;AACjB,MAAI,eAAe,OACjB,cAAa,KAAK;EAGpB,IAAIC;AAGJ,MAAI,cAAc,KAAK,kBACrB,mBAAkB;WAGX,cAAc,KAAK,oBAAoB,KAAK,kBACnD,mBAAkB,KAAK;MAIvB,mBAAkB,KAAK,KAAK,aAAa,KAAK,kBAAkB;AAIlE,SAAO,KAAK,IAAI,iBAAiB,KAAK,YAAY;;;;;;;;CASpD,yBAAyB,MAA4C;EACnE,MAAM,EAAE,UAAU;AAClB,SAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C;;;;;;;;CASN,oBAAoB,MAAuC;EACzD,MAAM,EAAE,UAAU;AAClB,SAAO,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,GAAG,aAAc,IAAI;;;;;;;;;CAUtE,MAAM,0BACJ,MACiB;EACjB,MAAM,EAAE,aAAa;EACrB,MAAM,QAAQ;AACd,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,WAAW,OAAO,YAAY,GAAG,CAAC,SAAS,MAAM;GACvD,MAAM,WAAW,KAAK,KAAK,GAAG,QAAQ,EAAE,GAAG,WAAW,WAAW;AACjE,OAAI;AACF,UAAM,GAAG,SAAS,OAAO,UAAU,GAAG,UAAU,KAAK;YAC9C,OAAO;AAEd,QADkB,MACJ,SAAS,SACrB,QAAO;;;AAIb,aAAW,iBAAiB,qBAAqB;AACjD,QAAM,IAAI,OAAO;;;;;;;;;AAUrB,SAAgB,yBACd,MACA;CACA,MAAM,EAAE,UAAU;AAClB,QAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C"}
@@ -1,4 +1,4 @@
1
- import { TusUploadMetadata } from "../../../types/index.js";
1
+ import { TusUploadMetadata } from "../../../types/index.mjs";
2
2
  import { S3 } from "@aws-sdk/client-s3";
3
3
  import { KvStore, Upload } from "@tus/utils";
4
4
 
@@ -82,4 +82,4 @@ declare class S3MetadataManager {
82
82
  }
83
83
  //#endregion
84
84
  export { S3MetadataManager };
85
- //# sourceMappingURL=metadata-manager.d.ts.map
85
+ //# sourceMappingURL=metadata-manager.d.mts.map
@@ -1,5 +1,5 @@
1
- import { MediaCloudLogs } from "../../../types/errors.js";
2
- import { useErrorHandler } from "../../../hooks/useErrorHandler.js";
1
+ import { MediaCloudLogs } from "../../../types/errors.mjs";
2
+ import { useErrorHandler } from "../../../hooks/useErrorHandler.mjs";
3
3
  import { TUS_RESUMABLE, Upload } from "@tus/utils";
4
4
 
5
5
  //#region src/tus/stores/s3/metadata-manager.ts
@@ -134,4 +134,4 @@ var S3MetadataManager = class {
134
134
 
135
135
  //#endregion
136
136
  export { S3MetadataManager };
137
- //# sourceMappingURL=metadata-manager.js.map
137
+ //# sourceMappingURL=metadata-manager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-manager.mjs","names":["client: S3","bucket: string","cache: KvStore<TusUploadMetadata>","shouldUseExpirationTags: () => boolean","generateCompleteTag: (value: 'false' | 'true') => string | undefined","metadata: TusUploadMetadata","completedMetadata: TusUploadMetadata"],"sources":["../../../../src/tus/stores/s3/metadata-manager.ts"],"sourcesContent":["import { TUS_RESUMABLE, Upload } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type { S3 } from '@aws-sdk/client-s3'\nimport type { KvStore } from '@tus/utils'\nimport type { TusUploadMetadata } from '../../../types'\n\ntype GenerateInfoKeyArgs = {\n id: string\n}\n\ntype GeneratePartKeyArgs = {\n id: string\n isIncomplete?: boolean\n}\n\ntype GetMetadataArgs = {\n id: string\n}\n\ntype SaveMetadataArgs = {\n upload: Upload\n uploadId: string\n}\n\ntype CompleteMetadataArgs = {\n upload: Upload\n}\n\ntype ClearCacheArgs = {\n id: string\n}\n\nconst { log } = useErrorHandler()\n\nexport class S3MetadataManager {\n constructor(\n private client: S3,\n private bucket: string,\n private cache: KvStore<TusUploadMetadata>,\n private shouldUseExpirationTags: () => boolean,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n /**\n * Generates the S3 key for metadata info files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @returns The S3 key for the metadata file\n */\n generateInfoKey(args: GenerateInfoKeyArgs): string {\n const { id } = args\n return `${id}.info`\n }\n\n /**\n * Generates the S3 key for part files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @param args.isIncomplete - Whether this is an incomplete part\n * @returns The S3 key for the part file\n */\n generatePartKey(args: GeneratePartKeyArgs): string {\n const { id, isIncomplete = false } = args\n let key = id\n if (isIncomplete) {\n key += '.part'\n }\n\n // TODO: introduce ObjectPrefixing for parts and incomplete parts.\n // ObjectPrefix is prepended to the name of each S3 object that is created\n // to store uploaded files. It can be used to create a pseudo-directory\n // structure in the bucket, e.g. \"path/to/my/uploads\".\n return key\n }\n\n /**\n * Retrieves upload metadata previously saved in `${file_id}.info`.\n * There's a small and simple caching mechanism to avoid multiple\n * HTTP calls to S3.\n * @param args - The function arguments\n * @param args.id - The file ID to retrieve metadata for\n * @returns Promise that resolves to the upload metadata\n */\n async getMetadata(args: GetMetadataArgs): Promise<TusUploadMetadata> {\n const { id } = args\n const cached = await this.cache.get(id)\n if (cached) {\n return cached\n }\n\n const { Body, Metadata } = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id }),\n })\n const file = JSON.parse((await Body?.transformToString()) as string)\n const metadata: TusUploadMetadata = {\n file: new Upload({\n id,\n creation_date: file.creation_date,\n metadata: file.metadata,\n offset: Number.parseInt(file.offset, 10),\n size: Number.isFinite(file.size)\n ? Number.parseInt(file.size, 10)\n : undefined,\n storage: file.storage,\n }),\n 'tus-version': Metadata?.['tus-version'] as string,\n 'upload-id': Metadata?.['upload-id'] as string,\n }\n await this.cache.set(id, metadata)\n return metadata\n }\n\n /**\n * Saves upload metadata to a `${file_id}.info` file on S3.\n * Please note that the file is empty and the metadata is saved\n * on the S3 object's `Metadata` field, so that only a `headObject`\n * is necessary to retrieve the data.\n * @param args - The function arguments\n * @param args.upload - The upload object containing metadata\n * @param args.uploadId - The upload ID for the multipart upload\n * @returns Promise that resolves when metadata is saved\n */\n async saveMetadata(args: SaveMetadataArgs): Promise<void> {\n const { upload, uploadId } = args\n log(MediaCloudLogs.S3_STORE_METADATA_SAVING)\n await this.client.putObject({\n Body: JSON.stringify(upload),\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id: upload.id }),\n Metadata: {\n 'tus-version': TUS_RESUMABLE,\n 'upload-id': uploadId,\n },\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_METADATA_SAVED)\n }\n\n /**\n * Completes metadata by updating it with completion tags\n * @param args - The function arguments\n * @param args.upload - The completed upload object\n * @returns Promise that resolves when metadata is updated\n */\n async completeMetadata(args: CompleteMetadataArgs): Promise<void> {\n const { upload } = args\n // Always update the cache with the completed upload\n const metadata = await this.getMetadata({ id: upload.id })\n const completedMetadata: TusUploadMetadata = {\n ...metadata,\n file: upload,\n }\n await this.cache.set(upload.id, completedMetadata)\n\n if (!this.shouldUseExpirationTags()) {\n return\n }\n\n const { 'upload-id': uploadId } = metadata\n await this.client.putObject({\n Body: JSON.stringify(upload),\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id: upload.id }),\n Metadata: {\n 'tus-version': TUS_RESUMABLE,\n 'upload-id': uploadId,\n },\n Tagging: this.generateCompleteTag('true'),\n })\n }\n\n /**\n * Removes cached data for a given file\n * @param args - The function arguments\n * @param args.id - The file ID to remove from cache\n * @returns Promise that resolves when cache is cleared\n */\n async clearCache(args: ClearCacheArgs): Promise<void> {\n const { id } = args\n log(MediaCloudLogs.S3_STORE_METADATA_CACHE_CLEARED)\n await this.cache.delete(id)\n }\n}\n"],"mappings":";;;;;AAmCA,MAAM,EAAE,QAAQ,iBAAiB;AAEjC,IAAa,oBAAb,MAA+B;CAC7B,YACE,AAAQA,QACR,AAAQC,QACR,AAAQC,OACR,AAAQC,yBACR,AAAQC,qBACR;EALQ;EACA;EACA;EACA;EACA;;;;;;;;CAQV,gBAAgB,MAAmC;EACjD,MAAM,EAAE,OAAO;AACf,SAAO,GAAG,GAAG;;;;;;;;;CAUf,gBAAgB,MAAmC;EACjD,MAAM,EAAE,IAAI,eAAe,UAAU;EACrC,IAAI,MAAM;AACV,MAAI,aACF,QAAO;AAOT,SAAO;;;;;;;;;;CAWT,MAAM,YAAY,MAAmD;EACnE,MAAM,EAAE,OAAO;EACf,MAAM,SAAS,MAAM,KAAK,MAAM,IAAI,GAAG;AACvC,MAAI,OACF,QAAO;EAGT,MAAM,EAAE,MAAM,aAAa,MAAM,KAAK,OAAO,UAAU;GACrD,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,CAAC;GAClC,CAAC;EACF,MAAM,OAAO,KAAK,MAAO,MAAM,MAAM,mBAAmB,CAAY;EACpE,MAAMC,WAA8B;GAClC,MAAM,IAAI,OAAO;IACf;IACA,eAAe,KAAK;IACpB,UAAU,KAAK;IACf,QAAQ,OAAO,SAAS,KAAK,QAAQ,GAAG;IACxC,MAAM,OAAO,SAAS,KAAK,KAAK,GAC5B,OAAO,SAAS,KAAK,MAAM,GAAG,GAC9B;IACJ,SAAS,KAAK;IACf,CAAC;GACF,eAAe,WAAW;GAC1B,aAAa,WAAW;GACzB;AACD,QAAM,KAAK,MAAM,IAAI,IAAI,SAAS;AAClC,SAAO;;;;;;;;;;;;CAaT,MAAM,aAAa,MAAuC;EACxD,MAAM,EAAE,QAAQ,aAAa;AAC7B,MAAI,eAAe,yBAAyB;AAC5C,QAAM,KAAK,OAAO,UAAU;GAC1B,MAAM,KAAK,UAAU,OAAO;GAC5B,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,OAAO,IAAI,CAAC;GAC5C,UAAU;IACR,eAAe;IACf,aAAa;IACd;GACD,SAAS,KAAK,oBAAoB,QAAQ;GAC3C,CAAC;AACF,MAAI,eAAe,wBAAwB;;;;;;;;CAS7C,MAAM,iBAAiB,MAA2C;EAChE,MAAM,EAAE,WAAW;EAEnB,MAAM,WAAW,MAAM,KAAK,YAAY,EAAE,IAAI,OAAO,IAAI,CAAC;EAC1D,MAAMC,oBAAuC;GAC3C,GAAG;GACH,MAAM;GACP;AACD,QAAM,KAAK,MAAM,IAAI,OAAO,IAAI,kBAAkB;AAElD,MAAI,CAAC,KAAK,yBAAyB,CACjC;EAGF,MAAM,EAAE,aAAa,aAAa;AAClC,QAAM,KAAK,OAAO,UAAU;GAC1B,MAAM,KAAK,UAAU,OAAO;GAC5B,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,OAAO,IAAI,CAAC;GAC5C,UAAU;IACR,eAAe;IACf,aAAa;IACd;GACD,SAAS,KAAK,oBAAoB,OAAO;GAC1C,CAAC;;;;;;;;CASJ,MAAM,WAAW,MAAqC;EACpD,MAAM,EAAE,OAAO;AACf,MAAI,eAAe,gCAAgC;AACnD,QAAM,KAAK,MAAM,OAAO,GAAG"}
@@ -1,7 +1,7 @@
1
- import { Semaphore } from "./semaphore.js";
2
- import { S3FileOperations } from "./file-operations.js";
3
- import { IncompletePartInfo, TusUploadMetadata } from "../../../types/index.js";
4
- import { S3MetadataManager } from "./metadata-manager.js";
1
+ import { Semaphore } from "./semaphore.mjs";
2
+ import { S3FileOperations } from "./file-operations.mjs";
3
+ import { IncompletePartInfo, TusUploadMetadata } from "../../../types/index.mjs";
4
+ import { S3MetadataManager } from "./metadata-manager.mjs";
5
5
  import stream, { Readable } from "node:stream";
6
6
  import AWS, { S3 } from "@aws-sdk/client-s3";
7
7
  import fs from "node:fs";
@@ -127,4 +127,4 @@ declare class S3PartsManager {
127
127
  }
128
128
  //#endregion
129
129
  export { S3PartsManager };
130
- //# sourceMappingURL=parts-manager.d.ts.map
130
+ //# sourceMappingURL=parts-manager.d.mts.map
@@ -1,5 +1,5 @@
1
- import { MediaCloudErrors, MediaCloudLogs } from "../../../types/errors.js";
2
- import { useErrorHandler } from "../../../hooks/useErrorHandler.js";
1
+ import { MediaCloudErrors, MediaCloudLogs } from "../../../types/errors.mjs";
2
+ import { useErrorHandler } from "../../../hooks/useErrorHandler.mjs";
3
3
  import stream from "node:stream";
4
4
  import { NoSuchKey, NotFound } from "@aws-sdk/client-s3";
5
5
  import { StreamSplitter } from "@tus/utils";
@@ -73,8 +73,7 @@ var S3PartsManager = class {
73
73
  UploadId: metadata["upload-id"]
74
74
  };
75
75
  try {
76
- const result = await this.client.completeMultipartUpload(params);
77
- return result.Location;
76
+ return (await this.client.completeMultipartUpload(params)).Location;
78
77
  } catch (_error) {
79
78
  throwError(MediaCloudErrors.TUS_UPLOAD_ERROR);
80
79
  throw new Error();
@@ -89,16 +88,15 @@ var S3PartsManager = class {
89
88
  async getIncompletePart(args) {
90
89
  const { id } = args;
91
90
  try {
92
- const data = await this.client.getObject({
91
+ return (await this.client.getObject({
93
92
  Bucket: this.bucket,
94
93
  Key: this.metadataManager.generatePartKey({
95
94
  id,
96
95
  isIncomplete: true
97
96
  })
98
- });
99
- return data.Body;
97
+ })).Body;
100
98
  } catch (error) {
101
- if (error instanceof NoSuchKey) return void 0;
99
+ if (error instanceof NoSuchKey) return;
102
100
  throw error;
103
101
  }
104
102
  }
@@ -111,16 +109,15 @@ var S3PartsManager = class {
111
109
  async getIncompletePartSize(args) {
112
110
  const { id } = args;
113
111
  try {
114
- const data = await this.client.headObject({
112
+ return (await this.client.headObject({
115
113
  Bucket: this.bucket,
116
114
  Key: this.metadataManager.generatePartKey({
117
115
  id,
118
116
  isIncomplete: true
119
117
  })
120
- });
121
- return data.ContentLength;
118
+ })).ContentLength;
122
119
  } catch (error) {
123
- if (error instanceof NotFound) return void 0;
120
+ if (error instanceof NotFound) return;
124
121
  throw error;
125
122
  }
126
123
  }
@@ -225,9 +222,8 @@ var S3PartsManager = class {
225
222
  UploadId: metadata["upload-id"]
226
223
  };
227
224
  try {
228
- const data = await this.client.uploadPart(params);
229
225
  return {
230
- ETag: data.ETag,
226
+ ETag: (await this.client.uploadPart(params)).ETag,
231
227
  PartNumber: partNumber
232
228
  };
233
229
  } catch (_error) {
@@ -324,4 +320,4 @@ var S3PartsManager = class {
324
320
 
325
321
  //#endregion
326
322
  export { S3PartsManager };
327
- //# sourceMappingURL=parts-manager.js.map
323
+ //# sourceMappingURL=parts-manager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts-manager.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/parts-manager.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 { Readable } from 'node:stream'\nimport type AWS from '@aws-sdk/client-s3'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './file-operations'\nimport type { S3MetadataManager } from './metadata-manager'\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 { throwError, log } = 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: id,\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 const params = {\n Bucket: this.bucket,\n Key: metadata.file.id,\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)\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 try {\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\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 try {\n const data = await this.client.headObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\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 await this.client.deleteObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\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 const data = await this.client.putObject({\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({ id, isIncomplete: true }),\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n return data.ETag as string\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: metadata.file.id,\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)\n throw new Error() // This will never execute but satisfies TypeScript\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,9 +1,9 @@
1
- import { Semaphore } from "./semaphore.js";
2
- import { S3ExpirationManager } from "./expiration-manager.js";
3
- import { S3FileOperations } from "./file-operations.js";
4
- import { S3StoreConfig, TusUploadMetadata } from "../../../types/index.js";
5
- import { S3MetadataManager } from "./metadata-manager.js";
6
- import { S3PartsManager } from "./parts-manager.js";
1
+ import { Semaphore } from "./semaphore.mjs";
2
+ import { S3ExpirationManager } from "./expiration-manager.mjs";
3
+ import { S3FileOperations } from "./file-operations.mjs";
4
+ import { S3StoreConfig, TusUploadMetadata } from "../../../types/index.mjs";
5
+ import { S3MetadataManager } from "./metadata-manager.mjs";
6
+ import { S3PartsManager } from "./parts-manager.mjs";
7
7
  import stream, { Readable } from "node:stream";
8
8
  import { S3 } from "@aws-sdk/client-s3";
9
9
  import { DataStore, KvStore, Upload } from "@tus/utils";
@@ -107,4 +107,4 @@ declare class S3Store extends DataStore {
107
107
  }
108
108
  //#endregion
109
109
  export { S3Store };
110
- //# sourceMappingURL=s3-store.d.ts.map
110
+ //# sourceMappingURL=s3-store.d.mts.map